@Configuration的实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@EnableTransactionManagement
//@Configuration
@Component("com.example.config")
public class JdbcConfig {
@Bean
public JdbcTemplate jdbcTemplate(){
return new JdbcTemplate(dataSource());
}

@Bean
public PlatformTransactionManager transactionManager(){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource());
return transactionManager;
}

@Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("xxx");
dataSource.setUrl("xxx");
dataSource.setUsername("xxx");
dataSource.setPassword("xxx");
return dataSource;
}
}

在上面的场景中,配置了JdbcTemplate的配置项,但没有@Configuration,也会出现一个事务失效问题:

1
2
3
4
5
@Transactional
public void b(){
jdbcTemplate.queryForList("update emp set name='小明' where emp_id=2");
throw new RuntimeException();
}

jdbcTemplate.queryForList执行完直接提交,后续抛出异常也无法回滚数据。

​ 再重温一遍事务的启动机制:

  1. 事务管理器新建一个数据库连接conn,不用jdbcTemplate建连接,因为其默认autocommit=true,执行完会直接提交
  2. 设置conn.autocommit = false;
  3. jdbc通过ThreadLocal<Map<DataSource, conn>>获取conn执行sql语句
  4. 如果一切正常,conn.commit(),否则conn.rollback()

​ 事务启动时事务管理器(PlatformTransactionManager)会新建一个数据库连接,将其存入ThreadLocal<Map<DataSource, conn>>中,然后取消事务自动提交,当jdbc需要时直接从ThreadLocal中获取。

​ 此时的问题主要是,因为没有配置@Configuration,当代理对象执行到b()方法,然后执行jdbc数据库方法,此时jdbc获取conn的原理实际上是new JdbcTemplate(dataSource()),而事务管理器中transactionManager.setDataSource(dataSource()),相当于是两个不同的dataSource对象,而jdbc的dataSource对象默认autocommit=true,当执行完sql语句,直接提交,导致后续无法回滚

1
2
3
4
5
6
7
8
9
10
11
@Bean
public JdbcTemplate jdbcTemplate(){
return new JdbcTemplate(dataSource());
}

@Bean
public PlatformTransactionManager transactionManager(){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource());
return transactionManager;
}

​ 那么为什么标注了@Configuration之后,就能解决这个问题呢?其原理到底是什么?

​ @Configuration的原理也是基于动态代理的(AOP、@Lazy都是基于动态代理),底层原理大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Class JdcbConfigProxy extends JdbcConfig{
JdbcConfig target;

public JdbcTemplate jdbcTemplate(){
.
return super.jdbcTemplate();
}

public DataSource dataSource(){
//代理逻辑:先去ioc容器中找dataSource,若有直接return
if(...){
...
return ioc.getBean();
}
else {
DataSource dataSource = super.dataSource();
ioc.putBean(dataSource);
return dataSource;
}
}
}

基于动态代理,不论是jdbc还是transactionManager,其获取dataSource最终都要先去ioc容器中找,若找到

就直接返回,找不到在调用dataSource()方法创建。