Spring:事务管理

2、事务管理

1、事务的隔离级别?

1、事务概念

事务就是由N步数据库操作序列组成的逻辑执行单元,这系列操作要么全部执行(事务提交)要么全部放弃(事务回滚)。

eg:在一个业务里操作数据库中的数据有多个步骤,如果中间有一步没有操作成功,就要将这个事务回滚,不改变数据库中的数据,如果这多步操作都执行成功了,就将事务提交,就是说这多个步骤是绑定在一起的,要么都成功,要么都失败。

eg:转账操作,小明给小红转1000元,小红收到1000元,这两个操作要绑定在一起,要么一起成功,要么一起失败。

2、事务的四大特性

任何支持事务的数据库,都必须具备四个特性,分别是:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),也就是我们常说的事务ACID,这样才能保证事务((Transaction)中数据的正确性。

1. 原子性(Atomicity):事务开始执行,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。

比如小明向小红转账,不可能小明扣了钱,小红却没收到。

2. 一致性(Consistency):事务执行前后数据的完整性应该保持一致。

小明和用户小红的钱加起来一共是2000,那么不管小明和小红之间如何转账,转几次账,事务结束后两个他俩的钱相加起来应该还得是2000,这就是事务的一致性。

3. 隔离性(Isolation):隔离性是当多个用户并发访问数据库时,一个用户的事务不跟被其他用户的事务所干扰,多个并发事务之间数据要相互隔离。

比如小明在转账时,小红不能取钱,两个事务之间要相互隔离

4. 持久性(Durability):一个事务一旦被提交,他对数据库中的数据的改变是永久性的,机试数据库发生故障也不应该对其有任何影响。

3、事务的隔离级别

事务的隔离性就是指,多个并发的事务同时访问一个数据库时,一个事务不能被另一个事务所干扰,每个并发的事务间要相互进行隔离。而事务的隔离级别就是为了解决并发事务导致的问题。

1. 如果不考虑隔离性产生的问题:

服务器程序本身就是多线程的环境,每一个浏览器(用户)访问服务器的时候,服务器都会创建一个新的线程来处理用户的请求,在这次请求的过程中,如果需要访问数据库就会有事务的操作,也就是说服务器时多线程的环境多事务并发的场景,如果多个用户同时访问同一个网站的一个功能,同时访问数据库中的同一条数据,多个事务同时访问同一个事务的情况就会出现,如果不做事务的隔离性处理,就可能会出现一些问题:

更新问题:第一类丢失更新、第二类丢失更新

读取问题:脏读、不可重复读、幻读

1、第一类丢失更新:某一个事务的回滚,导致另一个事务已更新的数据丢失了。(回滚丢失)

2、第二类丢失更新:某一个事务的提交,导致另一个事务已更新的数据丢失了。(提交丢失)

在这里插入图片描述

3、脏读:某一个事务,读取了另一个事务未提交的数据。

在这里插入图片描述

4、不可重复读:某一个事务,对同一个数据前后读取的结果不一致。(针对一行数据,要锁住行)

在这里插入图片描述

5、幻读:某一个事务,对同一个表前后查询到的行数不一致。(针对表,要锁住整张表)

在这里插入图片描述

不可重复读和幻读不要搞混:不可重复读针对一行数据的修改,幻读针对于向一张表中插入一条数据或删除一条数据,解决不可重复读需要锁住当前数据所在的行,解决幻读需要锁住整张表。

2. 设置隔离级别解决上述问题:
在这里插入图片描述

Read Uncommitted(读未提交):一个事务可以读取另一个事务未提交的数据,安全级别最低,问题都会产生

Read Committed(读已提交):一个事务可以读取另一个事务已提交的数据,可以解决第一类丢失更新和脏读

Repeatable Read(可重复读):事务开启后,其他事务不能对数据再进行修改(行锁),直到事务结束。

Serializable(序列化):解决所有问题。离级别最高,可以解决所有的问题,但是他解决问题是有代价的,需要对数据表加锁,加锁会降低数据处理的性能,效率最低。(银行,金融的业务可以选择)

一般我们都用读已提交和可重复读,如果对安全性要求比较高就是用可重复读,否则使用读已提交,具体情况根据业务问题而定。

2、Spring 事务的传播行为?

1、接口介绍

Spring事务管理高层抽象接口主要包括3个:

1、事务管理器(事务的开启、提交、回滚):PlatformTransactionManager

public interface PlatformTransactionManager {
	TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
	void commit(TransactionStatus status) throws TransactionException;
	void rollback(TransactionStatus status) throws TransactionException;
}

2、事务定义信息(隔离、传播、超时、只读):TransactionDefinition

public interface TransactionDefinition {
    //事务的传播特性:
	int PROPAGATION_REQUIRED = 0;
	int PROPAGATION_SUPPORTS = 1;
	int PROPAGATION_MANDATORY = 2;
	int PROPAGATION_REQUIRES_NEW = 3;
	int PROPAGATION_NOT_SUPPORTED = 4;
	int PROPAGATION_NEVER = 5;
	int PROPAGATION_NESTED = 6;
	
    //事务的隔离级别:
	int ISOLATION_DEFAULT = -1;
	int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
	int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
	int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
	int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
	
    //超时:
    int TIMEOUT_DEFAULT = -1;
    
    //操作属性的方法:
	int getPropagationBehavior();
	int getIsolationLevel();
	int getTimeout();
	boolean isReadOnly();
	String getName();
}

3、事务具体运行状态(事务是否已经提交、是否有保存点等):TransactionStatus

public interface TransactionStatus extends SavepointManager, Flushable {
	//是否是新事务
	boolean isNewTransaction();
	//是否设置保存点
	boolean hasSavepoint();
	//只读
	void setRollbackOnly();
	//是否已回滚
	boolean isRollbackOnly();

	@Override
	void flush();
	//是否已提交
	boolean isCompleted();
}

2、事务的传播特性

/**
 * 两个方法都加了 @Transactional,相互调用,那么他们之间的事务应该如何处理?
 *
 * 以被调用者的方法为中心:beidiaoyongzhe()方法
 * 1、propagation_required:
 *      如果调用我的方法在事务中执行,那我也在事务中执行
 *      如果调用我的方法没有事务,那我就新建一个事务
 *
 * 2、propagation_supports:
 *      如果调用我的方法在事务中执行,那我也在事务中执行。
 *      如果调用我的方法没有在事务中执行,那我也不用事务。
 *
 * 3、propagation_mandatory:
 *      调用我的方法必须在事务中执行,没有就抛出异常。
 *
 * 4、propagation_requierd_new:
 *      不管调用我的方法有没有在事务中执行,我都要新建一个事务,我要使用我自己的。
 *      如果调用我的方法有事务,那我就将它挂起,不使用。
 *
 * 5、propagation_not_supported:
 *      不管调用我的方法有没有在事务中执行,我都不用事务。
 *      如果调用我的方法有事务,我也要将它挂起
 *
 * 6、propagation_nerver:
 *      调用我的方法不能有事务,我自己也不能有事务。
 *      如果调用我的方法有事务,则抛出异常。
 *
 * 7、propagation_insert:
 *      如果调用我的方法有事务,则在嵌套事务内执行。
 *      如果调用我的方法没有事务,则执行与propagation_required类似的操作。
 */
@Service
public class PersonService {
    /**
     * 如果这个方法中出现了异常,回滚,那 beidiaoyongzhe() 方法需不需要回滚?
     */
    @Transactional
    public void diaoyongzhe(){
        System.out.println("我是调用者....");
        beidiaoyongzhe();
        //出现异常....
    }

    /**
     * 如果这个方法中出现了异常,回滚,那 diaoyongzhe() 方法需不需要回滚?
     */
    @Transactional
    public void beidiaoyongzhe(){
        System.out.println("我是被调用者....");
        //出现异常....
    }
}

总结:

1、被调用方死活不用事务的:

propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。

propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

2、被调用方可用可不用事务的:

propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。

3、被调用方必须用事务的:

propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。

propagation_required:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是Spring默认的选择。

propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。

propagation_inserted:如果当前存在事务,则在嵌套事务内执行。如果没有,就新建一个。

3、Spring业务层不使用事务

1、数据库建表:

create table account(
	id int(11) not null auto_increment,
	name varchar(20) not null,
	money double  default null,
	primary key(id)
)engine=InnoDB auto_increment=4 default charset=utf8;

在这里插入图片描述
2、Dao层操作数据库:

Dao层接口

public interface AccountDao {
    /**
     * @param out 转出账号
     * @param money 转多少钱
     */
    public void outMoney(String out,Double money);

    /**
     * @param in 转入账号
     * @param money 转多少钱
     */
    public void inMoney(String in,Double money);
}

Dao层实现类:使用JDBCTemplate操作数据库

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao  {

    @Override
    public void outMoney(String out, Double money) {
        String sql = "update account set money = money-? where name = ?";
        this.getJdbcTemplate().update(sql, money, out);
    }

    @Override
    public void inMoney(String in, Double money) {
        String sql = "update account set money = money+? where name = ?";
        this.getJdbcTemplate().update(sql,money,in);
    }

}

3、Service层实现转账:

Service层接口:

public interface AccountService {
    /**
     * @param out 转出账户
     * @param in 转入账户
     * @param money 转多少钱
     */
    public void transfer(String out,String in,Double money);
}

Service层实现类:实现转账操作

public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    public void setAccountDao(AccountDaoImpl accountDao) {
        this.accountDao = accountDao;
    }

    //转账操作
    @Override
    public void transfer(String out, String in, Double money) {
        accountDao.outMoney(out,money);
        //中间出现异常
        int i=1/0;
        accountDao.inMoney(in,money);
    }
}

