我有一系列的JDBC Connection
,PreparedStatement
和Statement
代理。
ConnectionProxy:
public class HikariConnectionProxy implements InvocationHandler {
private final HikariDataSource dataSource;
private final Connection delegate;
private boolean committed;
public static Connection newInstance(HikariDataSource dataSource) throws SQLException {
final Connection connection = dataSource.getConnection();
return (Connection) Proxy.newProxyInstance(
connection.getClass().getClassLoader(),
connection.getClass().getInterfaces(),
new HikariConnectionProxy(dataSource, connection)
);
}
public static Connection newInstance(HikariDataSource dataSource, String username, String password) throws SQLException {
final Connection connection = dataSource.getConnection(username, password);
return (Connection) Proxy.newProxyInstance(
connection.getClass().getClassLoader(),
connection.getClass().getInterfaces(),
new HikariConnectionProxy(dataSource, connection)
);
}
private HikariConnectionProxy(HikariDataSource dataSource, Connection connection) throws SQLException {
this.dataSource = dataSource;
delegate = connection;
checkAutoCommit();
}
private void checkAutoCommit() throws SQLException {
if (!delegate.getAutoCommit()) {
delegate.setAutoCommit(true);
}
}
private void commit() throws SQLException {
delegate.commit();
committed = true;
}
private void close() throws SQLException {
// some code here
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Void.TYPE == method.getReturnType()) {
final String methodName = method.getName().toLowerCase();
if ("close".equals(methodName)) {
this.close();
return null;
} else if ("commit".equals(methodName)) {
this.commit();
return null;
}
} else if (PreparedStatement.class == method.getReturnType()) {
return ServiceBusPreparedStatementProxy.newInstance((PreparedStatement) method.invoke(delegate, args));
} else if (Statement.class == method.getReturnType()) {
return ServiceBusStatementProxy.newInstance((Statement) method.invoke(delegate, args));
}
return method.invoke(delegate, args);
}
}
StatementProxy:
public class ServiceBusStatementProxy implements InvocationHandler {
final boolean toCheckUnsigned;
final Statement delegate;
public static Statement newInstance(Statement statement) throws SQLException {
return (Statement) Proxy.newProxyInstance(
statement.getClass().getClassLoader(),
statement.getClass().getInterfaces(),
new ServiceBusStatementProxy(statement, statement.getConnection().getMetaData().getDatabaseProductName())
);
}
ServiceBusStatementProxy(Statement statement, String dbName) {
String dbNameLowerCase = dbName.toLowerCase();
toCheckUnsigned = dbNameLowerCase.contains("oracle") || dbNameLowerCase.contains("mysql") || dbNameLowerCase.contains("mariadb");
delegate = statement;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return toCheckUnsigned && ResultSet.class == method.getReturnType()
? ServiceBusResultSetProxy.newInstance((ResultSet) method.invoke(delegate, args))
: method.invoke(delegate, args);
}
}
PreparedStatementProxy:
public class ServiceBusPreparedStatementProxy extends ServiceBusStatementProxy implements InvocationHandler {
private static final Predicate<Runnable> isValidArithmetic = runnable -> {
try {
runnable.run();
return true;
} catch (ArithmeticException e) {
return false;
}
};
public static PreparedStatement newInstance(PreparedStatement preparedStatement) throws SQLException {
return (PreparedStatement) Proxy.newProxyInstance(
preparedStatement.getClass().getClassLoader(),
preparedStatement.getClass().getInterfaces(),
new ServiceBusPreparedStatementProxy(preparedStatement, preparedStatement.getConnection().getMetaData().getDatabaseProductName())
);
}
private ServiceBusPreparedStatementProxy(Statement statement, String dbName) {
super(statement, dbName);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (toCheckUnsigned && Void.TYPE == method.getReturnType()
&& args != null && args.length >= 3 && "setObject".equalsIgnoreCase(method.getName()) && args[1] != null) {
Class<?> valueClass = args[1].getClass();
if (BigInteger.class == valueClass) {
BigInteger bigInt = (BigInteger) args[1];
if (!isValidArithmetic.test(bigInt::longValueExact)) {
((PreparedStatement) delegate).setString((Integer) args[0], bigInt.toString());
return null;
}
}
}
return super.invoke(proxy, method, args);
}
}
当我将此连接与带有@Transactional
注释的方法一起使用时,该事务将无法正常工作。当我删除此代理后,使用相同的@Transactional
方法一切正常。
对此有什么解释以及如何解决?
与此数据源一起使用:
@Service
public class BaseHikariDataSourceFactory implements DataSourceFactory {
@Value("${hikari.leak.detection.threshold:0}")
private int leakDetectionThreshold;
@Value("${hikari.minimum.idle:0}")
private int minimumIdle;
@Value("${hikari.idle.timeout:60000}")
private int idleTimeout;
@Value("${hikari.maximum.pool.size:10}")
private int maximumPoolSize;
@Override
public DataSource createHikariDataSource(BaseHikariDataSourceParams params) {
HikariConfig config = new HikariConfig();
ExternalSystemParams externalSystemParams = params.getExternalSystemParams();
config.setPoolName(params.getPoolName());
config.setJdbcUrl(params.getJdbcUrl());
config.setDriverClassName(externalSystemParams.getDriverClassName());
config.setUsername(externalSystemParams.getUsername());
config.setPassword(externalSystemParams.getPassword());
config.setMaximumPoolSize(Optional.ofNullable(externalSystemParams.getMaximumPoolSize()).orElse(maximumPoolSize));
config.setIdleTimeout(Optional.ofNullable(externalSystemParams.getIdleTimeout()).orElse(idleTimeout));
config.setMinimumIdle(Optional.ofNullable(externalSystemParams.getMinimumIdle()).orElse(minimumIdle));
config.setLeakDetectionThreshold(leakDetectionThreshold);
config.setRegisterMbeans(true);
if (externalSystemParams.getConnectionTestQuery() != null) {
config.setConnectionTestQuery(externalSystemParams.getConnectionTestQuery());
}
if (externalSystemParams.getValidationTimeout() != null) {
config.setValidationTimeout(externalSystemParams.getValidationTimeout());
}
return new HikariDataSourceProxyConnection(config);
}
@Override
public DataSource createHikariDataSource(Share share, boolean isWorker) {
HikariConfig config = new HikariConfig();
config.setPoolName("Hikari CP " + (isWorker ? share.getAlias() + WORKERS_DS_POSTFIX : share.getAlias()));
config.setDriverClassName(share.getDriver());
config.setJdbcUrl(share.getUrl());
config.setUsername(share.getLogin());
config.setPassword(share.getPassword());
config.setMaximumPoolSize(isWorker ? share.getMaxPoolSizeWorkers() : share.getMaxPoolSize());
config.addDataSourceProperty("useCompression", share.isUseCompression());
config.setLeakDetectionThreshold(leakDetectionThreshold);
config.setRegisterMbeans(true);
return new HikariDataSourceProxyConnection(config);
}
private static class HikariDataSourceProxyConnection extends HikariDataSource {
private final HikariDataSource delegate;
HikariDataSourceProxyConnection(HikariConfig configuration) {
delegate = new HikariDataSource(configuration);
}
@Override
public Connection getConnection() throws SQLException {
return HikariConnectionProxy.newInstance(delegate);
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return HikariConnectionProxy.newInstance(delegate, username, password);
}
}
}