一.环境搭建
1)引入相关依赖
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.14</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.3.14</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.7</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.3.14</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.3.14</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.23</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.3.14</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.3.14</version></dependency>
2)编写jdbc配置
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useSSL=false
jdbc.username=root
jdbc.password=root
3)全局配置类
@PropertySource("classpath:jdbc.properties")@Configuration@ComponentScan(basePackages={"transaction.service","transaction.proxy","transaction.config"})@Import(JdbcConfig.class)publicclassGlobalConfig{}
二.实现思路
1)事务基本实现步骤
- 开始事务
- 提交事务
- 若出现异常回滚事务
- 关闭连接
伪代码
try{beginTransaction();CRUDcommitTransaction();}catch(Exception e){rollbackTransaction();}finally{closeTransaction();}
要实现上诉功能最容易想到的方法就是每一个需要事务的方法里面都写一遍类似上诉代码,显然这是一种糟糕的设计,代码冗余太大。
2)方案实现
- Proxy动态代理
- AOP
- @Transaction注解
后面两种其实都是通过动态代理来实现的
三.案例
数据库表数据如下,要实现的目标就是刘备曹操孙权向自己手下员工发工资转账,这是一个事务的典型应用场景。
1)编写一个JavaBean类已经相关业务方法
// 省略set、getpublicclassAccount{privateint aId;privateString aName;privatedouble aBalance;privateint cId;}
publicinterfaceAccountService{/**
*
* @param name 用户名
* @return 账号信息
*/AccountgetAccountByName(String name);/**
*
* @param account 账号
* @return 影响行数
*/intupdateAccount(Account account);/**
*
* @param sourceName 转账人
* @param objectName 收账人
* @param cash 转账金额
*/voidtransfer(String sourceName,String objectName,double cash);}
@Service("accountService")publicclassAccountServiceImplimplementsAccountService{@AutowiredJdbcTemplate jdbcTemplate;@OverridepublicAccountgetAccountByName(String name){String querySql="select * from t_account where a_name=?";BeanPropertyRowMapper<Account> beanPropertyRowMapper=newBeanPropertyRowMapper<>(Account.class);return jdbcTemplate.queryForObject(querySql, beanPropertyRowMapper, name);}@OverridepublicintupdateAccount(Account account){String updateSql="update t_account set a_balance=? where a_id=?";return jdbcTemplate.update(updateSql, account.getaBalance(), account.getaId());}@Overridepublicvoidtransfer(String sourceName,String objectName,double cash){// 1.得到转账人的信息AccountA=getAccountByName(sourceName);// 2.得到收账人的信息AccountB=getAccountByName(objectName);// 3.修改转账人的金额A.setaBalance(A.getaBalance()-cash);// 4.修改收账人的金额B.setaBalance(B.getaBalance()+cash);// 5.更新转账人的信息updateAccount(A);// 6.更新收账人的信息updateAccount(B);}}
2)配置数据源
最主要解决的是数据库连接问题需要保证每一个线程使用的是不同的数据库连接,可以使用ThreadLocal来保证每一个线程拥有不同的连接。不过spring已经帮我们做好了。
// 1.初始化事务同步管理器TransactionSynchronizationManager.initSynchronization();// 2.使用spring的数据源工具类获取当前线程的连接returnDataSourceUtils.getConnection(dataSource);
publicclassJdbcConfig{@Value("${jdbc.driver}")privateString driver;@Value("${jdbc.url}")privateString url;@Value("${jdbc.password}")privateString password;@Value("${jdbc.username}")privateString username;@BeanpublicJdbcTemplatejdbcTemplate(DataSource dataSource){returnnewJdbcTemplate(dataSource);}@BeanpublicDataSourcedataSource(){DriverManagerDataSource dataSource=newDriverManagerDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setPassword(password);
dataSource.setUsername(username);return dataSource;}@BeanpublicConnectionconnection(DataSource dataSource){// 1.初始化事务同步管理器TransactionSynchronizationManager.initSynchronization();// 2.使用spring的数据源工具类获取当前线程的连接returnDataSourceUtils.getConnection(dataSource);}}
3)封装事务相关方法
@ComponentpublicclassTransactionManger{@AutowiredprivateConnection connection;publicvoidbeginTransaction(){try{
connection.setAutoCommit(false);}catch(SQLException e){
e.printStackTrace();}}publicvoidcommitTransaction(){try{
connection.commit();}catch(SQLException e){
e.printStackTrace();}}publicvoidrollbackTransaction(){try{
connection.rollback();}catch(SQLException e){
e.printStackTrace();}}publicvoidcloseTransaction(){try{
connection.close();}catch(SQLException e){
e.printStackTrace();}}}
4)编写一个代理类来实现对AccountService的代理增强
@ComponentpublicclassAccountServiceProxy{@Qualifier("accountService")@AutowiredprivateAccountService accountService;@AutowiredprivateTransactionManger transactionManger;@Bean("proxyAccountService")publicAccountServicegetAccountServiceProxy(){ClassLoader classLoader= accountService.getClass().getClassLoader();Class<?>[] interfaces= accountService.getClass().getInterfaces();Object proxyInstance=Proxy.newProxyInstance(classLoader, interfaces,(proxy, method, args)->{Object val=null;try{
transactionManger.beginTransaction();
val= method.invoke(accountService,args);
transactionManger.commitTransaction();}catch(Throwable throwable){
transactionManger.rollbackTransaction();
throwable.printStackTrace();}finally{
transactionManger.closeTransaction();}return val;});return(AccountService)proxyInstance;}}
5)测试类
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes=GlobalConfig.class)publicclassTestTransaction{@Qualifier("proxyAccountService")@AutowiredprivateAccountService accountService;@TestpublicvoidgetAccountByName(){Account account= accountService.getAccountByName("刘备");System.out.println(account);}@TestpublicvoidupdateAccount()throwsException{Account account=newAccount();
account.setaId(1);
account.setaBalance(1100);int result= accountService.updateAccount(account);System.out.println(result);}@TestpublicvoidtestTransfer()throwsException{StringA="刘备";StringB="关羽";double cash=100d;
accountService.transfer(A,B,cash);}}
上面是自己通过代理来实现事务操作,可以通过AOP来改造上面代码
@AutowiredprivateTransactionManger transactionManger;@Pointcut(value="execution(* transaction.service.impl.AccountServiceImpl.transfer(..))")publicvoidpointcut(){}@Before(value="pointcut()")publicvoidbeforeAdvice(){System.out.println("事务开始===>");
transactionManger.beginTransaction();}@AfterReturning(value="pointcut()")publicvoidafterReturning(){System.out.println("提交事务===>");
transactionManger.commitTransaction();}@AfterThrowing(value="pointcut()")publicvoidafterThrowing(){System.out.println("回滚事务===>");
transactionManger.rollbackTransaction();}@After(value="pointcut()")publicvoidafterAdvice(){System.out.println("关闭事务===>");
transactionManger.closeTransaction();}
使用环绕通知:
@Around(value="pointcut()")publicObjecttransaction(ProceedingJoinPoint pjp){try{System.out.println("事务开始===>");
transactionManger.beginTransaction();Object obj= pjp.proceed(pjp.getArgs());System.out.println("提交事务===>");
transactionManger.commitTransaction();return obj;}catch(Throwable throwable){
throwable.printStackTrace();System.out.println("回滚事务===>");
transactionManger.rollbackTransaction();}finally{System.out.println("关闭事务===>");
transactionManger.closeTransaction();}returnnull;}
当然spring已经将所有的事务操作封装到@Transaction注解中了,不过Spring的注解也是通过AOP的环绕通知来实现的。