事务概述
什么事务
逻辑上的一组操作,组成这组操作的各个单元,要么全都成功,要么全都失败。
事务的特性(ACID)
- 原子性:事务不可分割
- 一致性:事务执行前后数据完整性保持一致
- 隔离性:一个事务的执行不应该受到其他事务的干扰
- 持久性:一旦事务结束,数据就持久化到数据库
如果不考虑隔离性引发安全性问题
读问题
- 脏读 :一个事务读到另一个事务未提交的数据
- 不可重复读 :一个事务读到另一个事务已经提交的 update 的数据,导致一个事务中多次查询结果不一致
- 虚读、幻读 :一个事务读到另一个事务已经提交的 insert 的数据,导致一个事务中多次查询结果不一致。
写问题
- 丢失更新
解决读问题
设置事务的隔离级别
- Read uncommitted :未提交读,任何读问题解决不了。
- Read committed :已提交读,解决脏读,但是不可重复读和虚读有可能发生。
- Repeatable read :重复读,解决脏读和不可重复读,但是虚读有可能发生。
- Serializable :解决所有读问题。
思维导图总结
Spring的事务管理的API
PlatformTransactionManager
- PlatformTransactionManage
平台事务管理器 是一个接口,下面有两个实现类 - DataSourceTransactionManager
底层使用JDBC管理事务 - HibernateTransactionManager
底层使用Hibernate管理事务
TransactionDefinition
事务定义信息:用于定义事务的相关的信息,隔离级别、超时信息、传播行为、是否只读
TransactionStatus
事务状态:用于记录在事务管理过程中,事务的状态的对象。
事务管理的API的关系
- Spring进行事务管理的时候,首先平台事务管理器根据事务定义信息进行事务的管理,
- 在事务管理过程中,产生各种状态,将这些状态的信息记录到事务状态的对象中。
思维导图总结
Spring的事务的传播行为
什么是传播行为
一个业务方法当中,调用另一个业务的方法
Spring中提供了七种事务的传播行为
保证多个操作在同一个事务中
- PROPAGATION_REQUIRED
默认值,如果A中有事务,使用A中的事务,如果A没有,创建一个新的事务,将操作包含进来 - PROPAGATION_SUPPORTS
支持事务,如果A中有事务,使用A中的事务。如果A没有事务,不使用事务。 - PROPAGATION_MANDATORY
如果A中有事务,使用A中的事务。如果A没有事务,抛出异常。
保证多个操作不在同一个事务中
- PROPAGATION_REQUIRES_NEW
如果A中有事务,将A的事务挂起(暂停),创建新事务,只包含自身操作。如果A中没有事务,创建一个新事务,包含自身操作。 - PROPAGATION_NOT_SUPPORTED
如果A中有事务,将A的事务挂起。不使用事务管理。 - PROPAGATION_NEVER
如果A中有事务,报异常。
嵌套式事务
- PROPAGATION_NESTED
- 嵌套事务,如果A中有事务,按照A的事务执行,执行完成后,设置一个保存点
- 执行B中的操作,如果没有异常,执行通过,如果有异常,可以选择回滚到最初始位置,也可以回滚到保存点
思维导图总结
Spring事务管理
一、搭建Spring事务管理环境
1.创建AoccuntDao
public interface AccountDao {
public void addMoney(String name, Double money);
public void minusMoney(String name, Double money);
}
2.实现Dao接口
public class AccountDaoImpl implements AccountDao {
@Override
public void addMoney(String name, Double money) {
}
@Override
public void minusMoney(String name, Double money) {
}
}
3.把Dao交给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: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:property-placeholder location="classpath:jdbc.properties"/>
<!--druid-->
<bean id="druid" class="com.alibaba.druid.pool.DruidDataSource">
<!--key值不能和name一样,所以一般加上jdbc前缀-->
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druid"/>
</bean>
<bean id="accountDao" class="com.xzh.spring.demo.AccountDaoImpl"></bean>
</beans>
4.在Dao中注入数据源
(1) 普通注入
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void addMoney(String name, Double money) {
this.getJdbcTemplate().update("update account set money=money+? where name=?",money,name);
}
@Override
public void minusMoney(String name, Double money) {
this.getJdbcTemplate().update("update account set money=money-? where name=?",money,name);
}
}
<?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:property-placeholder location="classpath:jdbc.properties"/>
<!--druid-->
<bean id="druid" class="com.alibaba.druid.pool.DruidDataSource">
<!--key值不能和name一样,所以一般加上jdbc前缀-->
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druid"/>
</bean>
<bean id="accountDao" class="com.xzh.spring.demo.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
</beans>
(2)继承 JdbcDaoSupport
- 在DAO当中注入jdbc模板,要保证dao继承了JdbcDaoSupport,继承之后, 就有了datasource的set方法,就可以注入了
public abstract class JdbcDaoSupport extends DaoSupport {
@Nullable
private JdbcTemplate jdbcTemplate;
public JdbcDaoSupport() {
}
public final void setDataSource(DataSource dataSource) {
if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) {
this.jdbcTemplate = this.createJdbcTemplate(dataSource);
this.initTemplateConfig();
}
}
// 省略后面其他方法
... ...
}
- Dao继承JdbcDaoSupport
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void addMoney(String name, Double money) {
this.getJdbcTemplate().update("update account set money=money+? where name=?",money,name);
}
@Override
public void minusMoney(String name, Double money) {
this.getJdbcTemplate().update("update account set money=money-? where name=?",money,name);
}
}
- DAO注入JDBC模板
<?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:property-placeholder location="classpath:jdbc.properties"/>
<!--druid-->
<bean id="druid" class="com.alibaba.druid.pool.DruidDataSource">
<!--key值不能和name一样,所以一般加上jdbc前缀-->
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druid"/>
</bean>
<bean id="accountDao" class="com.xzh.spring.demo.AccountDaoImpl">
<property name="dataSource" ref="druid"/>
</bean>
</beans>
5.创建Account业务
public interface AccountService {
public void transferMoney(String from,String to,Double money);
}
public class AccountServiceImpl implements AccountService{
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transferMoney(String from, String to, Double money) {
this.accountDao.minusMoney(from,money);
this.accountDao.addMoney(to,money);
}
}
6.配置service 交给spring 并注入dao
<?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:property-placeholder location="classpath:jdbc.properties"/>
<!--druid-->
<bean id="druid" class="com.alibaba.druid.pool.DruidDataSource">
<!--key值不能和name一样,所以一般加上jdbc前缀-->
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druid"/>
</bean>
<bean id="accountDao" class="com.xzh.spring.demo.AccountDaoImpl">
<property name="dataSource" ref="druid"/>
</bean>
<bean id="accountService" class="com.xzh.spring.demo.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
</beans>
7.测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountTest {
@Resource(name = "accountService")
private AccountService accountService;
@Test
public void test(){
accountService.transferMoney("zs","ls",100d);
}
}
8. 遇到的问题
在业务中如果出现异常时,数据会发生错误
修改 AccountServiceImpl 类,手动添加一个异常
public class AccountServiceImpl implements AccountService{
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transferMoney(String from, String to, Double money) {
this.accountDao.minusMoney(from,money);
int i = 1/0;
this.accountDao.addMoney(to,money);
}
}
测试同上
发现一方金额扣了,另一方金额却没有加。
二、添加事务
编程式事务
需要手动编写代码
步骤
(1)配置平台事务管理器
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druid"/>
</bean>
(2)Spring提供了事务管理的模板类
<!--配置事务管理模板-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
(3)在业务层注入事务管理的模板
添加 TransactionTemplate 属性,并提供set方法
配置文件中配置
<bean id="accountService" class="com.xzh.springjdbc.demo2.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="transactionTemplate" ref="transactionTemplate"/>
</bean>
(4)编写事务管理的代码
public class AccountServiceImpl implements AccountService{
private AccountDao accountDao;
private TransactionTemplate transactionTemplate;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
@Override
public void transferMoney(String from, String to, Double money) {
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.minusMoney(from,money);
int i = 1/0;
accountDao.addMoney(to,money);
}
});
}
}
配置文件
<?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:property-placeholder location="classpath:jdbc.properties"/>
<!--druid-->
<bean id="druid" class="com.alibaba.druid.pool.DruidDataSource">
<!--key值不能和name一样,所以一般加上jdbc前缀-->
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druid"/>
</bean>
<bean id="accountDao" class="com.xzh.springjdbc.demo2.AccountDaoImpl">
<property name="dataSource" ref="druid"/>
</bean>
<bean id="accountService" class="com.xzh.springjdbc.demo2.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="transactionTemplate" ref="transactionTemplate"/>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druid"/>
</bean>
<!--配置事务管理模板-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
</beans>
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountTest {
@Resource(name = "accountService")
private AccountService accountService;
@Test
public void test(){
accountService.transferMoney("ls","zs",100d);
}
}
声明式事务
(1)XML方式声明事务管理
1. 引入aop的开发包
2. 配置事务管理器
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druid"/>
</bean>
3. AOP的配置
<!--事务增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--把增强织入对应的方法里-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.xzh.springjdbc.demo2.AccountServiceImpl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
业务层实现类
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transferMoney(String from, String to, Double money) {
accountDao.minusMoney(from, money);
// int i = 1/0;
accountDao.addMoney(to, money);
}
}
总的配置
<?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:property-placeholder location="classpath:jdbc.properties"/>
<!--druid-->
<bean id="druid" class="com.alibaba.druid.pool.DruidDataSource">
<!--key值不能和name一样,所以一般加上jdbc前缀-->
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="accountDao" class="com.xzh.springjdbc.demo2.AccountDaoImpl">
<property name="dataSource" ref="druid"/>
</bean>
<bean id="accountService" class="com.xzh.springjdbc.demo2.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druid"/>
</bean>
<!--事务增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--把增强织入对应的方法里-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.xzh.springjdbc.demo2.AccountServiceImpl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountTest {
@Resource(name = "accountService")
private AccountService accountService;
@Test
public void test(){
accountService.transferMoney("ls","zs",100d);
}
}
(2)注解方式声明事务管理
1. 配置事务管理器
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druid"/>
</bean>
2. 开启注解事务
<!--开启注解 事务增强-->
<tx:annotation-driven transaction-manager="transactionManager"/>
3. 在业务层添加注解
@Transactional
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transferMoney(String from, String to, Double money) {
accountDao.minusMoney(from, money);
// int i = 1/0;
accountDao.addMoney(to, money);
}
}
测试同上