Spring05_Spring事务

一、JdbcTemplate 工具

​ JdbcTemplate 类是 Spring 框架提供一个用于操作数据库的模板类,JdbcTemplate 类支持声明式事务管理。该类提供如下方法来执行数据库操作。

​ 1、queryForObject 查询单个对象

queryForObject(String sql,RowMapper mapper,Object[] args)

​ 2、query 查询集合

List query(String sql,RowMapper mapper,Object[] args)

​ 3、update 增加、删除、修改

int update(String sql, Object[] args)

​ 4、batchUpdate 批量增加、删除、修改

int[] batchUpdate(String sql, List list)

copy
package com.qlu.pojo; import lombok.Data; import lombok.experimental.Accessors; import java.util.Date; @Data @Accessors(chain = true) public class Book { private Long id; private String bookName; private String bookAuthor; private Date createTime; private Date updateTime; }

​ 相同包的放到一起了。

copy
package com.qlu.service; import com.qlu.pojo.Book; public interface BookService { void add(Book book); void add(Integer value); void delete(); void update(Book book); void find(); } package com.qlu.service.impl; import com.qlu.pojo.Book; import com.qlu.service.BookService; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @Service("bookService") public class BookServiceImpl implements BookService { private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public void add(Book book) { System.out.println("添加图书"); String sql = "insert into book (book_name,book_author,create_time,update_time) " + "values(?,?,?,?)"; Object[] args = {book.getBookName(),book.getBookAuthor(),book.getCreateTime(),book.getUpdateTime()}; int result = jdbcTemplate.update(sql, args); System.out.println(result>0?"成功":"失败"); } @Override public void add(Integer value) { System.out.println("有参数的add方法 添加图书"); } @Override public void delete() { System.out.println("删除图书"); } @Override public void update(Book book) { System.out.println("修改图书"); String sql = "update book set book_name =?,book_author=?,update_time=? where id = ?"; Object[] args = {book.getBookName(),book.getBookAuthor(),book.getUpdateTime(),book.getId()}; int result = jdbcTemplate.update(sql, args); System.out.println(result>0?"成功":"失败"); } @Override public void find() { System.out.println("查找图书"); } }

​ 就用了一个 @Service 注解,其他的 bean 都是直接配置在 xml 文件中的。

标记

copy
<?xml version="1.0" encoding="utf-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" 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/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="bookService" class="com.qlu.service.impl.BookServiceImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <!-- 配置 Spring 的事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置 C3P0 数据库链接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF-8&amp;useUnicode=true"/> <property name="user" value="root"/> <property name="password" value="a.miracle"/> <property name="maxPoolSize" value="50"/> <property name="minPoolSize" value="20"/> <property name="initialPoolSize" value="20"/> <property name="maxIdleTime" value="200"/> </bean> <!--配置jdbc模板工具类--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> </beans>

​ 因为使用到了数据库连接池的配置,所以需要以下东西

copy
<dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>

​ 到这儿就差不多了,反正我不想写了。

二、Spring 事务

(一)事务概述

​ 事务是恢复和并发控制的基本单位。即一组操作要么全都不做,要不就全都做。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性

​ 1.原子性:一个事务不可分割,里面的操作只能选择全做或者全不做。

​ 2.一致性:即事务是把数据库从一个一致的状态变到另一个一致的状态。

​ 3.隔离性:事务的执行不能相互干扰。

​ 4.持久性:事务一旦提交对数据库的改变就是永久的。

(二)事务管理举例

​ 我们以银行赚钱为实例,创建如下数据表并且插入数据如下

