- Spring中处理事务的Aop增强
- PlatformTransactionManager (事务管理器,也就是事务切面)
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
- TransactionStatus (保存了当前事务的一些信息,比如回滚点,只读事务,最重要的是保存了当前连接)
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
boolean hasSavepoint();
void flush();
}
3.TransactionSynchronizationManager 保存了每个线程的连接
public abstract class TransactionSynchronizationManager {
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active");
public TransactionSynchronizationManager() {
}
- 配置编程式事务
- 创建配置类
配置类需要标注@EnableTransactionManagement,并给容器中注入一个 TransactionManager ,此处注入的是DataSourceTransactionManager 。
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.aop")
public class AnnoConfig {
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setUrl("jdbc:mysql://localhost:3306/account?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai");
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean
public TransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager tm = new DataSourceTransactionManager();
tm.setDataSource(dataSource);
return tm;
}
}
- 创建数据库表
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`username` varchar(255) DEFAULT NULL,
`banlance` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO `account` VALUES ('A', '0');
INSERT INTO `account` VALUES ('B', '200');
- 创建AccountService类
@Service
public class AccountService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void transfer(String from, String to, Integer money) {
String sqlfrom = "update account set banlance = banlance - ? where username = ?";
jdbcTemplate.update(sqlfrom, money, from);
String sqlto = "update account set banlance = banlance + ? where username = ?";
jdbcTemplate.update(sqlto, money, to);
}
public Map<String, Object> getAccount(String username){
String sql = "select * from account where username = ?";
Map<String, Object> stringObjectMap = jdbcTemplate.queryForMap(sql, username);
return stringObjectMap;
}
}
- 测试事务
测试类
public class SpringAopApplicationTests {
private ApplicationContext getContext() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnoConfig.class);
return context;
}
@Test
public void tx() {
AccountService accountService = getContext().getBean(AccountService.class);
accountService.transfer("A", "B", 50);
}
- 首先看一下从容器中拿到的AccountService类型
此时的AccountService就是一个原始类型。因为该类没有方法标注@Transactional注解。给transfer方法标注该注解后再测试
此时被cglib创建了代理对象。
尝试执行transfer方法
此时首先来到TransactionManager的getTransaction方法中,这个方法用于获取一个TransactionStatus,等被代理对象的方法执行完成以后需要将这个TransactionStatus中的connection进行commit,如果出线异常,则需要rollback。
该方法传入了一个TransactionDefinition,事务定义信息,也就是@Transactional注解中可以配置的那些属性。如果只标注了一个@Transactional,那就相当于直接new了一个 DefaultTransactionDefinition。
此时调了DataSourceTransactionManager中的doGetTransaction方法。
该方法返回了一个DataSourceTransactionObject,该txObject中持有一个ConnectionHolder,xxxHolder一看就是持有一个数据库连接。
紧接着又调用了
AbstractPlatformTransactionManager的startTransaction方法
该方法中最关键的是调用了this.doBegin方法
此时进入了子类的DataSourceTransactionManager的doBegin方法。
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;
Connection con = null;
try {
if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = this.obtainDataSource().getConnection();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
txObject.setReadOnly(definition.isReadOnly());
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
this.prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = this.determineTimeout(definition);
if (timeout != -1) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder());
}
} catch (Throwable var7) {
if (txObject.isNewConnectionHolder()) {
DataSourceUtils.releaseConnection(con, this.obtainDataSource());
txObject.setConnectionHolder((ConnectionHolder)null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", var7);
}
}
由于当前线程中还没有创建连接,所以ConnectionHolder中的连接为null。此时事务管理器从数据源中获取了一个连接。
紧接着执行了 我们最熟悉的一行代码
con.setAutoCommit(false)
最后执行了
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder());
}
在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 = (Map)resources.get();
if (map == null) {
map = new HashMap();
resources.set(map);
}
Object oldValue = ((Map)map).put(actualKey, value);
if (oldValue instanceof ResourceHolder && ((ResourceHolder)oldValue).isVoid()) {
oldValue = null;
}
if (oldValue != null) {
throw new IllegalStateException("Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread");
}
}
这个方法非常关键,将新创建出来的连接作为value,将数据源作为key放入了threadlocal中。
当整个方法执行完成以后,执行增强的commit方法,此时进入了DataSourceTransactionManager.doCommit()
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
this.logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
con.commit();
} catch (SQLException var5) {
throw this.translateException("JDBC commit", var5);
}
}
在出现异常以后执行doRollback
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
this.logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
con.rollback();
} catch (SQLException var5) {
throw this.translateException("JDBC rollback", var5);
}
}
那么既然spring能控制事务,说明transfer方法中用的连接肯定是同一个连接,下面深究一下jdbcTemplate的update方法。
发现这个update方法最终调用了execute方法,而execute方法中有一行
Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
那关键就看怎么获取到的这个连接
进入 这个方法
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
return doGetConnection(dataSource);
} catch (SQLException var2) {
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", var2);
} catch (IllegalStateException var3) {
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + var3.getMessage());
}
}
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
if (conHolder == null || !conHolder.hasConnection() && !conHolder.isSynchronizedWithTransaction()) {
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = fetchConnection(dataSource);
if (TransactionSynchronizationManager.isSynchronizationActive()) {
try {
ConnectionHolder holderToUse = conHolder;
if (conHolder == null) {
holderToUse = new ConnectionHolder(con);
} else {
conHolder.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(new DataSourceUtils.ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
} catch (RuntimeException var4) {
releaseConnection(con, dataSource);
throw var4;
}
}
return con;
} else {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();
}
}
发现这个方法又调了doGetConnection,在该方法里有一行关键代码
ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
而TransactionSynchronizationManager.getResource(dataSource);正是从当前线程中获取之前前置增强放入的那个连接。此时是根据数据源作为key获取的,也就是说如果jdbcTemplate和DataSourceTransactionManager注入的不是同一个数据源,此时事务就不生效了。
拿到连接以后,在同一个方法中调用的多次update,就会一直用这一个连接,那么此时如果抛出异常,就可以进行回滚了。
- 若TransactionSynchronizationManager.getResource(dataSource);未获取数据源
则JDBCTemplate就会从自己的数据源中获取一个数据源
private static Connection fetchConnection(DataSource dataSource) throws SQLException {
Connection con = dataSource.getConnection();
if (con == null) {
throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource);
} else {
return con;
}
}
这也从侧面解释了为什么将数据源交给事务管理器后还需要交给JDBCTemplate。如果是JDBCTemplate自己获取数据源的话,默认从连接池中获取的数据源都是自动提交的。所以不会回滚事务。
总结:Spring为一些第三方的基于JDBC的操作数据库的框架提供了一个统一的事务管理抽象(PlatformTransactionManager ),只要实现了该接口 就可以切入方法,进行事务控制,当然,Spring也提供了一些默认的实现,JDBC和Mybatis都适用于DataSourceTransactionManager。
如果某日自己可以封装一个orm框架,那么就可以写一个自己的PlatformTransactionManager 来进行事务控制。
在执行事务方法执行前,Spring解析了@Transactional注解中的各个属性信息,封装成一个TransactionDefinition,调用事务管理器的getTransaction方法,这个方法会放入当前线程的threadlocal中一个connection,并返回TransactionStatus,TransactionStatus中持有ConnectionHolder,可以提交或回滚当前事务。