spring05-Spring事务管理

时间:2023-03-09 05:59:15
spring05-Spring事务管理

事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:

传播行为 含义

PROPAGATION_REQUIRED 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务

PROPAGATION_SUPPORTS 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行

PROPAGATION_MANDATORY 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常

PROPAGATION_REQUIRED_NEW 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager

PROPAGATION_NOT_SUPPORTED 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager

PROPAGATION_NEVER 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常

PROPAGATION_NESTED 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务.

1、事务相关概念

1.1、什么是事务(Transaction)

是并发控制的单元,是用户定义的一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位。通过事务,sql 能将逻辑相关的一组操作绑定在一起,以便服务器 保持数据的完整性。事务通常是以begin transaction开始,以commit或rollback结束。Commint表示提交,即提交事务的所有操作。具体地说就是将事务中所有对数据的更新写回到磁盘上的物理数据库中去,事务正常结束。Rollback表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行,系统将事务中对数据库的所有已完成的操作全部撤消,滚回到事务开始的状态。

设想网上购物的一次交易,其付款过程至少包括以下几步数据库操作:

1、更新客户所购商品的库存信息

2、保存客户付款信息--可能包括与银行系统的交互

3、生成订单并且保存到数据库中

4、更新用户相关信息,例如购物数量等等

正常的情况下,这些操作将顺利进行,最终交易成功,与交易相关的所有数据库信息也成功地更新。但是,如果在这一系列过程中任何一个环节出了差错,例如在更新商品库存信息时发生异常、该顾客银行帐户存款不足等,都将导致交易失败。一旦交易失败,数据库中所有信息都必须保持交易前的状态不变,比如最后一步更新用户信息时失败而导致交易失败,那么必须保证这笔失败的交易不影响数据库的状态--库存信息没有被更新、用户也没有付款,订单也没有生成。否则,数据库的信息将会一片混乱而不可预测。

数据库事务正是用来保证这种情况下交易的平稳性和可预测性的技术

1.2、为什么要使用事务?

1、为了提高性能

2、为了保持业务流程的完整性

3、使用分布式事务

1.3、事务的特性

ACID

1 - 原子性(atomicity)

事务是数据库的逻辑工作单位,而且是必须是原子工作单位,对于其数据修改,要么全部执行,要么全部不执行。

2、一致性(consistency)

事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。

3、隔离性(isolation)

一个事务的执行不能被其他事务所影响。企业级的数据库每一秒钟都可能应付成千上万的并发访问,因而带来了并发控制的问题。由数据库理论可知,由于并发访问,在不可预料的时刻可能引发如下几个可以预料的问题:(见“二、事务的并发问题“)

4、持久性(durability)

一个事务一旦提交,事物的操作便永久性的保存在DB中。即使此时再执行回滚操作也不能撤消所做的更改

1.4、事务的并发问题

1、脏读(Dirty Read)

一个事务读取到了另一个事务未提交的数据操作结果。这是相当危险的,因为很可能所有的操作都被回滚。

2、不可重复读(虚读)(NonRepeatable Read)

一个事务对同一行数据重复读取两次,但是却得到了不同的结果。例如事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值。

3、幻读(Phantom Read)

事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据,这是因为在两次查询过程中有另外一个事务插入数据造成的

1.5、事务的隔离级别

1、读未提交

Read uncommitted:最低级别,以上情况均无法保证。

2、读已提交

Read committed:可避免脏读情况发生。

3、可重复读

Repeatable read:可避免脏读、不可重复读情况的发生。不可以避免虚读。

4、串行化读 Serializable:事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重

2. 转账案例

1.创建项目

引入jar包

spring05-Spring事务管理

  • 引入日志: log4j.properties

  • 复制数据库配置:db.properties

2. 创建数据库

在springjdbc库中直接创建表

 /*Table structure for table `ar_account` */

 CREATE TABLE `ar_account` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`username` VARCHAR(20) NOT NULL,
`money` DECIMAL(10,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; /*Data for the table `ar_account` */ INSERT INTO `ar_account`(`id`,`username`,`money`) VALUES (1,'Helen','1000.00');
INSERT INTO `ar_account`(`id`,`username`,`money`) VALUES (2,'Tom','1000.00');

3. 创建dao接口

 public interface AccountDao {
/**
* 加钱方法
* @param id
* @param money
*/
void increaseMoney(Integer id , Double money); /**
* 减钱方法
* @param id
* @param money
*/
void decreaseMoney(Integer id , Double money);
}

4. 创建dao实现类

 @Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void increaseMoney(Integer id, Double money) {
jdbcTemplate.update("update ar_account set money = money + ? where id = ? ;",money,id); }
public void decreaseMoney(Integer id, Double money) {
jdbcTemplate.update("update ar_account set money = money - ? where id = ? ;",money,id);
}
}

5. 创建service接口

 public interface AccountService {
//转账业务
void transfer(Integer from , Integer to , Double money);
}

