接着上面第五章关于事务传播属性的源码分析:
一、创建事务状态对象、开启事务
第一:创建事务状态对象,事务状态就是记录事务流转过程中状态数据的,有一个数据非常重要,直接决定了提交,回滚和恢复绑定操作,就是newTransaction属性,这个属性要牢记。如果为true就代表当前事务允许单独提交和回滚,一般是第一次创建事务或者事务传播属性为PROPAGATION_REQUIRES_NEW的时候。如果为false则当前事务不能单独提交和回滚。
第二:doBegin该方法则是开启事务的核心方法,点进去看看:
1、从数据源连接池中获取连接
org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
//如果没有数据库连接
if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
//从连接池里面获取连接
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
//把连接包装成ConnectionHolder,然后设置到事务对象中
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
这块从数据源获取到连接对象,有一个数据源要提一下,就是动态切换数据源的一个抽象类:
AbstractRoutingDataSource,如果有多数据源的需求,可以自己定义数据源对象然后往自定义对象中设置目标数据源和默认数据源,通过切面控制动态切换逻辑:
1.1、动态数据源代码示例:
代码详见com.chj.datasource.DynamicDataSource,从ThreadLocal中获取连接字符串:
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
String ds = DynamicDataSourceHolder.getDs();
System.out.println("=========选择的数据源:" + ds);
return ds;
}
}
注意:ThreadLocal类,用来保存当前线程和数据源字符串的关系
public class DynamicDataSourceHolder {
private static ThreadLocal<String> local = new ThreadLocal<String>();
public static String getDs() {
return local.get();
}
public static ThreadLocal getLocal() {
return local;
}
}
1.2、切面类代码示例:
通过获取调用方法上面的注解来获取选择的数据源字符串,然后把这个字符串设置到ThreadLocal中跟当前线程绑定:com.chj.aop.aspectj.AspectDs
@Component
@Aspect
@Order(-1)
public class AspectDs {
@Before(value = "@annotation(targetSource)",argNames = "joinPoint,targetSource")
public void xx(JoinPoint joinPoint, TargetSource targetSource) {
System.out.println("========AspectDs.xx");
String value = targetSource.value();
if(value != null && !"".equals(value)) {
DynamicDataSourceHolder.getLocal().set(value);
} else {
DynamicDataSourceHolder.getLocal().set("ds1");
}
}
}
1.3、动态数据源初始化:
com.chj.datasource.DataSourceConfiguration
@Bean
public DataSource dynamicDataSource() {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
try {
comboPooledDataSource.setDriverClass(driverClass);
comboPooledDataSource.setJdbcUrl(jdbcUrl);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
comboPooledDataSource.setMinPoolSize(10);
comboPooledDataSource.setMaxPoolSize(100);
comboPooledDataSource.setMaxIdleTime(1800);
comboPooledDataSource.setAcquireIncrement(3);
comboPooledDataSource.setMaxStatements(1000);
comboPooledDataSource.setInitialPoolSize(10);
comboPooledDataSource.setIdleConnectionTestPeriod(60);
comboPooledDataSource.setAcquireRetryAttempts(30);
comboPooledDataSource.setBreakAfterAcquireFailure(false);
comboPooledDataSource.setTestConnectionOnCheckout(false);
comboPooledDataSource.setAcquireRetryDelay(100);
} catch (PropertyVetoException e) {
e.printStackTrace();
}
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("ds1",comboPooledDataSource);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(comboPooledDataSource);
return dynamicDataSource;
}
这里建立了字符串和数据源对象的映射关系,然后把映射关系保存到了动态数据源对象中,动态数据源对象通过钩子方法determineCurrentLookupKey就可以从ThreadLocal中获取到字符串,然后根据字符串从映射关系中找到选择的数据源对象。
2、设置只读事务
从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见。设置只读事务就是告诉数据库,我这个事务内没有新增、修改、删除操作只有查询操作,不需要数据库锁等操作,减少数据库压力。
org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
prepareTransactionalConnection(con, definition);
protected void prepareTransactionalConnection(Connection con, TransactionDefinition definition)
throws SQLException {
if (isEnforceReadOnly() && definition.isReadOnly()) {
Statement stmt = con.createStatement();
try {
stmt.executeUpdate("SET TRANSACTION READ ONLY");
}finally {
stmt.close();
}
}
}
3、把自动提交关闭
org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
//关闭连接的自动提交,其实这步就是开启了事务
con.setAutoCommit(false);
}
4、建立ThreadLocal的绑定关系
这个绑定关系就是前面提到的,从绑定关系中可以拿到map,map中建立的是数据源对象和连接对象的映射。
// Bind the connection holder to the thread.
if (txObject.isNewConnectionHolder()) {
//如果是新创建的事务,则建立当前线程和数据库连接的关系
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
org.springframework.transaction.support.TransactionSynchronizationManager#bindResource
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
Map<Object, Object> map = resources.get();
// set ThreadLocal Map if none found
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
Object oldValue = map.put(actualKey, value);
// Transparently suppress a ResourceHolder that was marked as void...
if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
oldValue = null;
}