11、Spring5-事务操作
1、事务的概念
1、什么是事务
(1)事务是数据库操作最基本单元,逻辑上的一组操作,要么都成功,如果有一个失败,那么所有操作都失败
(2)典型场景:银行转账
lucy转账 100 给mary,lucy少100,mary多100,若出现异常,lucy不会少,mary不会多
2、事务的特性(ACID):
(1)原子性:过程不可分割
(2)一致性:事务执行之前或执行之后数据库都必须,处于一致性状态,不管是错误还是断电,总量不变
(3)隔离性:多事务操作不会相互影响
(4)持久性:事务提交后就会更改表中的数据
2、搭建事务操作的基本环境
1、创建数据库和表,添加记录
2、创建service,搭建 dao 和实现类,完成对象创建和注入关系,配置连接池
(1)service 注入 dao ,在 dao 中注入 jdbcTemplate ,在jdbcTemplate 中注入 DataSource
service中注入dao对象
@Service public class UserService { //注入dao @Autowired private UserDao userDao; }
dao中注入jdbcTemplate对象
@Repository public class USerDaoImpl implements UserDao{ @Autowired private JdbcTemplate jdbcTemplate; }
配置文件中jdbcTemplate注入DataSource
<?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: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/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"> <!--开启组件扫描--> <context:component-scan base-package="com.spring5"></context:component-scan> <!--引入外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--配置数据库连接池--> <!-- DruidDataSource dataSource = new DruidDataSource(); --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <!-- dataSource.setDriverClassName("com.mysql.jdbc.Driver"); set方法注入 --> <!-- 获取properties文件内容,根据key获取,使用spring表达式获取 --> <property name="driverClassName" value="${prop.driverClass}"></property> <property name="url" value="${prop.url}"></property> <property name="username" value="${prop.userName}"></property> <property name="password" value="${prop.password}"></property> </bean> <!-- JdbcTemplate对象 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入dataSource--> <property name="dataSource" ref="dataSource"></property> </bean> </beans>
3、在dao创建两个方法,一个是多钱的方法,一个是少钱的方法,在Service中创建转账方法
下面是转账方法的基本实现:
dao中的实现如下:
@Repository public class USerDaoImpl implements UserDao{ @Autowired private JdbcTemplate jdbcTemplate; //lucy转账100给mary @Override public void addMoney() { String sql = "update t_account set money=money-? where username=?"; jdbcTemplate.update(sql,100,"lucy"); } //mary收款100 @Override public void reduceMoney() { String sql = "update t_account set money=money+? where username=?"; jdbcTemplate.update(sql,100,"mary"); } }
service的实现方法如下:
@Service public class UserService { //注入dao @Autowired private UserDao userDao; //转账的方法 public void accountMoney(){ //lucy少100 userDao.reduceMoney(); //mary多100 userDao.addMoney(); } }
4、上面代码实现转账功能,正常执行时是没有问题的,但是如果代码执行过程中出现了异常,会产生一些问题
(1)上面的问题如何解决?
使用事务进行解决:逻辑上一组事务要么都成功,一个失败所有都失败
(2)基本步骤如下:
try{ //第一步 开启事务操作 //第二步 进行业务操作(转账) //第三步 没有异常,提交事务 }catch (Exception e){ //第四步 出现异常,事务回滚 }
上述为编程式事务管理,但是在开发中一般使用声明式事务管理
3、Spring事务管理介绍
1、事务一般添加到 JavaEE 三层结构的 Service 层(业务逻辑层)
2、在Spring进行进行事务管理操作,
(1)有两种方式:编程式事务管理和声明式事务管理(一般使用声明式)
3、声明式事务管理
(1)基于注解方式(常用)
(2)基于xml配置文件方式
4、在Spring中进行声明式事务管理,底层使用 AOP 原理
5、Spring事务管理相关API
(1)提供一个接口,代替事务管理器,针对不同的框架提供了不同的实现类
PlatformTransactionManager 代表事务管理器,Spring 做事务管理都是使用这个接口做到的
DataSourceTransactionManager 针对数据库的事务管理
PlatformTransactionManager 是一个顶层接口,在接口中针对不同操作数据库的框架,有不同的实现类。
4、实现声明式事务管理(注解的方式)
1、在 spring 配置文件中配置事务管理器
<!--创建事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean>
2、在Spring配置文件中,开启事务的注解
(1)在 Spring 的配置文件中引入名称空间 tx
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
(2)开启事务的注解
<!--开启事务的注解 transaction-manager 指定事务管理器 --> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
3、在Service 类上面(或在 Service 类中的方法上面)添加事务的注解 @Transactional
(1)@Transactional 可以添加到类上面,也可以添加到方法上面
(2)如果把这个注解添加到类上面,则表示这个类里面的所有方法都添加上了事务
(3)如果式添加到类的方法上面,则只是为这个方法添加了事务
@Service @Transactional //@Transactional可以加到类上,也可以加到方法上面 public class UserService { //注入dao @Autowired private UserDao userDao; //转账的方法 public void accountMoney(){ // //lucy少100 userDao.reduceMoney(); // //模拟异常 int i=10/0; // //mary多100 userDao.addMoney(); } }
4、声明式事务管理参数配置
1、在 service 类上面添加注解 @Transactional,在这个注解里面可以配置事务相关参数
2、propagation : 表示事务的传播行为
(1)多事务方法之间进行调用(有事务方法调无事务方法或有事务方法调有事务方法等情况),这个事务是如何进行管理的。如:
默认时 REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
3、isolation : 表示事务的隔离级别
(1)事务中有一个特性称为隔离性,多事务操作之间不会产生影响。不考虑隔离性会产生很多问题
(2)有三个读的问题:脏读、不可重复读、虚读(幻读)
脏读:多个事务之间,一个未提交的事务读取到了另外一个未提交的数据
不可重复读:一个未提交的事务,读取到了另一个提交事务修改的事务
幻读:一个未提交的事务,读取到了另外一个提交事务添加的数据
(3)通过设置事务的隔离性,解决读的问题
事务的隔离级别:
隔离级别设置:
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
在 mysql 中默认使用的时 REAPEATABLE READ 隔离级别
4、timeout : 超时时间
(1)事务在一定的时间内进行提交,否则进行事务的回滚
(2)在 Spring 中,默认值时-1(不超时),可以对时间进行设置,以秒为单位
@Transactional(timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
5、readonly : 是否只读
(1)读:查询操作,写:添加修改删除操作
(2)readOnly 的默认值时 false ,表示可以进行增加修改删除操作
(3)可以设置 readOnly 的值为 true ,设置成 true 后,只能查询
@Transactional(readOnly = false,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
6、rollbackFor : 回滚
(1)设置事务中出现的哪些异常,进行回滚
7、noRollbackFor : 不回滚
(2)设置事务出现那些异常不进行回滚
5、使用xml配置文件的方式进行事务的操作
1、在spring配置文件中进行配置
第一步 配置事务管理器
第二步 配置通知(增强)
第三步 配置切入点和切面
<!--配置通知--> <tx:advice id="txAdvice"> <!--配置事务的相关参数--> <tx:attributes> <!--指定哪种规则的方法上面添加事务--> <tx:method name="accountMoney" propagation="REQUIRED"/><!--可以在后面添加属性--> <!-- <tx:method name="account*"/><!–表示以account开头的方法都进行事务操作–>--> </tx:attributes> </tx:advice> <!--配置切入点和切面--> <aop:config> <!--配置切入点--> <aop:pointcut id="pt" expression="execution(* com.spring5.service.UserService.*(..))"/> <!--配置切面--> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor> </aop:config>
6、完全注解方式进行声明式事务管理
1、创建配置类,使用配置类替代xml配置文件
@Configuration //代表它是一个配置类 @ComponentScan(basePackages = "com.spring5") //组件扫描 @EnableTransactionManagement //开启事务 public class txConfig { //创建数据库连接池 @Bean public DruidDataSource getDruidDataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/user_db"); dataSource.setUsername("root"); dataSource.setPassword("124869"); return dataSource; } //创建JdbcTemplate对象 @Bean public JdbcTemplate getJdbcTemplate(DataSource dataSource){ //到ioc容器中,根据类型找到dataSource完成注入 JdbcTemplate jdbcTemplate = new JdbcTemplate(); //注入dataSource jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } //创建事务管理器对象 @Bean public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }
2、在Service中添加注解@Transactional
@Service @Transactional(readOnly = false,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ) //@Transactional可以加到类上,也可以加到方法上面 public class UserService { //注入dao @Autowired private UserDao userDao; //转账的方法 public void accountMoney(){ //lucy少100 userDao.reduceMoney(); //模拟异常 int i=10/0; //mary多100 userDao.addMoney(); } }
测试代码如下:
@Test public void testAccount3(){ ApplicationContext context = new AnnotationConfigApplicationContext(txConfig.class); UserService userService = context.getBean("userService", UserService.class); userService.accountMoney(); }