4、xml配置文件与数据源配置文件:

db.driverClass=com.mysql.jdbc.Driver
db.url = jdbc:mysql:///spring_transaction
db.username = root
db.password = root
<?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-4.3.xsd">

   <!--引入外部属性文件-->
   <context:property-placeholder location="classpath:dbconfig.properties"/>

   <!--配置C3p0的连接池-->
   <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
      <!--从dbconfig.properties配置文件中取值-->
      <property name="driverClass" value="${db.driverClass}"/>
      <property name="jdbcUrl" value="${db.url}"/>
      <property name="user" value="${db.username}"/>
      <property name="password" value="${db.password}"/>
   </bean>
   
   <!-- 注册dao类,依赖DataSource-->
   <bean id="accountDao" class="com.hh.dao.AccountDaoImpl">
      <property name="dataSource" ref="dataSource"/>
   </bean>

   <!-- 注册Service类,依赖AccountDao-->
   <bean id="accountService" class="com.hh.service.AccountServiceImpl">
      <property name="accountDao" ref="accountDao" />
   </bean>
</beans>

5、测试转账结果:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:beans.xml")
public class TransactionTest {

    @Resource(name="accountService")
    private AccountService accountService;

    @Test
    public void demo1(){
        accountService.transfer("aaa", "bbb", 200d);
    }
}

