数据库
首页 > 数据库> > 06-Spring之基于AOP的事务管理(针对数据库操作)

06-Spring之基于AOP的事务管理(针对数据库操作)

作者:互联网

事务管理(针对数据库操作)

注意spring中的事务控制是基于AOP的
事务控制应该针对Service层进行配置(比如银行转账的一个案例,A转账给B 100元,这里涉及到了两个操作,A账户的资金减少100元以及B账户资金增加100元,如果不添加事务操作,B账户资金增加时发生错误,增加失败,而A操作正常发生,这时A的账户会减少100元,但是B账户没有增加100元,这明显是不可取的。因此需要引入事务控制,只有当两个操作都成功时,才真正地进行操作,任意一个操作失败时,发生回滚,即两个操作都撤回)

简介

定义:事务管理是针对于一系列对数据库操作的管理(这是数据库的概念),一个事务包含一条或多条SQL语句
假设增加一条数据需要进行的操作:查询数据库查看是否已经存在这条数据,再将这条数据插入(注意这里是假设).这样子的一个操作可以看做事务(单纯的查找数据或是插入数据也可以看做一个事务).
我们在实际业务场景中,经常会遇到数据频繁修改读取的问题。在同一时刻,不同的业务逻辑对同一个表数据进行修改,这种冲突很可能造成数据不可挽回的错乱,所以我们需要用事务来对数据进行管理.
关键属性ACID:
原子性(atomicity):事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的.
一致性(consistency):表明应用系统从一个正确的状态到另一个正确的状态,比如转账中,我们在数据库中约束余额不能为负,因此转账前后账户的余额都不为负(注意这是数据库约束的),否则会产生回滚。
隔离性(isolation):可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏.
持久性(durability):一个事务一旦完成全部操作后,这个事务的结果必须是永久性的,不能因系统故障而从数据库中删除.

一个事务的简单流程

1--开始事务
2--执行一系列的SQL语句
3--如果操作都成功,则执行提交操作,否则回滚所有操作.

事务类型

编程式事务--通过编码方式实现事务,即类似于JDBC编程实现事务管理
声明式事务--管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务.

数据库并发的问题

- 脏读(dirty reads):当事务读取还未被提交的数据时,就会发生这种事件。举例来说:Transaction1 修改了一行数据,然后Transaction2在Transaction1还未提交修改操作之前读取了被修改的行。如果Transaction1回滚了修改操作,那么Transaction2读取的数据就可以看作是从未存在过的。        
- 不可重复的读(non-repeatable reads):当事务两次读取同一行数据,但每次得到的数据都不一样时,就会发生这种事件。举例来说:Transaction1读取一行数据,然后Transaction2修改或删除该行并提交修改操作。当Transaction1试图重新读取该行时,它就会得到不同的数据值(如果该行被更新)或发现该行不再存在(如果该行被删除)。           
- 虚读(phantom read):如果符合搜索条件的一行数据在后面的读取操作中出现,但该行数据却不属于最初的数据,就会发生这种事件。举例来说:Transaction1读取满足某种搜索条件的一些行,然后Transaction2插入了符合Transaction1的搜索条件的一个新行。如果Transaction1重新执行产生原来那些行的查询,就会得到不同的行。

事务的隔离级别

- ISOLATION_DEFAULT
	- 默认级别,归属下列某一种
- ISOLATION_READ_UNCOMMITTED
	- 表明可以发生误读、不可重复读和虚读。(可以读取未提交的数据)
- ISOLATION_READ_COMMITTED
	- 表明能够阻止误读;可以发生不可重复读和虚读。(只能读取已提交的数据,解决脏读问题(Oracle默认级别))
- ISOLATION_REPEATABLE_READ
	- 表明能够阻止误读和不可重复读;可以发生虚读。(是否读取其他事务提交后的数据,解决不可重复读的问题(MYSQL默认级别))
