spring Affairs is based on the same data connection to achieve, realize this is the key to spring the transaction, the transaction will be the key point of spring is in the transaction regardless of db operations performed several times, always using the same database connection. By looking at the source code, we can see the spring following the transaction realization of ideas
This is one of the key points is how to ensure the commit, rollback, acquired in a transaction connected to the same database as well as to agents by aop database connection. code show as below
Build your own transaction manager, using threadlocal to ensure that acquired a thread within the same database connection
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; } }
Custom annotation
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 { }
Custom jdbcTemplate simplified operation 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 section, add a method to our custom annotations enhanced, open affairs
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; } }
Test and test-class 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); } }
For convenience, a built spingboot project, depending on the configuration and the following
<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