/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.kernel.dao.jdbc; import com.liferay.portal.kernel.concurrent.ConcurrentHashSet; import com.liferay.portal.kernel.concurrent.FutureListener; import com.liferay.portal.kernel.concurrent.NoticeableFuture; import com.liferay.portal.kernel.concurrent.ThreadPoolExecutor; import com.liferay.portal.kernel.executor.PortalExecutorManagerUtil; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.util.PropsKeys; import com.liferay.portal.kernel.util.PropsUtil; import com.liferay.portal.kernel.util.ProxyUtil; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; /** * @author Shuyang Zhou * @author Preston Crary */ public class AutoBatchPreparedStatementUtil { public static PreparedStatement autoBatch( PreparedStatement preparedStatement) throws SQLException { Connection connection = preparedStatement.getConnection(); DatabaseMetaData databaseMetaData = connection.getMetaData(); if (databaseMetaData.supportsBatchUpdates()) { return (PreparedStatement)ProxyUtil.newProxyInstance( ClassLoader.getSystemClassLoader(), _interfaces, new BatchInvocationHandler(preparedStatement)); } return (PreparedStatement)ProxyUtil.newProxyInstance( ClassLoader.getSystemClassLoader(), _interfaces, new NoBatchInvocationHandler(preparedStatement)); } public static PreparedStatement concurrentAutoBatch( Connection connection, String sql) throws SQLException { DatabaseMetaData databaseMetaData = connection.getMetaData(); if (databaseMetaData.supportsBatchUpdates()) { return (PreparedStatement)ProxyUtil.newProxyInstance( ClassLoader.getSystemClassLoader(), _interfaces, new ConcurrentBatchInvocationHandler(connection, sql)); } return (PreparedStatement)ProxyUtil.newProxyInstance( ClassLoader.getSystemClassLoader(), _interfaces, new ConcurrentNoBatchInvocationHandler(connection, sql)); } private static final int _HIBERNATE_JDBC_BATCH_SIZE = GetterUtil.getInteger( PropsUtil.get(PropsKeys.HIBERNATE_JDBC_BATCH_SIZE)); private static final Method _addBatchMethod; private static final Method _closeMethod; private static final Method _executeBatch; private static final Class<?>[] _interfaces = new Class<?>[] {PreparedStatement.class}; static { try { _addBatchMethod = PreparedStatement.class.getMethod("addBatch"); _closeMethod = PreparedStatement.class.getMethod("close"); _executeBatch = PreparedStatement.class.getMethod("executeBatch"); } catch (NoSuchMethodException nsme) { throw new ExceptionInInitializerError(nsme); } } private static class BatchInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.equals(_executeBatch)) { if (_count > 0) { _count = 0; return _preparedStatement.executeBatch(); } return new int[0]; } if (!method.equals(_addBatchMethod)) { return method.invoke(_preparedStatement, args); } _preparedStatement.addBatch(); if (++_count >= _HIBERNATE_JDBC_BATCH_SIZE) { _preparedStatement.executeBatch(); _count = 0; } return null; } private BatchInvocationHandler(PreparedStatement preparedStatement) { _preparedStatement = preparedStatement; } private int _count; private final PreparedStatement _preparedStatement; } private static class ConcurrentBatchInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.equals(_addBatchMethod)) { _preparedStatement.addBatch(); if (++_count >= _HIBERNATE_JDBC_BATCH_SIZE) { _executeBatch(); } return null; } if (method.equals(_executeBatch)) { if (_count > 0) { _executeBatch(); } return new int[0]; } if (method.equals(_closeMethod)) { Throwable throwable = null; for (Future<Void> future : _futures) { try { future.get(); } catch (Throwable t) { if (t instanceof ExecutionException) { t = t.getCause(); } if (throwable == null) { throwable = t; } else { throwable.addSuppressed(t); } } } if (throwable != null) { throw throwable; } } return method.invoke(_preparedStatement, args); } private ConcurrentBatchInvocationHandler( Connection connection, String sql) throws SQLException { _connection = connection; _sql = sql; _preparedStatement = _connection.prepareStatement(_sql); } private void _executeBatch() throws SQLException { _count = 0; final PreparedStatement preparedStatement = _preparedStatement; NoticeableFuture<Void> noticeableFuture = _threadPoolExecutor.submit( new Callable<Void>() { @Override public Void call() throws SQLException { try { preparedStatement.executeBatch(); } finally { preparedStatement.close(); } return null; } }); _futures.add(noticeableFuture); noticeableFuture.addFutureListener( new FutureListener<Void>() { @Override public void complete(Future<Void> future) { try { future.get(); _futures.remove(future); } catch (Throwable t) { } } }); _preparedStatement = _connection.prepareStatement(_sql); } private final Connection _connection; private int _count; private final Set<Future<Void>> _futures = new ConcurrentHashSet<>(); private PreparedStatement _preparedStatement; private final String _sql; private final ThreadPoolExecutor _threadPoolExecutor = PortalExecutorManagerUtil.getPortalExecutor( ConcurrentBatchInvocationHandler.class.getName()); } private static class ConcurrentNoBatchInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.equals(_addBatchMethod)) { _executeUpdate(); return null; } if (method.equals(_executeBatch)) { return new int[0]; } if (method.equals(_closeMethod)) { Throwable throwable = null; for (Future<Void> future : _futures) { try { future.get(); } catch (Throwable t) { if (t instanceof ExecutionException) { t = t.getCause(); } if (throwable == null) { throwable = t; } else { throwable.addSuppressed(t); } } } if (throwable != null) { throw throwable; } } return method.invoke(_preparedStatement, args); } private ConcurrentNoBatchInvocationHandler( Connection connection, String sql) throws SQLException { _connection = connection; _sql = sql; _preparedStatement = _connection.prepareStatement(_sql); } private void _executeUpdate() throws SQLException { final PreparedStatement preparedStatement = _preparedStatement; NoticeableFuture<Void> noticeableFuture = _threadPoolExecutor.submit( new Callable<Void>() { @Override public Void call() throws SQLException { try { preparedStatement.executeUpdate(); } finally { preparedStatement.close(); } return null; } }); _futures.add(noticeableFuture); noticeableFuture.addFutureListener( new FutureListener<Void>() { @Override public void complete(Future<Void> future) { try { future.get(); _futures.remove(future); } catch (Throwable t) { } } }); _preparedStatement = _connection.prepareStatement(_sql); } private final Connection _connection; private final Set<Future<Void>> _futures = new ConcurrentHashSet<>(); private PreparedStatement _preparedStatement; private final String _sql; private final ThreadPoolExecutor _threadPoolExecutor = PortalExecutorManagerUtil.getPortalExecutor( ConcurrentNoBatchInvocationHandler.class.getName()); } private static class NoBatchInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.equals(_addBatchMethod)) { _preparedStatement.executeUpdate(); return null; } if (method.equals(_executeBatch)) { return new int[0]; } return method.invoke(_preparedStatement, args); } private NoBatchInvocationHandler(PreparedStatement preparedStatement) { _preparedStatement = preparedStatement; } private final PreparedStatement _preparedStatement; } }