copy
CREATE TABLE `money` ( `id` bigint NOT NULL, `person_name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, `sum` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

image-20230419163504603

​ 依然配置用到了c3p0等数据库连接池,可以参少上面 标记 处,下面给出 dao、pojo、service 层的逻辑。

copy
package com.qlu.service; public interface MoneyService { /** * 转钱 * @param fromId 从哪儿 * @param toId 转给谁 * @param money 转账金额 * @return */ boolean changeMoney(int fromId,int toId,int money); }

​ 下面是数据持久层,在数据持久层的 changeMoney 有两个参数,你可以认为是改变某个 id 的 sum 值(sum表示总的金额)。

copy
package com.qlu.dao; public interface MoneyDao { /** * 根据id修改金额 * @param id * @param money * @return */ int changeMoney(int id,int money); } @Repository("moneyDao") public class MoneyDaoImpl implements MoneyDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public int changeMoney(int id, int money) { String sql = "update money set sum = sum + ? where id = ?"; Object[] args = {money,id}; return jdbcTemplate.update(sql,args); } }

​ 下面是业务逻辑层,service 中的 changeMoney 表示从哪个 id 转移到哪个 id ,转多少钱。

copy
package com.qlu.service; public interface MoneyService { /** * 转钱 * @param fromId 从哪儿 * @param toId 转给谁 * @param money 转账金额 * @return */ boolean changeMoney(int fromId,int toId,int money); } package com.qlu.service.impl; @Service("moneyService") public class MoneyServiceImpl implements MoneyService { @Autowired private MoneyDao moneyDao; @Override public boolean changeMoney(int fromId, int toId, int money) { /** * from 减少 * to 增加 */ //张三减少 int result1 = moneyDao.changeMoney(fromId,-1*money); //李四增加 int result2 = moneyDao.changeMoney(toId,money); return (result1>0 && result2>0 ? true:false); } }

​ 通过 Ctrl + Shift + T 生成测试业务逻辑的测试类

copy
@Test public void textTx1() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml"); MoneyService moneyService = applicationContext.getBean(MoneyService.class); boolean result = moneyService.changeMoney(1, 2, 100); System.out.println(result); }

​ 成功后我们可以看到数据库的变化

image-20230419163547954

​ 显然在这种正常的情况下没有错的,我们可以自定义一个 /by zero 异常在张三的钱减少和李四的钱增加之间来触发这个异常,

image-20230419165613851

​ 此时张三的钱没有了,但是李四的钱却没有增加,这一点类似于我们在多线程访问共享资源的情况一样,不给共享资源上锁,就可能出现两个线程信息不同步的情况。

image-20230419165741721

​ 为解决这个情况,Spring 提供了 TransactionManger,该功能依赖于 AOP,下面的配置都是老几样了,无非就是相当于把这个事务管理器当成一个增强函数来用,然后定义切面 aop-config,里面定义切点和增强组成切面,需要注意的是这个 aop:aspect 得换成我们事务管理的 aop:advisor

copy
xmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd" <!-- 配置 Spring 的事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置事务的消息通知 --> <tx:advice id="tAdvice" transaction-manager="txManager"> <tx:attributes> <!-- 配置消息通知类型列表,监控的方法由上而下开始匹配,以及事务的传播特性 --> <tx:method name="find*" propagation="SUPPORTS"/> <tx:method name="query*" propagation="SUPPORTS"/> <tx:method name="add*"/> <tx:method name="delete*"/> <tx:method name="update*"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="pointcut" expression="execution(* com.qlu.service.impl.*.*(..))"/> <!--配置切面,将消息通知切入到Service层--> <aop:advisor advice-ref="tAdvice" pointcut-ref="pointcut"></aop:advisor> </aop:config>

​ 关于配置事务的消息通知点这里

​ 再来跑一下,那自然还是有异常抛出,但是重点关注于数据库的数据,和上面可以说是完全一样,事务具有原子性(不可分割),这个减少 money 和增加 money 要不都做,要不都别做。

image-20230419183701278

​ 难办?那就都别办了,在张三减少了之后触发异常,程序被迫中止,那已经完成的 “张三减少” 也应该回滚成为没有减少的状态。image-20230419184349218

​ 针对可能抛出异常的代码块,我们可能会选择使用 try...catch 环绕捕获异常

copy
@Override public boolean changeMoney(int fromId, int toId, int money) { /** * from 减少 * to 增加 */ try { //张三减少 int result1 = moneyDao.changeMoney(fromId,-1*money); int exception = 1/0; //李四增加 int result2 = moneyDao.changeMoney(toId,money); return (result1>0 && result2>0 ? true:false); } catch (Exception e) { e.printStackTrace(); //throw e; return false; } }

image-20230419184936840

​ 此时在这个业务内部就完成了对异常的处理(捕获到打印出信息),那在外部事务管理就不会捕获到这个异常。

image-20230419184951542

​ 解决方法:再把这个异常扔出来就行了,这样外部事务管理就可以捕获到异常信息

image-20230419185352163

​ 数据库没有变化(忘了应该在测试的 before 里面加个当前系统时间的)

image-20230419185400653

(三)Spring 事务的传播

​ 这个配置事务的消息通知就把它当成增强函数看就行,毕竟事务管理就是基于AOP的,你AOP能用注解事务管理也肯定是能用的。

tx:attributes 内的 tx:method 有如下属性:

​ 1、name 属性:表示在执行哪些方法时,会触发消息通知,name 的属性值支持模糊匹配,例如:find*,表示 调用以 find 开头的方法时都会触发消息通知。

​ 2、propagation 属性:表示事务的传播特性:

​ 属性值“SUPPORTS”表示支持当前事务,如果当前没有事务, 就使用无事务机制;

​ 属性值“REQUIRED”支持当前事务,有事务就加入到该事务中,如果没有事务则新建事务,默认值;

​ 属性值 REQUIRES_NEW 如果有当前事务,则挂起当前事务,新建新事务,反之,直接新建事务;

​ 属性值 MANDATORY 支持当前事务,如果没有事务,则抛出异常。

​ 3、timeout 属性:事务超时时间 默认值是-1,-1 表示不超时,以秒为单位。

​ 4、read-only 属性:事务是否只读,默认值是 false

​ 关于事务的传播机制:

传播机制 说明
REQUIRED 如果当前没有事务,就创建一个事务,如果已经存在事务,就加入到这个事务。当前传播机制也是spring默认传播机制
REQUIRES_NEW 新建事务,如果当前存在事务,就抛出异常。
SUPPORTS 支持当前事务,如果当前没有事务, 就使用无事务机制。
NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
MANDATORY 支持当前事务,如果没有事务,则抛出异常。
NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED 如果当前存在事务,则在嵌套的事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作
posted @   Purearc  阅读(19)  评论(2编辑  收藏  举报
点击右上角即可分享
微信分享提示
🚀