spring事务是基于同一个数据连接来实现的,认识到这一点是spring事务的关键,spring事务的关键点便在于在事务中不管执行几次db操作,始终使用的是同一个数据库连接。通过查看源码,我们可以看到spring事务实现思路如下
这其中的关键点就在于如何保证在事务内获取的数据库连接为同一个以及通过aop来代理数据库连接的提交、回滚。代码如下
构建自己的事务管理器,使用threadlocal来保证一个线程内获取到的数据库连接为同一个
package com.jlwj.custom; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; /** * Created by Administrator on 2019/8/31. */ @Component public class MyTransactionManager { private ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); @Autowired @Qualifier("dataSource") private DataSource dataSource; public Connection getConnection(){ Connection connection = null; if(threadLocal.get()!=null){ connection = threadLocal.get(); }else{ try { connection = dataSource.getConnection(); threadLocal.set(connection); } catch (SQLException e) { e.printStackTrace(); } } return connection; } }
自定义注解
package com.jlwj.custom; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Created by Administrator on 2019/8/31. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyTransactionAnnotation { }
自定义jdbcTemplate简化db操作
package com.jlwj.custom; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; /** * Created by Administrator on 2019/8/31. */ @Component public class MyJdbcTemplate { @Autowired private MyTransactionManager transactionManager; public void execute(String sql,@Nullable Object... args) throws SQLException { Connection connection = transactionManager.getConnection(); PreparedStatement preparedStatement = connection.prepareStatement(sql); for (int i = 0; i < args.length; i++) { if(args[i] instanceof Integer){ preparedStatement.setInt(i+1, (Integer) args[i]); }else if(args[i] instanceof String){ preparedStatement.setString(i+1, (String) args[i]); } } preparedStatement.execute(); } }
aop切面,对添加了我们自定义注解的方法进行增强,开启事务
package com.jlwj.custom; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.sql.Connection; import java.sql.SQLException; /** * Created by Administrator on 2019/8/31. */ @Aspect @Component public class Aop { @Autowired private MyTransactionManager myTransactionManager; @Around("@annotation(com.jlwj.custom.MyTransactionAnnotation)") public Object doTransaction(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { Object o = null; Connection connection = myTransactionManager.getConnection(); try { connection.setAutoCommit(false); o = proceedingJoinPoint.proceed(); connection.commit(); } catch (SQLException e) { e.printStackTrace(); connection.rollback(); }finally { connection.close(); } return o; } }
测试service及测试类
package com.jlwj.service; import com.jlwj.custom.MyJdbcTemplate; import com.jlwj.custom.MyTransactionAnnotation; import com.jlwj.custom.MyTransactionManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; /** * Created by Administrator on 2019/8/31. */ @Service public class TransactionTestService2 { @Autowired private MyJdbcTemplate myJdbcTemplate; @Autowired private MyTransactionManager transactionManager; @MyTransactionAnnotation public void addUser(String userName,Integer userId){ String sql1 = "insert into t_user(user_id,user_name,password,phone) values(?,?,?,?)"; String sql2 = "insert into t_log(content)values (?)"; try { myJdbcTemplate.execute(sql1,userId,userName,"1231456","123213213123"); myJdbcTemplate.execute(sql2,userName); // int a = 1/0; } catch (SQLException e) { e.printStackTrace(); } } public void addUser2(String userName,Integer userId){ String sql1 = "insert into t_user(user_id,user_name,password,phone) values(?,?,?,?)"; String sql2 = "insert into t_log(content)values (?)"; try { myJdbcTemplate.execute(sql1,userId,userName,"1231456","123213213123"); myJdbcTemplate.execute(sql2,userName); int a = 1/0; } catch (SQLException e) { e.printStackTrace(); } } }
package com.jlwj; import com.jlwj.service.TransactionTestService; import com.jlwj.service.TransactionTestService2; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class CustomTransactionApplicationTests { @Autowired private TransactionTestService transactionTestService; @Autowired private TransactionTestService2 transactionTestService2; @Test public void contextLoads() { transactionTestService.addUser("wangwu",3); } @Test public void contextLoad2() { transactionTestService2.addUser("qwe",7); } @Test public void contextLoad3() { transactionTestService2.addUser2("123",8); } }
为了方便,构建了一个spingboot项目,依赖和配置如下
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
spring: datasource: druid: url: jdbc:mysql://127.0.0.1:3306/test02?characterEncoding=utf-8&serverTimezone=UTC username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver initial-size: 1 max-active: 20 max-wait: 6000 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2000 min-idle: 1 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: select 1 test-while-idle: true test-on-borrow: false test-on-return: false