在⼀个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这 多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全。
多条DML要么同时成功,要么同时失败,这叫做事务。
第⼀步:开启事务 (start transaction)
第⼆步:执⾏核⼼业务代码
第三步:提交事务(如果核⼼业务处理过程中没有出现异常)(commit transaction)
第四步:回滚事务(如果核⼼业务处理过程中出现异常)(rollback transaction)
A 原⼦性:事务是最⼩的⼯作单元,不可再分。
C ⼀致性:事务要求要么同时成功,要么同时失败。事务前和事务后的总量不变。
I 隔离性:事务和事务之间因为有隔离性,才可以保证互不⼲扰。
D 持久性:持久性是事务结束的标志
Spring实现事务的两种⽅式
第一种:编程式事务
事务功能的相关操作全部通过自己编写代码来实现:
第二种:声明式事务
既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出 来,进行相关的封装。 封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。有基于注解⽅式 和基于XML配置⽅式
Spring对事务的管理底层实现⽅式是基于AOP实现的。采⽤AOP的⽅式进⾏了封装。所以Spring专⻔针 对事务开发了⼀套API,API的核⼼接⼝如下:
如果要在Spring6中使⽤JdbcTemplate,就要使⽤DataSourceTransactionManager来管理事务。 (Spring内置写好了,可以直接⽤。)
事务功能的相关操作全部通过自己编写代码来实现:
- Connection conn = ...;
- try {
- // 开启事务:关闭事务的自动提交
- conn.setAutoCommit(false);
- // 核心操作
- // 提交事务
- conn.commit();
- }catch(Exception e){
- // 回滚事务
- conn.rollBack();
- }finally{
- // 释放数据库连接
- conn.close();
- }
编程式的实现方式存在缺陷:
细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复 用。
依赖
- <dependency>
- <groupId>org.springframeworkgroupId>
- <artifactId>spring-contextartifactId>
- <version>5.3.1version>
- dependency>
- <dependency>
- <groupId>org.springframeworkgroupId>
- <artifactId>spring-ormartifactId>
- <version>5.3.1version>
- dependency>
⼀定要集成Log4j2⽇志框架,在⽇志信息中可以看到更加详细的信息。
jdbc配置文件
- jdbc.user=root
- jdbc.password=root
- jdbc.url=jdbc:mysql://localhost:3306/ssm
- jdbc.driver=com.mysql.cj.jdbc.Driver
spring配置文件
- <context:component-scan base-package="com.demo.spring.tx.annotation">
- context:component-scan>
-
- <context:property-placeholder location="classpath:jdbc.properties" />
-
- <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
- <property name="url" value="${jdbc.url}"/>
- <property name="driverClassName" value="${jdbc.driver}"/>
- <property name="username" value="${jdbc.username}"/>
- <property name="password" value="${jdbc.password}"/>
- bean>
-
- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="druidDataSource"/>
- bean>
-
- <bean id="transactionManager"
- class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource">property>
- bean>
-
- <tx:annotation-driven transaction-manager="transactionManager" />
添加注解
因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层添加注解@Transactional
@Transactional标识在方法上,只会影响该方法
@Transactional标识的类上,会影响类中所有的方法
接口BookDao:
- public interface BookDao {
- Integer getPriceByBookId(Integer bookId);
- void updateStock(Integer bookId);
- void updateBalance(Integer userId, Integer price);
- }
实现类BookDaoImpl:
- @Repository
- public class BookDaoImpl implements BookDao {
- @Autowired
- private JdbcTemplate jdbcTemplate;
- @Override
- public Integer getPriceByBookId(Integer bookId) {
- String sql = "select price from t_book where book_id = ?";
- return jdbcTemplate.queryForObject(sql, Integer.class, bookId);
- }
- @Override
- public void updateStock(Integer bookId) {
- String sql = "update t_book set stock = stock - 1 where book_id = ?";
- jdbcTemplate.update(sql, bookId);
- }
- @Override
- public void updateBalance(Integer userId, Integer price) {
- String sql = "update t_user set balance = balance - ? where user_id =
- ?";
- jdbcTemplate.update(sql, price, userId);
- }
- }
接口BookService:
- public interface BookService {
- void buyBook(Integer bookId, Integer userId);
- }
实现类BookServiceImpl:
- @Service
- public class BookServiceImpl implements BookService {
- @Autowired
- private BookDao bookDao;
-
- @Transactional
- @Override
- public void buyBook(Integer bookId, Integer userId) {
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- }
- }
事务属性包括哪些
事 务 中 的 重 点 属 性:
什么是事务的传播⾏为? 在service类中有a()⽅法和b()⽅法,a()⽅法上有事务,b()⽅法上也有事务,当a()⽅法执⾏过程中调⽤了 b()⽅法,事务是如何传递的?合并到⼀个事务⾥?还是开启⼀个新的事务?这就是事务传播⾏为。
事务传播⾏为在spring框架中被定义为枚举类型:
⼀共有七种传播⾏为:
在代码中设置事务的传播⾏为:
@Transactional(propagation = Propagation.REQUIRED)
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事 务与其他事务隔离的程度称为隔离级别。
SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同 的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
数据库中读取数据存在的三⼤问题:(三⼤读问题)
隔离级别一共有四种:
各个隔离级别解决并发问题的能力
各种数据库产品对事务隔离级别的支持程度
使用,隔离级别在spring中以枚举类型存在:
代码使用
- @Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
- @Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
- @Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
- @Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
- @Transactional(isolation = Isolation.SERIALIZABLE)//串行化
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这 样数据库就能够针对查询操作来进行优化。
- @Transactional(readOnly = true)
- public void buyBook(Integer bookId, Integer userId) {
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- //System.out.println(1/0);
- }
对增删改操作设置只读会抛出下面异常: Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
该特性的作⽤是:启动spring的优化策略。提⾼select语句执⾏效率。
如果该事务中确实没有增删改操作,建议设置为只读事务
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间 占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。
此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常 程序可以执行。
概括来说就是一句话:超时回滚,释放资源。
- @Transactional(timeout = 3)
- public void buyBook(Integer bookId, Integer userId) {
- try {
- TimeUnit.SECONDS.sleep(5);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- //System.out.println(1/0);
- }
执行过程中抛出异常: org.springframework.transaction.TransactionTimedOutException
以上代码表示设置事务的超时时间为3秒。 表示超过3秒如果该事务中所有的DML语句还没有执⾏完毕的话,最终结果会选择回滚。
默认值-1,表示没有时间限制。
这⾥有个坑,事务的超时时间指的是哪段时间? 在当前事务当中,最后⼀条DML语句执⾏之前的时间。如果最后⼀条DML语句后⾯很有很多业务逻辑, 这些业务代码执⾏的时间不被计⼊超时时间。
以下代码的超时不会被计⼊超时时间
- @Transactional(timeout = 10) // 设置事务超时时间为10秒。
- public void save(Account act) {
- accountDao.insert(act);
- // 睡眠⼀会
- try {
- Thread.sleep(1000 * 15);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
当然,如果想让整个⽅法的所有代码都计⼊超时时间的话,可以在⽅法最后⼀⾏添加⼀⾏⽆关紧要的 DML语句。
声明式事务默认只针对运行时异常回滚,编译时异常不回滚。 可以通过@Transactional中相关属性设置回滚策略
rollbackFor属性:需要设置一个Class类型的对象
rollbackForClassName属性:需要设置一个字符串类型的全类名
noRollbackFor属性:需要设置一个Class类型的对象
rollbackFor属性:需要设置一个字符串类型的全类名
- @Transactional(noRollbackFor = ArithmeticException.class)
- //@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
- public void buyBook(Integer bookId, Integer userId) {
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- System.out.println(1/0);
- }
虽然购买图书功能中出现了数学运算异常(ArithmeticException),但是我们设置的回滚策略是,当 出现ArithmeticException不发生回滚,因此购买图书的操作正常执行
表示只有发⽣RuntimeException异常或该异常的⼦类异常才回滚。
@Transactional(rollbackFor = RuntimeException.class)
编写⼀个类来代替配置⽂件,代码如下:
- @Configuration
- @ComponentScan("com.demo.bank")
- @EnableTransactionManagement
- public class Spring6Config {
- @Bean
- public DataSource getDataSource(){
- DruidDataSource dataSource = new DruidDataSource();
- dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
- dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
- dataSource.setUsername("root");
- dataSource.setPassword("root");
- return dataSource;
- }
- @Bean(name = "jdbcTemplate")
- public JdbcTemplate getJdbcTemplate(DataSource dataSource){
- JdbcTemplate jdbcTemplate = new JdbcTemplate();
- jdbcTemplate.setDataSource(dataSource);
- return jdbcTemplate;
- }
- @Bean
- public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
- DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
- dataSourceTransactionManager.setDataSource(dataSource);
- return dataSourceTransactionManager;
- }
- }
基于xml实现的声明式事务,必须引入aspectJ的依赖
- <dependency>
- <groupId>org.springframeworkgroupId>
- <artifactId>spring-aspectsartifactId>
- <version>5.3.1version>
- dependency>
applicationContext.xml配置
- "1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:tx="http://www.springframework.org/schema/tx"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context.xsd
- http://www.springframework.org/schema/tx
- http://www.springframework.org/schema/tx/spring-tx.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop.xsd">
-
- <context:component-scan base-package="com.demo"/>
-
- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
- <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
- <property name="username" value="root"/>
- <property name="password" value="root"/>
- bean>
-
- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="dataSource"/>
- bean>
-
-
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource"/>
- bean>
-
-
-
-
- <tx:advice id="txAdvice" transaction-manager="transactionManager">
- <tx:attributes>
-
-
- <tx:method name="get*" read-only="true"/>
- <tx:method name="query*" read-only="true"/>
- <tx:method name="find*" read-only="true"/>
-
-
-
-
-
-
- <tx:method name="save*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Throwable"/>
- <tx:method name="del*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Throwable"/>
- <tx:method name="update*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Throwable"/>
- <tx:method name="transfer*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Throwable"/>
- tx:attributes>
- tx:advice>
-
-
- <aop:config>
- <aop:pointcut id="txPointcut" expression="execution(* com.demo.service..*(..))"/>
-
- <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
- aop:config>
- beans>
事务注解 @Transactional 失效的3种场景及解决办法