6. 创建service实现类

 @Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void transfer(Integer from, Integer to, Double money) {
accountDao.decreaseMoney(from,money);
accountDao.increaseMoney(to,money);
}
}

7. 添加ioc配置文件

先不处理事务问题

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<context:component-scan base-package="com.itqf.spring"/>
<context:property-placeholder location="db.properties" />
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
p:driverClass="${jdbc.driverClass}"
p:jdbcUrl="${jdbc.jdbcUrl}"
p:user="${jdbc.user}"
p:password="${jdbc.password}"
/>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>

测试代码:

     ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService service = (AccountService) context.getBean("accountService");
service.transfer(1,2,100.0);

3. Spring XML配置声明事务 

3.1. TransactionManager

在不同平台,操作事务的代码各不相同,因此spring提供了一个 TransactionManager 接口:

  • DateSourceTransactionManager 用于 JDBC 的事务管理

  • HibernateTransactionManager 用于 Hibernate 的事务管理

  • JpaTransactionManager 用于 Jpa 的事务管理

spring05-Spring事务管理

3.2 接口的定义

事务的属性介绍:这里定义了传播行为、隔离级别、超时时间、是否只读

 package org.springframework.transaction;
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;//默认级别,MYSQL: 默认为REPEATABLE_READ级别 SQLSERVER: 默认为READ_COMMITTED
int ISOLATION_READ_UNCOMMITTED = 1;//读取未提交数据(会出现脏读, 不可重复读) 基本不使用
int ISOLATION_READ_COMMITTED = 2;//读取已提交数据(会出现不可重复读和幻读)
int ISOLATION_REPEATABLE_READ = 4;//可重复读(会出现幻读)
int ISOLATION_SERIALIZABLE = 8;//串行化 int TIMEOUT_DEFAULT = -1;//默认是-1,不超时,单位是秒 //事务的传播行为
int getPropagationBehavior();
//事务的隔离级别
int getIsolationLevel();
//事务超时时间
int getTimeout();
//是否只读
boolean isReadOnly();
String getName();
}

3.3添加tx命名空间

事务基础组件,包括对 DAO 的支持及 JCA 的集成

修改 applicationContext.xml

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
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-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
</beans>

3.4. 添加事务相关配置

修改applicationContext.xml

 <!-- 平台事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 传播行为 -->
<!-- REQUIRED:如果有事务,则在事务中执行;如果没有事务,则开启一个新的事物 -->
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="transfer" propagation="REQUIRED" />
<!-- SUPPORTS:如果有事务,则在事务中执行;如果没有事务,则不会开启事物 -->
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="select*" propagation="SUPPORTS" read-only="true" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com..service..*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

配置介绍:

tx:advice 是用于配置事务相关信息, transaction-manager属性是引入对应类型的事务管理;

jdbc/mybatias : DataSourceTransactionManager

hibernate: HibernateTransactionManager

JPA:JPATransactionManager


tx:attributes 此标签所配置的是 哪些方法可以作为事务方法(为后面切点进行补充)


tx:method 标签设置具体要添加事务的方法和其他属性

name 是必须的,表示与事务属性关联的方法名(业务方法名),对切入点进行细化。通配符*可以用来指定一批关联到相同的事务属性的方法。如:'get*'、'handle*'、'on*Event'等等.

propagation 不是必须的 ,默认值是REQUIRED 表示事务传播行为, 包括REQUIRED,SUPPORTS,MANDATORY,REQUIRES_NEW,NOT_SUPPORTED,NEVER,NESTED

isolation 不是必须的 默认值DEFAULT

timeout 不是必须的 默认值-1(永不超时) 表示事务超时的时间(以秒为单位)

read-only 不是必须的 默认值false不是只读的 表示事务是否只读?

rollback-for 不是必须的 表示将被触发进行回滚的 Exception(s);以逗号分开。 如:'com.itqf.MyBusinessException,ServletException'

no-rollback-for 不是必须的 表示不被触发进行回滚的 Exception(s);以逗号分开。 如:'com.foo.MyBusinessException,ServletException'

aop:config标签 设置事务的切点,配置参与事务的类和对应的方法.

注意: aop:config和tx:advice 但是两者并不冲突, aop:config面向切面编程的切点,选择对应的方法进行切入,而tx:adivce是设置事务的相关的属性和描述,换句话说,aop:config选择了对应的切入点,tx:advice是在这些切入点上根据 method name属性再次进行筛选!

4.编程式的事务管理

在实际应用中,很少需要通过编程来进行事务管理,即便如此,Spring还是为编程式事务管理提供了模板类 org.springframework.transaction.support.TransactionTemplate,以满足一些特殊场合的需求!以满足

一些特殊场合的需求.

TransactionTemplate和那些持久化模板类一样是线程安全的,因此,可以在多个业务类*享TranscationTemplate实例进行事务管理.TransactionTemplate拥有多个设置事务的属性方法.如setReadOnly(boolean only), setIsolationLevel(int isolationLevel)等.

TransactionTemplate有两个主要方法:

