Spring事务管理

事务概述

什么事务

逻辑上的一组操作,组成这组操作的各个单元,要么全都成功,要么全都失败。

事务的特性(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);
    }
}

测试同上

posted @ 2019-05-16 09:13  Lomen~  阅读(601)  评论(0编辑  收藏  举报