数据库中数据:结果转出账户将钱转出去了,但是转入账户却没有收到钱,这种问题很严重的,因此必须使用事务管理,对于业务层如果有多个操作数据库的指令,必须加事务,没得商量。

在这里插入图片描述

4、Spring编程式事务

1、在xml中配置以下依赖关系:

​ 在AccountService中使用事务模板TransactionTemplate(简化事务管理而封装的类)

​ TransactionTemplate依赖DataSourceTransactionManager(PlatformTransactionManager的实现类)

​ 事务管理器DataSourceTransactionManager依赖DataSource

	<!-- 配置dao类-->
	<bean id="accountDao" class="com.hh.dao.AccountDaoImpl">
		<property name="dataSource" ref="dataSource"/>
	</bean>

	<!--配置事务管理器DataSourceTransactionManager,依赖DataSource-->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>

	<!--配置事务模板TransactionTemplate,依赖事务管理器DataSourceTransactionManager-->
	<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
		<property name="transactionManager" ref="transactionManager"/>
	</bean>

	<!-- 配置Service层,依赖事务模板TransactionTemplate-->
	<bean id="accountService" class="com.hh.service.AccountServiceImpl">
		<property name="accountDao" ref="accountDao" />
		<property name="transactionTemplate" ref="transactionTemplate"/>
	</bean>

2、在Service层对操作数据库的方法加上事务:

public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    //注入事务模板
    @Autowired
    private TransactionTemplate transactionTemplate;

    public void setAccountDao(AccountDaoImpl accountDao) {
        this.accountDao = accountDao;
    }

    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }

    //转账操作
    @Override
    public void transfer(String out, String in, Double money) {
        /**
         * TransactionTemplate已经搭好了事务框架,
         * 我们只需将多条指令放在doInTransaction中执行就可以了
         *
         * TransactionTempale采用和其他Spring模板,如JdbcTempalte和HibernateTemplate一样的方法。
         * 它使用回调方法,把应用程序从处理取得和释放资源中解脱出来。
         * 如同其他模板,TransactionTemplate是线程安全的
         *
         * 使用TransactionCallback()可以返回一个值。
         * 使用TransactionCallbackWithoutResult则没有返回值。
         */
        transactionTemplate.execute(new TransactionCallback<Object>() {
            @Override
            public Object doInTransaction(TransactionStatus status) {
                accountDao.outMoney(out,money);
                int i=1/0;
                accountDao.inMoney(in,money);
                return "成功";
            }
        });
    }
}

3、结果:加上事务后,方法出现异常,转账失败:
在这里插入图片描述

