1、事务概念
https://blog.csdn.net/chengqingshihuishui/article/details/111806766
2、事务操作
3、引出事务
需求:
银行转账
* lucy 转账100元 给mary
* lucy少100,mary多100
3.1 创建数据表,添加记录
用SQLyog直接创建好数据表
3.2 编写spring配置文件,和上例一样的。bean1.xml下写配置。
(1)配置druid数据池
(2)jdbcTemplate对象中注入dataSource数据池
(3)开启组件注解扫描,扫描目录为com.atguigu
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!-- 配置JdbcTemplate对象,注入DataSource -->
<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.atguigu"></context:component-scan>
3.3 在com.atguigu.spring5文件夹下创建service文件夹,在该包下com.atguigu.spring5.service创建UserService类
package com.atguigu.spring5.service;
@Service
public class UserService{
//将创建好的userDao对象注入
@Autowired
private UserDao userDao;
}
3.4 在com.atguigu.spring5文件夹下创建dao文件夹,创建接口UserDao 和 该接口实现类UserDaoImpl
创建接口
package com.atguigu.spring5.dao;
public interface UserDao{
}
创建实现类
package com.atguigu.spring5.dao;
//创建UserDao对象 userDao
@Repository
public class UserDaoImpl implements UserDao{
//在该类注入jdbcTemplate对象
@Autowired
private JdbcTemplate jdbcTemplate;
}
3.5 在dao创建两个方法:多钱和少钱的方法,在实现类中实现lucy 转账100元 给mary,lucy少100,mary多100
package com.atguigu.spring5.dao;
public interface UserDao{
//多钱
public void addMoney();
//少钱
public void reduceMoney();
}
进入实现类中,实现转账方法
package com.atguigu.spring5.dao;
//创建UserDao对象 userDao
@Repository
public class UserDaoImpl implements UserDao{
//在该类注入jdbcTemplate对象
@Autowired
private JdbcTemplate jdbcTemplate;
//lucy转账100给mary
//少钱方法
@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");
}
}
再进入UserService写方法,accountMoney是lucy少100,mary多一百的集合
package com.atguigu.spring5.service;
@Service
public class UserService{
//将创建好的userDao对象注入
@Autowired
private UserDao userDao;
//转账的方法
public void accountMoney() {
//lucy少100
userDao.reduceMoney();
//mary多100
userDao.addMoney(); }
}
在com.atguigu.spring5文件夹下,创建test文件夹,再该包下创建测试类TestBook
package com.atguigu.spring5.test;
public class TestBook{
@Test
public void testAccount(){
ApplicationContext context=new ClassPathXmlApplicationContext("bean1.xml");
UserService userService=context.getBean("userService",UserService.class);
userService.accountMoney();
}
}
以上代码,某个环节一旦出现异常,就会破坏事务一致性原则。因此需要引出事务进行操作
4、事务操作过程
以上步骤只是方便说明事务的过程,实际已经不用了,以上步骤是编程式事务管理,非常不方便。
5、spring事务管理介绍
5.1 简介
(1)事务添加到JavaEE三层结构(web、Service、dao)里面的Service层(业务逻辑层)
(2)Spring进行管理操作有两种方式:编程式事务管理 和 声明式事务管理(使用)
(3)声明式事务管理又分为基于注解方式(使用) 和 基于xml配置文件方式
(4)Spring进行声明式事务管理,底层使用AOP原理
5.2 Spring事务管理API
事务管理器 PlatformTransactionManager接口,目录结构如下:
该接口都写好了子接口和实现类(根据不同的框架有不同的实现类)
我们jdbcTemplate自然就使用DateSourceTransactionManager实现类
5.3 事务操作演示
5.3.1 注解声明式事务管理
(1)在spring配置文件配置事务管理器
回到bean1.xml配置文件。用标签创建事务管理器实现类对象
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!-- 配置JdbcTemplate对象,注入DataSource -->
<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
(2)使用spring配置文件,扫描开启事务的注解
① 引入名称空间tx
<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">
② 开启事务的注解
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
说明:
tx:annotation-driven 标签开启注解
transaction-manager属性表示事务管理器对象,说明开启哪个事务管理器的注解
完整的bean.xml
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!-- 配置JdbcTemplate对象,注入DataSource -->
<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
(3)在service类上面(或者service类里面方法上面)添加事务注解
① @Transactional,这个注解添加到类上面,也可以添加方法上面
② 如果把这个注解添加类上面,这个类里面所有的方法都添加事务
③ 如果把这个注解添加方法上面,为这个方法添加事务
package com.atguigu.spring5.service;
@Service
@Transactional //整个类的所有方法,都开启事务
public class UserService{
//将创建好的userDao对象注入
@Autowired
private UserDao userDao;
//转账的方法
public void accountMoney() {
//lucy少100
userDao.reduceMoney();
//mary多100
userDao.addMoney(); }
}
(4)编写测试文件(就是原来的测试文件)
package com.atguigu.spring5.test;
public class TestBook{
@Test
public void testAccount(){
ApplicationContext context=new ClassPathXmlApplicationContext("bean1.xml");
UserService userService=context.getBean("userService",UserService.class);
userService.accountMoney();
}
}
此时,就算出现异常,也会自动回滚了。(原来没有加事务注解之前,出异常就凉凉)
5.3.2 声明式事务管理的参数配置(即@Transactional注解的属性值配置)
说明:在service类上面添加注解@Transactional,在这个注解里面可以配置事务相关参数(主要讲解以下标记红色的参数)
(1)propagation():事务传播行为
事务传播行为:多个事务方法之间,存在相互调用,这个过程就是传播行为
事务方法:对数据库表的数据进行变化的操作(比如增删改,查不是事务方法)
如图:add事务方法调用update事务方法,就是传播行为
最常用的两个行为就是REQUIRED 和 REQUIRED NEW(必须掌握,其他了解即可)
注解属性 | 说明 |
REQUIRED(默认行为) | 如果add方法本身有事务,调用update方法之后,update使用当前add方法里面的事务。如果add方法本身没有事务,调用update方法之后,创建新事务 |
REQUIRED_NEW | 使用add方法调用update方法,如果add无论是否有事务,都创建新的事务 |
@Transaction不写,默认使用propagation=Propagation.REQUIRED
package com.atguigu.spring5.service;
@Service
@Transactional(propagation=Propagation.REQUIRED) //不写属性,默认也是这个
public class UserService{
}
(2)ioslation:事务的隔离级别
复习mysql事务隔离性:
① 事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
② 有三个读问题:脏读、不可重复读、虚(幻)读
③ 脏读(绝对不能忍,会导致严重问题):一个未提交事务读取到另一个未提交事务的数据
④ 不可重复读(也不算问题,可以忍,所以mysql默认是不解决这个):一个未提交事务读取到另一提交事务修改数据
⑤ 虚读:一个未提交事务读取到另一提交事务添加数据(跟不可重复读一样,可以忍的)
解决办法:设置事务隔离级别
语法:
@Transactional注解的isolation=属性值
默认值:isolation=Isolation.REPEATABLE_READ
@Service
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.REPEATABLE_READ)
public class UserService{
}
(4)timeout:超时时间
① 事务需要在一定时间内进行提交,如果不提交进行回滚
② 默认值是 -1 ,设置时间以秒单位进行计算
语法:
@Transactional(timeout=-1)
(5)readOnly:是否只读
① 读:查询操作,写:添加修改删除操作
② readOnly默认值false,表示可以查询,可以添加修改删除操作
③ 设置readOnly值是true,设置成true之后,只能查询
@Transactional(readOnly=false)
(6)rollbackFor:回滚
设置出现哪些异常进行事务回滚
(7)noRollbackFor:不回滚
设置出现哪些异常不进行事务回滚
5.4 XML声明式事务管理(基本不用,了解即可)
一定要参考AOP https://blog.csdn.net/chengqingshihuishui/article/details/112528712
Step1 新建一个bean2.xml配置文件
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!-- 配置JdbcTemplate对象,注入DataSource -->
<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--step1 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2 配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务-->
<tx:method name="accountMoney" propagation="REQUIRED"/>
<!--<tx:method name="account*"/>-->
</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>
Step2 编写测试文件
package com.atguigu.spring5.test;
public class TestBook{
@Test
public void testAccount(){
ApplicationContext context=new ClassPathXmlApplicationContext("bean2.xml");
UserService userService=context.getBean("userService",UserService.class);
userService.accountMoney();
}
}
5.5 完全注解式声明事务管理
在com.atguigu.spring5下新建一个config文件夹(包com.atguigu.spring5.config),在该文件夹下创建TxConfig类,用于替代xml配置文件(将xml下所有的配置,都用这个配置类来写)
package com.atguigu.spring5.config;
@Configuration
//配置类
@ComponentScan(basePackages = "com.atguigu")
//组件扫描
@EnableTransactionManagement
//开启事务
public class TxConfig {
//创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///user_db");
dataSource.setUsername("root");
dataSource.setPassword("root");
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;
}
}
编写测试类
package com.atguigu.spring5.test;
public class TestBook{
@Test
public void testAccount(){
ApplicationContext context=new AnnotationConfigApplicationContext(TxConfig.class);
UserService userService=context.getBean("userService",UserService.class);
userService.accountMoney();
}
}