问题:多个database的多张表需要进行加密操作,必须保证事务,否则会导致部分库的部分数据加密
代码地址:https://gitee.com/edward_liu/migration-data/tree/master
环境JDK1.8:
tk.mybatis
mapper-spring-boot-starter
2.0.2
tk.mybatis
mapper
4.0.4
com.alibaba
druid
1.1.6
mysql
mysql-connector-java
runtime
service的bean以及方法上添加@Transactional,事务的力度比较粗,多个库需要加密因此需要针对每个connection进行事务控制,力度要尽量的小
尝试一
使用spring中jdbcTemplate,每个库对应一个jdbcTemplate,通过jdbcTemplate获取connection进行事务控制达到事务一致的控制
public Object getInfo() throws Exception {
List
实际效果事务不生效,大胆猜测:Connection connection = jdbcTemplate.getDataSource().getConnection();获取的connection与步骤二真正执行时获取的connection并不是同一个。
上代码开启debug模式,开启小心验证环节
步骤二debug后会进入execute这个方法中
使用的是druidDatasource
最终获取到的connection是DruidPooledConnection,从字面上的意思应该是从一个连接池里获取的连接,步骤一和步骤二中获取到的connection应该都是从连接池中获取的连接,因此可以猜测事务不生效的因为是:步骤一获取的connection和步骤二获取connection不是同一个连接,导致无法控制事务,我们进一步继续看源码
直接看返回值,最后的结果是返回新创建的对象,这就更加诡异了,怎么每次创建都会创建一个新对象呢?匪夷所思,我们小心求证,看一下DruidPooledConnection这个实体的构造方法。
了然了,这是从holder中获取connection这个真正的连接,那我们看一下holder的实现过程
for (boolean createDirect = false;;) {
//false不执行
if (createDirect) {
if (createCountUpdater.compareAndSet(this, 0, 1)) {
PhysicalConnectionInfo pyConnInfo = DruidDataSource.this.createPhysicalConnection();
holder = new DruidConnectionHolder(this, pyConnInfo);
holder.lastActiveTimeMillis = System.currentTimeMillis();
createCountUpdater.decrementAndGet(this);
directCreateCountUpdater.incrementAndGet(this);
if (LOG.isDebugEnabled()) {
LOG.debug("conn-direct_create ");
}
boolean discard = false;
lock.lock();
try {
if (activeCount < maxActive) {
activeCount++;
if (activeCount > activePeak) {
activePeak = activeCount;
activePeakTime = System.currentTimeMillis();
}
break;
} else {
discard = true;
}
} finally {
lock.unlock();
}
if (discard) {
JdbcUtils.close(pyConnInfo.getPhysicalConnection());
}
}
}
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException("interrupt", e);
}
try {
if (maxWaitThreadCount > 0
&& notEmptyWaitThreadCount >= maxWaitThreadCount) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
+ lock.getQueueLength());
}
if (onFatalError
&& onFatalErrorMaxActive > 0
&& activeCount >= onFatalErrorMaxActive) {
connectErrorCountUpdater.incrementAndGet(this);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String errorMsg = "onFatalError, activeCount " + activeCount
+ ", onFatalErrorMaxActive " + onFatalErrorMaxActive;
if (lastFatalErrorTimeMillis > 0) {
errorMsg += ", time '" + format.format(new Date(lastFatalErrorTimeMillis)) + "'";
}
if (lastFatalErrorSql != null) {
errorMsg += ", sql \n" + lastFatalErrorSql;
}
throw new SQLException(errorMsg, lastFatalError);
}
connectCount++;
if (createScheduler != null
&& poolingCount == 0
&& activeCount < maxActive
&& createCountUpdater.get(this) == 0
&& createScheduler instanceof ScheduledThreadPoolExecutor) {
ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) createScheduler;
if (executor.getQueue().size() > 0) {
createDirect = true;
continue;
}
}
//获取holder的真正代码 步骤三
if (maxWait > 0) {
holder = pollLast(nanos);
} else {
holder = takeLast();
}
if (holder != null) {
activeCount++;
if (activeCount > activePeak) {
activePeak = activeCount;
activePeakTime = System.currentTimeMillis();
}
}
} catch (InterruptedException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException(e.getMessage(), e);
} catch (SQLException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw e;
} finally {
lock.unlock();
}
break;
}
步骤三是真正的获取holder实例,看一下实现过程:
正真的原因与我们的猜测是一致的,每一次获取connection都是从holder中获取一个新的连接,因此代码中控制事务的connection与真正的执行connection不同,解决事务不生效思路:获取同一个connection
大胆尝试一:把连接池设为1不就可以了吗?
数据库连接池都设为1,代码调试中会报错,原因:池子里最大只有一个连接conn,而它又未被释放,导致jdbcTemplate内部再去从池子里获取con时,一直在等待已有连接conn的释放,一直等不到释放,所以等待了max-wait的时间,最后报错
大胆尝试一:获取真正的connection
JdbcTemplate是如何控制事务的呢?答案就是TransactionTemplate。
public Object getInfoTransaction(){
List migrationDatabaseInfos = mapper.selectAll();
for (MigrationDatabaseInfo databaseInfo : migrationDatabaseInfos) {
DataSource datasource = DbConnect.getDatasource(databaseInfo);
TransactionTemplate transactionTemplate = DbConnect.getTransactionTemplate(datasource);
JdbcTemplate jdbcTemplate = DbConnect.getJdbcTemplate(datasource);
MigrationTableInfo tableInfo = new MigrationTableInfo();
tableInfo.setDataCode(databaseInfo.getDataCode());
List migrationTableInfos = migrationTableInfoService.selectByCondition(tableInfo);
transactionTemplate.execute( (TransactionStatus transactionStatus) ->{
Object savepoint = transactionStatus.createSavepoint();
try {
for (MigrationTableInfo migrationTableInfo : migrationTableInfos) {
String sql = "select id ," + migrationTableInfo.getTableColumn() + " from " + migrationTableInfo.getTableName();
log.info("执行的sql是:{}", sql);
List
欢迎关注作者公众号交流
回复 8888可以领取面试资料