- ISOLATION_SERIALIZABLE
	- 表明能够阻止误读、不可重复读和虚读。(是否读取其他事务提交添加后的数据,解决幻影读问题)

事务的传播行为

1、REQUIRED

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
   methodB();
// do something
}
 
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
    // do something
}

REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择----单独调用methodB()时,因为不存在事务,因此会开启一个新的事务,调用methodA()时,开启一个新的事务,而执行到methodB()时,由于已经存在一个事务了,因此会加入到这个事务中

2、SUPPORTS

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
 methodB();
// do something
}
 
// 事务属性为SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
    // do something
}

SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行----单独调用methodB()时,不存在事务,但methodB()是可以非事务执行的,因此以非事务执行,调用methodA()时,会开启一个新的事务,而执行到methodB()时,会加入到methodA()的事务

3、MANDATORY

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
 methodB();
// do something
}
 
// 事务属性为MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
    // do something
}
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。单独调用methodB()时,因为当前没有事务,会抛出异常,调用methodA()不会发生错误

4、REQUIRES_NEW

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

5、NOT_SUPPORTED

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

6、NEVER

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

7、NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作。

超时时间和是否是只读事务

超时时间:默认为-1,没有超时限制。如果有,以秒为单位进行设置。
是否是只读事务:建议查询时设置为只读。

基于xml的声明式事务控制

注意:如果不进行事务配置,那么一个操作是一个sqlSession(即一个mapper.function就是一个sqlSession)

	<!--Spring中基于xml的声明式事务控制
		1.配置事务管理器
		2.配置事务通知
			<tx:advice>标签配置事务通知
				属性:
					id:给事务通知提供一个唯一标志
					transaction-manager:给事务通知提供一个事务管理器引用
		3.配置AOP中的通用切入点表达式
		4.建立事务通知和切入点表达式的对应关系
		5.配置事务属性
			isolation属性:用于指定事务隔离级别,默认值DEFAULT,表示使用数据库的默认级别。
			propagation属性:用于指定事务的传播行为,默认值REQUIRED,表示一定会有事务,增删改的选择,查询可选SUPPORTS。
			read-only属性:设置事务是否只读,只用查询方法才能设置为true,默认值时false便是读写。
			timeout属性:用于指定事务的超时时间,默认值-1,如果制定了时间以s为单位。
			rollback-for属性:用于指定一个异常,当产生该异常时事务回滚,产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚。
			no-rollback-for属性:用于指定一个异常,当产生该异常时事务不回滚,产生其他异常时,事务回滚。没有默认值,表示任何异常都回滚。
	-->
需要的命名空间:
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
<!--1.配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
<!--2.配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--5.配置事务属性-->
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED" read-only="false"/>
        <!--优先级高-->
        <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
    </tx:attributes>
</tx:advice>
<!--配置AOP-->
<aop:config>
    <!--3.配置切入点表达式-->
    <aop:pointcut id="pt1" expression="execution(* com.whether.service.impl.*.*(..))"></aop:pointcut>
    <!--4.建立事务通知和切入点表达式的对应关系-->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>

基于注解的声明式事务控制(重点)

	<!--Spring中基于注解的声明式事务控制
		1.配置事务管理器
		2.开启Spring对注解事务的支持
		3.在需要事务支持的地方使用@Transactional注解
	-->
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启Spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)//只读型事务配置
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;

    @Override
    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer....");
        //2.1根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.2根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //2.3转出账户减钱
        source.setMoney(source.getMoney() - money);
        //2.4转入账户加钱
        target.setMoney(target.getMoney() + money);
        //2.5更新转出账户
        accountDao.updateAccount(source);
        int i = 1 / 0;
        //2.6更新转入账户
        accountDao.updateAccount(target);
    }
}

标签:事务管理,事务,06,读取,Spring,methodB,操作,数据,public
来源: https://www.cnblogs.com/whether/p/14166422.html