关于spring事务管理这一块,大多数人都知道在做spring整合的时候配置一下就ok了,但是并不清楚其底层原理;本章就介绍一下aop的面向切面的编程思想,如何应用到事务当中去。
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
自己手动控制事务,就叫做编程式事务控制。
Jdbc代码:
Conn.setAutoCommite(false); // 设置手动控制事务
Hibernate代码:
Session.beginTransaction(); // 开启一个事务
【细粒度的事务控制: 可以对指定的方法、指定的方法的某几行添加事务控制】
(比较灵活,但开发起来比较繁琐: 每次都要开启、提交、回滚.)
Spring提供了对事务的管理, 这个就叫声明式事务管理。
Spring提供了对事务控制的实现。用户如果想用Spring的声明式事务管理,只需要在配置文件中配置即可; 不想使用时直接移除配置。这个实现了对事务控制的最大程度的解耦。
Spring声明式事务管理,核心实现就是基于Aop。
【粗粒度的事务控制: 只能给整个方法应用事务,不可以对方法的某几行应用事务。】
(因为aop拦截的是方法。)
Spring声明式事务管理器类:
Jdbc技术:DataSourceTransactionManager
Hibernate技术:HibernateTransactionManager
<properties> <hibernate.version>5.2.12.Final</hibernate.version> <mysql.version>5.1.44</mysql.version> <spring.version>5.0.1.RELEASE</spring.version> <struts2.version>2.5.13</struts2.version> <slf4j.version>1.7.7</slf4j.version> <log4j2.version>2.9.1</log4j2.version> <disruptor.version>3.2.0</disruptor.version> <junit.version>4.12</junit.version> <javax.servlet.version>4.0.0</javax.servlet.version> <jstl.version>1.2</jstl.version> <standard.version>1.1.2</standard.version> <tomcat-jsp-api.version>8.0.47</tomcat-jsp-api.version> </properties> <dependencies> <!-- 2、导入spring依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <!-- 5、other --> <!-- 5.1、junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <!-- 5.2、servlet --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${javax.servlet.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-c3p0</artifactId> <version>${hibernate.version}</version> </dependency> </dependencies>
package com.javaxl.spring.transaction.entity; public class Account { private Integer id; private String name; private Integer money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getMoney() { return money; } public void setMoney(Integer money) { this.money = money; } @Override public String toString() { return "Account [id=" + id + ", name=" + name + ", money=" + money + "]"; } public Account(Integer id, String name, Integer money) { super(); this.id = id; this.name = name; this.money = money; } public Account() { super(); } }
package com.javaxl.spring.transaction.dao; import java.sql.ResultSet; import java.sql.SQLException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; import com.javaxl.spring.transaction.entity.Account; @Repository public class UserDao { @Autowired private JdbcTemplate jdbcTemplate; public void add(String name, Integer money) { String sql = "INSERT INTO t_spring_account(name, money) VALUES(?,?);"; int updateResult = jdbcTemplate.update(sql, name, money); System.out.println("updateResult:" + updateResult); } public void update(Integer money, Integer id) { String sql = "update t_spring_account set money = ? where id = ?;"; int updateResult = jdbcTemplate.update(sql, money, id); System.out.println("updateResult:" + updateResult); } public Account get(Integer id) { String sql = "select * from t_spring_account where id = ?"; return jdbcTemplate.queryForObject(sql, new RowMapper<Account>() { @Override public Account mapRow(ResultSet resultSet, int arg1) throws SQLException { String name = resultSet.getString("name"); Integer money = resultSet.getInt("money"); Integer id = resultSet.getInt("id"); return new Account(id, name, money); } }, id); } }
package com.javaxl.spring.transaction.service; import com.javaxl.spring.transaction.entity.Account; public interface UserService { /** * 开户 * @param account */ public void add(Account account); /** * a账户转账b账户 * @param a * @param b */ public void transfer(Account a,Account b,Integer money); }
package com.javaxl.spring.transaction.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.TransactionAspectSupport; import com.javaxl.spring.transaction.dao.UserDao; import com.javaxl.spring.transaction.entity.Account; import com.javaxl.spring.transaction.service.UserService; import com.javaxl.spring.transaction.util.TransactionUtils; @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Autowired private TransactionUtils transactionUtils; // spring 事务封装呢? aop技术 // public void add() { // TransactionStatus transactionStatus = null; // try { // // 开启事务 // transactionStatus = transactionUtils.begin(); // userDao.add("test001", 20); // System.out.println("开始报错啦!@!!"); // // int i = 1 / 0; // System.out.println("################"); // userDao.add("test002", 21); // // 提交事务 // if (transactionStatus != null) // transactionUtils.commit(transactionStatus); // } catch (Exception e) { // e.getMessage(); // // 回滚事务 // if (transactionStatus != null) // transactionUtils.rollback(transactionStatus); // } // } public void add(Account account) { userDao.add(account.getName(), account.getMoney()); } @Override public void transfer(Account a, Account b, Integer money) { // try { Account aa = userDao.get(a.getId()); Account bb = userDao.get(b.getId()); userDao.update(aa.getMoney() - money, aa.getId()); // int i = 1 / 0; System.out.println("################"); userDao.update(bb.getMoney() + money, bb.getId()); // } catch (Exception e) { // e.printStackTrace(); /* * 在这里,如果将代码用try * catch包裹起来,那么异常会被捕捉,那么就不会被切面类com.javaxl.spring.transaction.aop.AopTransaction中 * 的异常通知所处理,如果硬是要用try catch,那么必须使用手动回滚事务 */ // 这是手动回滚事务 // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // } } }
package com.javaxl.spring.transaction.web; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import com.javaxl.spring.transaction.entity.Account; import com.javaxl.spring.transaction.service.UserService; @Controller public class UserAction { @Autowired private UserService userService; public void transfer(Account a, Account b, Integer money) { userService.transfer(a, b, money); } public void add(Account account) { userService.add(account); } }
package com.javaxl.spring.transaction.util; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.stereotype.Component; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; @Component public class TransactionUtils { @Autowired private DataSourceTransactionManager dataSourceTransactionManager; // 开启事务 public TransactionStatus begin() { TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute()); return transaction; } // 提交事务 public void commit(TransactionStatus transactionStatus) { dataSourceTransactionManager.commit(transactionStatus); } // 回滚事务 public void rollback(TransactionStatus transactionStatus) { dataSourceTransactionManager.rollback(transactionStatus); } }
package com.javaxl.spring.transaction.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; 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 org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.TransactionAspectSupport; import com.javaxl.spring.transaction.util.TransactionUtils; /** * 切面类 基于手手动事务封装 * @author Administrator * */ @Component @Aspect public class AopTransaction { @Autowired private TransactionUtils transactionUtils; /** * TransactionUtils 不要实现为单例子: 如果为单例子的话可能会发生线程安全问题 * 异常通知 */ @AfterThrowing("execution(* *..*Service.*(..))") // @AfterThrowing("execution(* *..*Dao.*(..))") public void afterThrowing() { System.out.println("回滚事务"); // 获取当前事务 直接回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } /** * 环绕通知 在方法之前和之后处理事情 * @param proceedingJoinPoint * @throws Throwable */ @Around("execution(* *..*Service.*(..))") // @Around("execution(* *..*Dao.*(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 调用方法之前执行 System.out.println("开启事务"); TransactionStatus transactionStatus = transactionUtils.begin(); proceedingJoinPoint.proceed();// 代理调用方法 注意点: 如果调用方法抛出溢出不会执行后面代码 // 调用方法之后执行 System.out.println("提交事务"); transactionUtils.commit(transactionStatus); } }
package com.javaxl.spring.transaction.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; // 切面类 @Component @Aspect public class AopLog { // aop 编程里面有几个通知: 前置通知 后置通知 运行通知 异常通知 环绕通知 @Before("execution(* *..*Service.*(..))") public void before() { System.out.println("AopLog 前置通知 在方法之前执行..."); } // 后置通知 在方法运行后执行 @After("execution(* *..*Service.*(..))") public void after() { System.out.println("AopLog 前置通知 在方法之后执行..."); } // 运行通知 @AfterReturning("execution(* *..*Service.*(..))") public void returning() { System.out.println("AopLog 运行通知"); } // 异常通知 @AfterThrowing("execution(* *..*Service.*(..))") public void afterThrowing() { System.out.println("AopLog 异常通知"); } // 环绕通知 在方法之前和之后处理事情 @Around("execution(* *..*Service.*(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 调用方法之前执行 System.out.println("AopLog 环绕通知 调用方法之前执行"); proceedingJoinPoint.proceed();// 代理调用方法 注意点: 如果调用方法抛出溢出不会执行后面代码 // 调用方法之后执行 System.out.println("AopLog 环绕通知 调用方法之后执行"); } }
<context:component-scan base-package="com.javaxl"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 开启事物注解 --> <!-- 1. 数据源对象: C3P0连接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis_ssm"></property> <property name="user" value="mybatis_ssm"></property> <property name="password" value="xiaoli"></property> </bean> <!-- 2. JdbcTemplate工具类实例 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 3.配置事务 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
package com.javaxl.spring.transaction.test; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.javaxl.spring.transaction.entity.Account; import com.javaxl.spring.transaction.web.UserAction; public class Test001 { public static void main(String[] args) { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml"); UserAction userAction = (UserAction) applicationContext.getBean("userAction"); // 添加两个账号 // userAction.add(new Account(null,"zs",1000)); // userAction.add(new Account(null,"ls",1000)); // zs向ls转账300 Account a = new Account(); a.setId(6); Account b = new Account(); b.setId(7); userAction.transfer(a, b, 300); } }
注意:在做以下几点情景测试的时候,我都会将数据回退到初始数据版本;
现象:
zs账户少了300,ls账户并没有增加300
原因:
因为异常被try catch所包裹起来了,所以并不会执行异常通知;
为了解决事务一致性的问题,有两种方式
1、手动回滚事务
2、不要在切入点上扑捉异常
以上截图是注销掉try catch代码得来的
切入点定义所带来的差异性
一个转账的操作被分为N个事务,这就违背了事务的基本特性(原子性,一致性)
日志切面类接入进来
over.......
备案号:湘ICP备19000029号
Copyright © 2018-2019 javaxl晓码阁 版权所有