事务
一、概述
-
概念
事务是数据库操作的最基本单元,逻辑上的一组操作,要么都成功,如果有一个失败所有操作都失败
-
四个特性 - ACID
原子性(Atomicity)
该组操作在功能上不可分割,要成功必须都成功,有一个执行不通过则整组操作全部失败
一致性(Consistency)
操作之前和操作之后的总量是不变的
隔离性(Isolation)
多事务操作,彼此之间不会产生影响。
持久性(Duration)
提交后会对数据库中的数据产生对应的改变,这种改变是持久的
二、搭建事务操作环境
-
业务逻辑
-
在数据库中创建数据表,添加数据
-
创建service,搭建dao
① service中注入dao,dao中注入JdbcTemplate,JdbcTemplate中注入DataSource
② 在dao中写入具体的事务方法
③ 在service中写入业务逻辑,并进行事务管理
-
事务管理
① 开启事务
② 进行业务操作
③ 如果没有异常,提交事务
④ 如果有异常,回滚事务
-
操作代码示例
xml配置文件
<?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" 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"> <!-- 组件扫描 --> <context:component-scan base-package="com.atguigu.spring5"/> <!-- 数据库连接池 --> <context:property-placeholder location="jdbc.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="${prop.url}"/> <property name="username" value="${prop.username}"/> <property name="password" value="${prop.password}"/> <property name="driverClassName" value="${prop.driverClassName}"/> </bean> <!-- 创建JdbcTemplate对象 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入DataSource --> <property name="dataSource" ref="dataSource"/> </bean> </beans>
Service
public class UserService { // 注入dao @Autowired private UserDAO userDAO; public void accountMoney() { try { // 第一步 开启事务 // 第二步 业务操作 // lucy少100 userDAO.reduceMoney(); // 模拟异常 int i = 10/0; // mary多100 userDAO.addMoney(); // 第三步:没有异常,提交事务 } catch (Exception e) { // 第四步:回滚事务 } } }
dao
@Repository public class UserDAOImpl implements UserDAO{ // 注入JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; // 少钱 @Override public void reduceMoney() { String sql = "update t_account set money = money - ? where userName = ?"; jdbcTemplate.update(sql,100,"lucy"); } // 多钱 @Override public void addMoney() { String sql = "update t_account set money = money + ? where userName = ?"; jdbcTemplate.update(sql,100,"mary"); } }
三、Spring中的事务操作
-
事务添加到JavaEE三层结构里面的Service层(业务逻辑层)
-
在Spring进行事务管理有两种方式:
编程式:就是每次都要书写事务的各个步骤,会造成代码臃肿,且在后期不方便修改
声明式:有两种形式,基于注解、基于xml配置文件
-
在Spring中进行声明式事务管理,底层使用AOP原理
-
Spring事务管理API
① 提供了一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
四、基于注解 - 声明式事务操作
-
在spring配置文件中配置事务管理器
<!-- 创建事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
-
在spring配置文件中开启事务注解
<?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: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/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <beans> <!-- 开启事务注解 --> <tx:annotation-driven transaction-manager="transactionManager" /> </beans>
-
在service上(或service内部的方法上)添加事务注解:@Transactional
① 如果添加到了类上面,相当于里面的所有方法都添加了事务注解
② 如果添加到了具体的方法上,就只有该方法添加了事务注解
@Service @Transactional public class UserService { // 注入dao @Autowired private UserDAO userDAO; public void accountMoney() { // 1.lucy少100 userDAO.reduceMoney(); // 2.模拟异常 int i = 10/0; // 3.mary多100 userDAO.addMoney(); } }
-
@Transactional注解中的参数
propagation:事务传播行为
ioslation:事务隔离级别
timeout:超时时间
readOnly:是否只读
rollbackFor:回滚
noRollbackFor:不回滚
-
propagation:事务传播行为
Service @Transactional(propagation = Propagation.REQUIRED) public class UserService {
-
ioslation:事务隔离级别
@Service @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ) public class UserService {
-
timeout:超时时间
事务需要在一定的时间内进行提交,如果超过时间未提交,就会回滚
默认值是-1,表示不超时,设置的值的单位为秒
@Service @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ, timeout=-1) public class UserService {
-
readOnly:是否只读
读:查询操作;写:增删改操作
readOnly的默认值为false,即增删改查都可进行;设置为true,只可以进行读操作,即只可查询
-
rollbackFor:回滚
设置出现哪些异常会进行事务回滚
-
noRollbackFor:不回滚
设置出现哪些异常不会进行事务回滚
五、事务知识补充
-
事务传播行为的分类
注:描述中的当前的方法指代的是被调用的方法
传播属性 描述 REQUIRED 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行(@Transation注解的默认参数) REQUIRED_NEW 当前的方法必须启动新事物,并在它自己的事务内运行,如果有事务正在运行,必须将它挂起 SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中 NOT_SUPPORTED 当前的方法不应该运行在事务中,如果有运行的事务,就将它挂起 MANDATORY 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常 NEVER 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常 NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行。 具体举例说明,详见博文:[带你读懂Spring 事务——事务的传播机制 (https://zhuanlan.zhihu.com/p/148504094#circle=on)
-
事务的隔离性会引发的三种读问题
事务的特性中有隔离性,即多事务操作之间互不影响。如果不考虑隔离性,会产生脏读、不可重复读和幻读现象。
脏读:一个未提交事务读取到了另一个未提交事务修改的数据。如果另一事务发生回滚,那么就会发生数据错误。
不可重复读:一个未提交事务读取到了另一个已提交事务修改的数据。因为事务之间应该是互不影响的,正常情况,应该是该事务提交之后,才可以读到被其它事务修改的数据
幻读:一个未提交的事务读取到了另一个已提交事务插入的数据或没有读到另一个已提交事务删除的数据
具体说明,可参考:带你读懂Spring 事务——事务的隔离级别 - 知乎 (zhihu.com)
-
设置事务的隔离性
隔离级别 脏读 不可重复读 幻读 READ UNCOMMITTED(读未提交) 有 有 有 READ COMMITTED(读已提交) 无 有 有 REPEATABLE READ(可重复读)(默认) 无 无 有 SERIALIZABLE(串行化) 无 无 无
六、基于xml - 声明式事务操作
-
配置事务管理器
-
配置通知
-
配置切入点和切面
<?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: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.atguigu.spring5"/> <!-- 数据库连接池 --> <context:property-placeholder location="jdbc.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="${prop.url}"/> <property name="username" value="${prop.username}"/> <property name="password" value="${prop.password}"/> <property name="driverClassName" value="${prop.driverClassName}"/> </bean> <!-- 创建JdbcTemplate对象 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入DataSource --> <property name="dataSource" ref="dataSource"/> </bean> <!-- 1.创建事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 2.配置通知 --> <tx:advice id="txadvice"> <!-- 配置事务参数 --> <tx:attributes> <!-- 在符合指定的命名规则的方法上添加事务 --> <tx:method name="account*" propagation="REQUIRED" isolation="REPEATABLE_READ"/> </tx:attributes> </tx:advice> <!-- 3.配置切入点和切面 --> <aop:config> <!-- 配置切入点 --> <aop:pointcut id="pt" expression="execution(* com.atguigu.spring5.service.UserService.*(..))"/> <!-- 配置切面 第一个参数是通知 第二个参数是切入点 该切面的配置就是将通知(增强)匹配到对应的切入点上 --> <aop:advisor advice-ref="txadvice" pointcut-ref="pt" /> </aop:config> </beans>
七、完全注解开发模式
-
创建配置类
// 指定其是配置类 @Configuration // 开启组件扫描 @ComponentScan(basePackages = "com.atguigu.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/bookdb"); dataSource.setUsername("root"); dataSource.setPassword("7989775nx"); return dataSource; } // 创建JdbcTemplate对象 // 在IOC容器中,已经存在了DataSource,在JdbcTemplate的方法中的参数可通过组件扫描达到自动匹配 @Bean public JdbcTemplate getJdbcTemplate(DruidDataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate(); // 注入DataSource jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } // 创建事务管理器 @Bean public DataSourceTransactionManager getTransactionManager(DruidDataSource dataSource) { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }
-
测试类
public class TestAccount { @Test public void test2() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class); UserService userService = context.getBean("userService", UserService.class); userService.accountMoney(); } }