一. 事务
1. 什么事务?
1.1. 事务: 逻辑上的一组操作, 组成这组操作的各个单元, 要么全都成功, 要么全都失败。
2. 事务的特性
2.1. 原子性: 事务不可分割。
2.2. 一致性: 事务执行前后数据完整性保持一致。
2.3. 隔离性: 一个事务的执行不应该受到其他事务的干扰。
2.4. 持久性: 一旦事务结束, 数据就持久化到数据库。
3. 如果不考虑隔离性引发安全性问题
3.1. 读问题
3.1.1. 脏读: 一个事务读到另一个事务未提交的数据。
3.1.2. 不可重复读: 一个事务读到另一个事务已经提交的update的数据, 导致一个事务中多次查询结果不一致。
3.1.3. 虚读、幻读: 一个事务读到另一个事务已经提交的insert的数据, 导致一个事务中多次查询结果不一致。
3.2. 写问题
3.2.1. 丢失更新。
4. 设置事务的隔离级别解决读问题
4.1. Read uncommitted: 未提交读, 任何读问题解决不了。
4.2. Read committed: 已提交读, 解决脏读, 但是不可重复读和虚读有可能发生。
4.3. Repeatable read: 重复读, 解决脏读、不可重复读和幻读(MySQL8中)。
4.4. Serializable: 解决所有读问题, 同时似乎给整张表添加了一个锁, 客户并发读, 但不能并发写。
二. Spring的事务管理的API
1. PlatformTransactionManager平台事务管理器接口
1.1. DataSourceTransactionManager: 底层使用JDBC管理事务。
1.2. HibernateTransactionManager: 底层使用Hibernate管理事务。
2. TransactionDefinition事务定义信息
2.1. 事务定义: 用于定义事务的相关的信息, 隔离级别、超时信息、传播行为、是否只读(只读的时候不能写)。
3. TransactionStatus事务的状态
3.1. 事务状态: 用于记录在事务管理过程中, 事务的状态的对象。
4. 事务管理的API的关系
4.1. Spring进行事务管理的时候, 首先平台事务管理器根据事务定义信息进行事务的管理, 在事务管理过程中, 产生各种状态, 将这些状态的信息记录到事务状态的对象中。
5. Spring中提供了七种事务的传播行为:
5.1. 保证多个操作在同一个事务中
5.1.1. PROPAGATION_REQUIRED: 默认值, 如果A中有事务, 使用A中的事务, 如果A没有, 创建一个新的事务, 将操作包含进来。
5.1.2. PROPAGATION_SUPPORTS: 支持事务, 如果A中有事务, 使用A中的事务。如果A没有事务, 不使用事务。
5.1.3. PROPAGATION_MANDATORY: 如果A中有事务, 使用A中的事务。如果A没有事务, 抛出异常。
5.2. 保证多个操作不在同一个事务中
5.2.1. PROPAGATION_REQUIRES_NEW: 如果A中有事务, 将A的事务挂起(暂停), 创建新事务, 只包含自身操作。如果A中没有事务, 创建一个新事务, 包含自身操作。
5.2.2. PROPAGATION_NOT_SUPPORTED: 如果A中有事务, 将A的事务挂起。不使用事务管理。
5.2.3. PROPAGATION_NEVER: 如果A中有事务, 报异常。
5.3. 嵌套式事务
5.3.1. PROPAGATION_NESTED: 嵌套事务, 如果A中有事务, 按照A的事务执行, 执行完成后,设置一个保存点, 执行B中的操作, 如果没有异常, 执行通过, 如果有异常, 可以选择回滚到最初始位置, 也可以回滚到保存点。
三. 编程式事务
1. 配置平台事务管理器
2. Spring提供了事务管理的模板类
3. 在业务层注入事务管理的模板
4. 编写事务管理的代码
5. 例子
5.1. 新建一个名为SpringJdbcProgramTx的Java工程, 拷入相关jar包
5.2. 新建一个Account.java
package com.lywgames.tx;
import java.io.Serializable;
public class Account implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account [id=" + id + ", name=" + name + ", money=" + money + "]";
}
}
5.3. 新建一个AccountDao.java数据库操作接口
package com.lywgames.tx;
/**
* 转账的Dao的接口
*/
public interface AccountDao {
public void outMoney(String from, Float money);
public void inMoney(String to, Float money);
}
5.4. 新建一个AccountDaoImpl.java数据库操作实现类
package com.lywgames.tx;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
/**
* 转账的Dao的实现类
*/
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void outMoney(String from, Float money) {
getJdbcTemplate().update("update account set money = money - ? where name = ?", money, from);
}
@Override
public void inMoney(String to, Float money) {
getJdbcTemplate().update("update account set money = money + ? where name = ?", money, to);
}
}
5.5. 新建一个AccountService.java业务接口
package com.lywgames.tx;
/**
* 转账的业务层的接口
*/
public interface AccountService {
public void transfer(String from, String to, Float money);
}
5.6. 新建一个AccountServiceImpl.java业务实现类
package com.lywgames.tx;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
/**
* 转账的业务层的实现类
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
/**
* from: 转出账号
* to: 转入账号
* money: 转账金额
*/
@Override
public void transfer(String from, String to, Float money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.outMoney(from, money);
int d = 1/0;
accountDao.inMoney(to, money);
}
});
}
}
5.7. 新建一个Test.java测试类
package com.lywgames.tx;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = context.getBean(AccountService.class);
accountService.transfer("王五", "李四", 100.00F);
context.close();
}
}
5.8. 在src目录下创建jdbc.properties数据库连接配置
5.9. 在src目录下创建applicationContext.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"
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">
<!-- 通过context标签引入属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- 配置Spring内置连接池 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 配置平台事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事务管理的模板 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
<!-- Dao继承了JdbcDaoSupport, 可以不配置jdbcTemplate模板bean, 在注入dataSource属性时, 会自动创建jdbcTemplate。 -->
<bean id="accountDao" class="com.lywgames.tx.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="accountService" class="com.lywgames.tx.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<!-- 注入事务管理的模板 -->
<property name="transactionTemplate" ref="transactionTemplate" />
</bean>
</beans>
5.10. 查看account表
5.11. 运行项目, 抛出异常, 查看数据库结果, 转载失败, 每个人的钱数不变
5.12. 注释掉异常, 运行项目, 查看数据库结果, 转账成功
四. 声明式事务管理(XML方式的声明式事务管理)
1. 配置平台事务管理器
2. 配置增强
3. AOP的配置
4. 例子
4.1. 新建一个名为SpringJdbcXmlTx的Java工程, 拷入相关jar包
4.2. 新建一个Account.java
package com.lywgames.tx;
import java.io.Serializable;
public class Account implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account [id=" + id + ", name=" + name + ", money=" + money + "]";
}
}
4.3. 新建一个AccountDao.java数据库操作接口
package com.lywgames.tx;
/**
* 转账的Dao的接口
*/
public interface AccountDao {
public void outMoney(String from, Float money);
public void inMoney(String to, Float money);
}
4.4. 新建一个AccountDaoImpl.java数据库操作实现类
package com.lywgames.tx;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
/**
* 转账的Dao的实现类
*/
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void outMoney(String from, Float money) {
getJdbcTemplate().update("update account set money = money - ? where name = ?", money, from);
}
@Override
public void inMoney(String to, Float money) {
getJdbcTemplate().update("update account set money = money + ? where name = ?", money, to);
}
}
4.5. 新建一个AccountService.java业务接口
package com.lywgames.tx;
/**
* 转账的业务层的接口
*/
public interface AccountService {
public void transfer(String from, String to, Float money);
}
4.6. 新建一个AccountServiceImpl.java业务实现类
package com.lywgames.tx;
/**
* 转账的业务层的实现类
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
/**
* from: 转出账号
* to: 转入账号
* money: 转账金额
*/
@Override
public void transfer(String from, String to, Float money) {
accountDao.outMoney(from, money);
int d = 1/0;
accountDao.inMoney(to, money);
}
}
4.7. 新建一个Test.java测试类
package com.lywgames.tx;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = context.getBean(AccountService.class);
accountService.transfer("王五", "李四", 100.00F);
context.close();
}
}
4.8. 在src目录下创建jdbc.properties数据库连接配置
4.9. 在src目录下创建applicationContext.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/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">
<!-- 通过context标签引入属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- 配置Spring内置连接池 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- Dao继承了JdbcDaoSupport, 可以不配置jdbcTemplate模板bean, 在注入dataSource属性时, 会自动创建jdbcTemplate。 -->
<bean id="accountDao" class="com.lywgames.tx.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="accountService" class="com.lywgames.tx.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置事务的增强/通知, 类似前置通知、后置通知等。 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- name="*" 表示所有方法 -->
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
</tx:attributes>
</tx:advice>
<!-- aop的配置 -->
<aop:config>
<aop:pointcut expression="execution(* com.lywgames.tx.AccountServiceImpl.*(..))" id="pointcut"/>
<!-- aop:advisor和aop:aspect都是切面。aop:advisor多个切入点和多个通知的组合。aop:aspect一个切入点和一个通知的组合。 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
</beans>
4.10. 查看account表
4.11. 运行项目, 抛出异常, 查看数据库结果, 转载失败, 每个人的钱数不变
4.12. 注释掉异常, 运行项目, 查看数据库结果, 转账成功
五. 声明式事务管理(注解方式的声明式事务管理)
1. 配置平台事务管理器
2. 开启注解事务
3. 在业务层添加注解
4. 例子
4.1. 新建一个名为SpringJdbcAnnotationTx的Java工程, 拷入相关jar包
4.2. 新建一个Account.java
package com.lywgames.tx;
import java.io.Serializable;
public class Account implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account [id=" + id + ", name=" + name + ", money=" + money + "]";
}
}
4.3. 新建一个AccountDao.java数据库操作接口
package com.lywgames.tx;
/**
* 转账的Dao的接口
*/
public interface AccountDao {
public void outMoney(String from, Float money);
public void inMoney(String to, Float money);
}
4.4. 新建一个AccountDaoImpl.java数据库操作实现类
package com.lywgames.tx;
import javax.annotation.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
/**
* 转账的Dao的实现类
*/
@Component(value="accountDao")
public class AccountDaoImpl implements AccountDao {
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
@Override
public void outMoney(String from, Float money) {
jdbcTemplate.update("update account set money = money - ? where name = ?", money, from);
}
@Override
public void inMoney(String to, Float money) {
jdbcTemplate.update("update account set money = money + ? where name = ?", money, to);
}
}
4.5. 新建一个AccountService.java业务接口
package com.lywgames.tx;
/**
* 转账的业务层的接口
*/
public interface AccountService {
public void transfer(String from, String to, Float money);
}
4.6. 新建一个AccountServiceImpl.java业务实现类
package com.lywgames.tx;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* 转账的业务层的实现类
*/
@Transactional
@Component(value="accountService")
public class AccountServiceImpl implements AccountService {
@Resource(name="accountDao")
private AccountDao accountDao;
/**
* from: 转出账号
* to: 转入账号
* money: 转账金额
*/
@Override
public void transfer(String from, String to, Float money) {
accountDao.outMoney(from, money);
int d = 1/0;
accountDao.inMoney(to, money);
}
}
4.7. 新建一个Test.java测试类
package com.lywgames.tx;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = context.getBean(AccountService.class);
accountService.transfer("王五", "李四", 100.00F);
context.close();
}
}
4.8. 在src目录下创建jdbc.properties数据库连接配置
4.9. 在src目录下创建applicationContext.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/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">
<!-- 使用IOC的注解开发, 配置组件扫描, 哪些包下的类使用了IOC的注解 -->
<context:component-scan base-package="com.lywgames"></context:component-scan>
<!-- 通过context标签引入属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- 配置Spring内置连接池 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 配置Spring的Jdbc模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
4.10. 查看account表
4.11. 运行项目, 抛出异常, 查看数据库结果, 转载失败, 每个人的钱数不变
4.12. 注释掉异常, 运行项目, 查看数据库结果, 转账成功