void setTransactionManager(PlatformTransactionManager transactionManager) 设置事务管理器.

Object execute(TransactionCallback action): 在TransactionCallback回调接口中定义需要以事务方式组织的数据访问逻辑.

TransactionCallback 接口只有一个方法: Object doInTransaction(TransactionStatus status).如果操作不会返回结果,则可以使用TransactionCallback的子接口 TransactionCallbackWithoutResult.

配置:

 <!-- 配置事务管理类 -->
<bean id="template" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>

测试代码:

 @Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao; @Autowired
TransactionTemplate template; public void transfer(final Integer from, final Integer to, final Double money) {
//事务管管理类
template.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.decreaseMoney(from,money);
// int i = 1/0;
accountDao.increaseMoney(to,money);
}
});
}
}

5. 使用注解方式添加事务

除了基于XML的事务配置,Spring还提供了基于注解的事务配置,即通过@Transactional对需要事务增强的Bean接口,实现类或者方法进行标注,在容器中配置基于注解的事务增强驱动,即可启用注解的方式声明事务

5.1 使用@Transactional注解

顺着原来的思路,使用@Transactional对基于aop/tx命名空间的事务配置进行改造!

  • 修改service类添加@Transactional注解

 @Transactional    //对业务类进行事务增强的标注
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void transfer(final Integer from, final Integer to, final Double money) {
accountDao.decreaseMoney(from,money);
int i = 1 / 0 ;
accountDao.increaseMoney(to,money);
}

因为注解本身具有一组默认的事务属性,所以往往只要在需要事务的业务类中添加一个@Transactional注解,就完成了业务类事务属性的配置!

当然,注解只能提供元数据,它本身并不能完成事务切面织入的功能.因此,还需要在Spring的配置中通过一行配置'通知'Spring容器对标注@Transactional注解的Bean进行加工处理!

配置:

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
<context:component-scan base-package="spring"/>
<context:property-placeholder location="db.properties" />
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
p:driverClass="${jdbc.driverClass}"
p:jdbcUrl="${jdbc.jdbcUrl}"
p:user="${jdbc.user}"
p:password="${jdbc.password}"
/>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="template" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
<!-- <bean id="entityManagerFactory" class="org.springframework.orm.jpa.aspectj.JpaExceptionTranslatorAspect"-->
<!--① 对标注@Transactional注解的Bean进行加工处理,以织入事物管理切面 -->
<tx:annotation-driven transaction-manager="transactionManager" /> </beans>

在默认情况, <tx:annotation-driven /> 中transaction-manager属性会自动使用名为 "transactionManager" 的事务管理器.所以,如果用户将事务管理器的id定义为 transactionManager , 则可以进一步将①处的配置简化为 <tx:annotation-driven />.

使用以上测试用例即可

5.2 @Transactional其他方面介绍

关于@Transactional的属性

基于@Transactional注解的配置和基于xml的配置一样,它拥有一组普适性很强的默认事务属性,往往可以直接使用默认的属性.

  • 事务传播行为: PROPAGATION_REQUIRED.

  • 事务隔离级别: ISOLATION_DEFAULT.

  • 读写事务属性:读/写事务.

  • 超时时间:依赖于底层的事务系统默认值

  • 回滚设置:任何运行期异常引发回滚,任何检查型异常不会引发回滚.

默认值可能适应大部分情况,但是我们依然可以可以自己设定属性,具体属性表如下:

spring05-Spring事务管理

  • 在何处标注@Transactional注解

    @Transactional注解可以直接用于接口定义和接口方法,类定义和类的public方法上.

    但Spring建议在业务实现类上使用@Transactional注解,当然也可以添加到业务接口上,但是这样会留下一些容易被忽视的隐患,因为注解不能被继承,所以业务接口中标注的@Transactional注解不会被业务类实现继承.

  • 在方法出使用注解

    方法出添加注解会覆盖类定义处的注解,如果有写方法需要使用特殊的事务属性,则可以在类注解的基础上提供方法注解,如下:

     @Transactional
    @Service("accountService")
    public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao; //不等于默认值!可以覆盖类注解
    @Transactional(readOnly = false , isolation = Isolation.READ_COMMITTED)
    public void transfer(final Integer from, final Integer to, final Double money) { accountDao.decreaseMoney(from,money); // int i = 1/0; accountDao.increaseMoney(to,money); }
    }

    使用不同的事务管理器

    一般情况下,一个应用仅需要使用一个事务管理器.如果希望在不同的地方使用不同的事务管理,@Transactional注解同样支持!

    实现代码:

     @Transactional("事务管理器的名字") //此处添加指定事务管理器的名字
    @Service("accountService")
    public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao; public void transfer(final Integer from, final Integer to, final Double money) { accountDao.decreaseMoney(from,money); // int i = 1/0; accountDao.increaseMoney(to,money); }
    }

    对应事务查找事务管理器的名字应该在xml中进行定义!如下:

     <!--声明一个事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
    <qualifier value="定义事务管理器的名字,可以被注解查找" />
    </bean>