事务概念
1、什么是事务
(1)事务是数据库操作最基本单元,逻辑上的一组操作,要么都成功,如果有一个失败所有操作都失败。
(2)典型场景:银行转账。
2、事务四个特性(ACID)
(1)原子性
(2)一致性
(3)隔离性
(4)持久性
搭建事务操作环境
1、创建数据库表,添加记录
2、创建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"
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.spring5"/>
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="${prop.driverClass}"/>
<property name="url" value="${prop.url}"/>
<property name="username" value="${prop.username}"/>
<property name="password" value="${prop.password}"/>
</bean>
<!--JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
3、创建 dao,完成对象创建和注入关系
package com.spring5.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 模拟lucy给mary转账
*/
@Override
public void addMoney(int id, float money) {
String sql = "update account set money=money+? where id=?";
jdbcTemplate.update(sql, money, id);
}
@Override
public void reduceMoney(int id, float money) {
String sql = "update account set money=money-? where id=?";
jdbcTemplate.update(sql, money, id);
}
}
4、创建service,完成对象创建和注入关系
package com.spring5.service;
import com.spring5.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountService {
@Autowired
private AccountDao accountDao;
/**
* 转账方法,lucy的id为1,mary的id值为2
*/
public void transferAccount() {
accountDao.reduceMoney(1, 100);
accountDao.addMoney(2, 100);
}
}
5、编写测试方法
package com.spring5.test;
import com.spring5.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAccount {
@Test
public void testAccount() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
AccountService accountService = context.getBean("accountService", AccountService.class);
accountService.transferAccount();
}
}
运行结果:控制台+数据库
6、上面代码,如果正常执行没有问题的,但是如果代码执行过程中出现异常,有问题
若更改如下:则转账会有问题!
public void transferAccount() {
accountDao.reduceMoney(1, 100);
//模拟异常
int i = 10 / 0;
accountDao.addMoney(2, 100);
}
1、上面问题如何解决?
使用事务进行解决
2、事务操作过程
public void transferAccount() { try { //1、开启事务 //2、进行业务操作 accountDao.reduceMoney(1, 100); //模拟异常 int i = 10 / 0; accountDao.addMoney(2, 100); //3、若未发生异常,提交事务 } catch (Exception e) { //4、出现异常,事务回滚 } }
Spring事务管理
1、事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)
2、在 Spring 进行事务管理操作
有两种方式:编程式事务管理和声明式事务管理(使用)
3、声明式事务管理
(1)基于注解方式(使用)
(2)基于 xml 配置文件方式
4、在 Spring 进行声明式事务管理,底层使用 AOP 原理
5、Spring 事务管理 API
提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类。
注解声明式事务管理
1、在 spring 配置文件配置事务管理器
<!--创建事务管理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</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: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">
(2)开启事务注解
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>
3、在 service 类上面(或者 service 类里面方法上面)添加事务注解
(1)@Transactional,这个注解添加到类上面,也可以添加方法上面
(2)如果把这个注解添加类上面,这个类里面所有的方法都添加事务
(3)如果把这个注解添加方法上面,为这个方法添加事务
package com.spring5.service;
import com.spring5.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class AccountService {
@Autowired
private AccountDao accountDao;
/**
* 转账方法,lucy的id为1,mary的id值为2
*/
public void transferAccount() {
//try {
// //1、开启事务
//
//2、进行业务操作
accountDao.reduceMoney(1, 100);
//模拟异常
int i = 10 / 0;
accountDao.addMoney(2, 100);
//
// //3、若未发生异常,提交事务
//} catch (Exception e) {
// //4、出现异常,事务回滚
//}
}
}
4、测试
此时,数据库数据保持一致,转账不会出错!
声明式事务管理参数配置
1、在 service 类上面添加注解@Transactional,在这个注解里面可以配置事务相关参数
2、propagation:事务传播行为
@Transactional(propagation = Propagation.REQUIRED)
多事务方法之间进行调用,这个过程中事务是如何进行管理的
默认
REQUIRED
3、isolation:事务隔离级别
@Transactional(isolation = Isolation.REPEATABLE_READ)
(1)事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
(2)有三个读问题:脏读、不可重复读、虚读 (幻读)
(3)脏读:一个未提交事务读取到另一个未提交事务的数据
(4)不可重复读:一个未提交事务读取到另一提交事务修改数据
(5)虚读:一个未提交事务读取到另一提交事务添加数据
(6)解决:通过设置事务隔离级别,解决读问题
在MySQL中默认是
REPEATABLE_READ
4、timeout:超时时间
@Transactional(timeout = -1)
(1)事务需要在一定时间内进行提交,如果不提交进行回滚
(2)默认值是 -1 ,设置时间以秒单位进行计算
5、readOnly:是否只读
@Transactional(readOnly = false)
(1)读:查询操作,写:添加修改删除操作
(2)readOnly 默认值 false,表示可以查询,可以添加修改删除操作
(3)设置 readOnly 值是 true,设置成 true 之后,只能查询
6、rollbackFor:回滚
设置出现哪些异常进行事务回滚
7、noRollbackFor:不回滚
设置出现哪些异常不进行事务回滚
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: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/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.spring5"/>
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="${prop.driverClass}"/>
<property name="url" value="${prop.url}"/>
<property name="username" value="${prop.username}"/>
<property name="password" value="${prop.password}"/>
</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="transferAccount" propagation="REQUIRED"/>
<!--<tx:method name="transfer*"/>-->
</tx:attributes>
</tx:advice>
<!--3、配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.spring5.service.AccountService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
</beans>
测试
先注释掉AccountService
类上的@Transactional
注解,在测试类中新增测试方法,如下
@Test
public void testAccount2() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
AccountService accountService = context.getBean("accountService", AccountService.class);
accountService.transferAccount();
}
运行结果:数据库数据也正常
完全注解声明式事务管理
创建配置类TxConfig.class
,使用配置类替代 xml 配置文件
package com.spring5.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.ResourceBundle;
@Configuration // 配置类
@ComponentScan(basePackages = "com.spring5") // 组件扫描
@EnableTransactionManagement // 开启事务
public class TxConfig {
// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource datasource = new DruidDataSource();
ResourceBundle bundle = ResourceBundle.getBundle("jdbc"); // 属性配置文件扩展名不写
datasource.setDriverClassName(bundle.getString("prop.driverClass"));
datasource.setUrl(bundle.getString("prop.url"));
datasource.setUsername(bundle.getString("prop.username"));
datasource.setPassword(bundle.getString("prop.password"));
return datasource;
}
// 创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
// 到ioc容器中根据类型找到dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 创建事务管理器
@Bean
public DataSourceTransactionManager getTransactionManager(DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
测试方法:
@Test
public void testAccount3() {
ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
AccountService accountService = context.getBean("accountService", AccountService.class);
accountService.transferAccount();
}
记得恢复
AccountService
类上的@Transactional
注解!然后再执行测试方法,否则数据库数据会变!