5、Spring声明式事务

编程式事务管理需要手动的在Service层改代码,因此建议使用声明式事务管理

1、基于TransactionProxyFactoryBean的方式

使用TransactionProxyFactoryBean来配置事务代理Bean,它是一个专门为目标Bean生成事务代理的工厂Bean。既然TransactionProxyFactoryBean产生的是事务代理Bean,可见Spring的声明式事务策略是基于Spring AOP的。

每个TransactionProxyFactoryBean为一个目标Bean生成一个事务代理Bean,事务代理的方法改写了目标Bean的方法,就是在目标Bean的方法执行之前加入开始事务,在目标Bean的方法正常结束之前提交事务,如果遇到特定异常则回滚。

1、配置XMl文件:

<!--配置事务管理器DataSourceTransactionManager,依赖DataSource-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
   <property name="dataSource" ref="dataSource"/>
</bean>

<!--配置业务层的代理-->
<bean id="transactionProxyFactoryBean"
     class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
   <!--注入目标对象-->
   <property name="target" ref="accountService"/>
   <!--注入事务管理器-->
   <property name="transactionManager" ref="transactionManager"/>
   <!--注入事务属性-->
   <property name="transactionAttributes">
      <props>
         <!--
            key为方法名,value为值
            PROPAGATION:事务传播行为
            ISOLATION:事务的隔离级别
            readonly:只读(不可以增删改)
            -Exception:发生哪些异常回滚事务
            +Exception:发生哪些异常不回滚事务
         -->
         <prop key="transfer">PROPAGATION_REQUIRED</prop>
      </props>
   </property>
</bean>

2、使用代理类后我们的AccountService就不需要改动了,只需要该测试类即可:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:beans1.xml")
public class TransactionTest1 {

    //注入AccountService的代理类
    @Resource(name="transactionProxyFactoryBean")
    private AccountService accountService;

    @Test
    public void demo1(){
        accountService.transfer("aaa", "bbb", 200d);
    }
}

3、结果:转账失败

在这里插入图片描述

2、基于Aspetj的xml方式

1、xml的配置:

<!--配置事务管理器DataSourceTransactionManager,依赖DataSource-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
   <property name="dataSource" ref="dataSource"/>
</bean>

<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
   <tx:attributes>
      <!--为哪个方法增强-->
      <tx:method name="transfer" propagation="REQUIRED"/>
   </tx:attributes>
</tx:advice>

<!--配置切面-->
<aop:config>
   <!--配置切入点,AccountService实现类下面的所有方法-->
   <aop:pointcut id="pointcut1" expression="execution(* com.heng.service.AccountService+.*(..))"/>
   <!--配置切面,在切入点pointcut1上应用txAdvice增强-->
   <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>

2、测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:beans2.xml")
public class TransactionTest2 {

    //注入AccountService
    @Resource(name="accountService")
    private AccountService accountService;

    @Test
    public void demo1(){
        accountService.transfer("aaa", "bbb", 200d);
    }
}

3、结果:转账失败

在这里插入图片描述

3、基于注解的方式

1、xm文件l配置:

<!--配置事务管理器DataSourceTransactionManager,依赖DataSource-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
   <property name="dataSource" ref="dataSource"/>
</bean>

<!--开启注解事务-->
<tx:annotation-driven transaction-manager="transactionManager"/>

2、在Service层中想要使用事务的类上或者方法上加上注解@Transactional

public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    public void setAccountDao(AccountDaoImpl accountDao) {
        this.accountDao = accountDao;
    }
    
    /**
     * 转账操作
     *
     * 事务属性:
     * propagation:传播机制
     * isolation:隔离级别
     * readOnly:只读
     * rollbackFor:发生哪些异常回滚
     * noRollbackFor:发生哪些异常不回滚
     */
    @Transactional(propagation = Propagation.REQUIRED,
            isolation = Isolation.DEFAULT,
            readOnly = false)
    @Override
    public void transfer(String out, String in, Double money) {
        accountDao.outMoney(out,money);
        int i=1/0;
        accountDao.inMoney(in,money);
    }
}

3、结果:转账失败
在这里插入图片描述

总结:

Spring将事务管理分成了两类:声明式事务管理、编程式事务管理

基于TransactionProxyFactoryBean的方式:需要为每个需要事务管理的类配置一个TransactionProxy FactoryBean进行增强,很少使用。

基于基于Aspetj的xml方式:常使用,不需要修改业务层的类。

基于注解的方式:常使用,但是需要修改业务层的类,加一个注解。

原创文章 723 获赞 153 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_42764468/article/details/106039955