七、spring学习之事物管理器
一、spring事务简述
* spring中的事务管理,是借助spring的aop实现的
* 使用spring的事务管理:导入 spring-tx
事务管理器 : PlaformTransactionManager (接口)
1.DataSourceTransactionManager :对jdbc操作的事务控制(jdbcTemplate,mybatis)
2.HibernateTransactionManager :对hibernate操作的事务控制
3.JpaTransactionManager :对jpa操作的事务控制
aop处理方式
1.定义了一个处理事务的切面类
*即事务管理器
2.将切面类(事务管理器)交给spring容器管理
3.配置事务管理的aop
二、声明事务的流程:
1.声明事务管理器、并交给spring管理 使用bean标签结合id、class属性即可,id一般默认transactionManager。
2.tx:advice 配置事务的通知引用事务管理器,
属性:
id :事务通知的唯一标识
transaction-manager:引用的事务管理器,当事物管理器id为默认的transactionManager,此处的引用可不写
3.配置事务的属性 主要配置的有三个
1)propagation: 传播行为 常用的两个 REQUIRED SUPPORTS
2)read-only: 是否只读 配置可以提高查询效率 true只读 false非只读
3)name : 方法名称 为不同的service方法引入不同的事务通知
可以支持通配符的写法(*:代表任意字符串)
4.配置aop:config 将事务通知的引入到切入点
1)、aop:pointcut 设置切入点表达式
2)、aop:advisor 将通知配置到切入点上
属性:
advice-ref 事务通知的引用
pointcut-ref 切入点的引用
spring事务属性的类型
read-only:是否是只读事务。默认false,不只读。
isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
propagation:指定事务的传播行为。
timeout:指定超时时间。默认值为:-1。永不超时。
rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。
没有默认值,任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回
滚。没有默认值,任何异常都回滚。
三:事物的特性简述
1.事务的定义:事务是指多个操作单元组成的合集,多个单元操作是整体不可分割的,要么都操作不成功,要么都成功。其必须遵循四个原则(ACID)。
原子性(Atomicity):即事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做;
一致性(Consistency):在事务执行前数据库的数据处于正确的状态,而事务执行完成后数据库的数据还是应该处于正确的状态,即数据完整性约束没有被破坏;如银行转帐,A转帐给B,必须保证A的钱一定转给B,一定不会出现A的钱转了但B没收到,否则数据库的数据就处于不一致(不正确)的状态。
隔离性(Isolation):并发事务执行之间互不影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性;
持久性(Durability):事务一旦执行成功,它对数据库的数据的改变必须是永久的,不会因比如遇到系统故障或断电造成数据不一致或丢失。
2Spring事务隔离级别:spring有五大隔离级别,其在TransactionDefinition接口中定义。看源码可知,其默isolation_default(底层数据库默认级别),其他四个隔离级别跟数据库隔离级别一致。
Tables | Are |
---|---|
ISOLATION_DEFAULT | 用底层数据库的默认隔离级别,数据库管理员设置什么就是什么 |
ISOLATION_READ_UNCOMMITTED(读未提交) | 最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读) |
ISOLATION_READ_COMMITTED(读提交) | 一个事务提交后才能被其他事务读取到(该隔离级别禁止其他事务读取到未提交事务的数据、所以还是会造成幻读、不可重复读)、解决脏读,Oracle、sql server默认级别 |
ISOLATION_REPEATABLE_READ(可重复读) | 可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(该隔离基本可防止脏读,不可重复读(重点在修改),但会出现幻读(重点在增加与删除))(MySql默认级别,更改可通过set transaction isolation level 级别) |
ISOLATION_SERIALIZABLE(序列化) | 代价最高最可靠的隔离级别(该隔离级别能防止脏读、不可重复读、幻读) |
关于读问题
读问题 | 说明 |
---|---|
丢失更新* | 两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,这是由于没有加锁造成的; |
幻读 | 同样的事务操作过程中,不同时间段多次(不同事务)读取同一数据,读取到的内容不一致(一般是行数变多或变少)。 |
脏读 | 一个事务读取到另外一个未提及事务的内容,即为脏读。 |
不可重复读 | 幻读的重点在于插入与删除,即第二次查询会发现比第一次查询数据变少或者变多了,以至于给人一种幻象一样,而不可重复读重点在于修改,即第二次查询会发现查询结果比第一次查询结果不一致,即第一次结果已经不可重现了。 |
数据库隔离级别越高,执行代价越高,并发执行能力越差,因此在实际项目开发使用时要综合考虑,为了考虑并发性能一般使用提交读隔离级别,它能避免丢失更新和脏读,尽管不可重复读和幻读不能避免,但可以在可能出现的场合使用悲观锁或乐观锁来解决这些问题。
3spring中事物的传播行为(propagation:指定事务的传播行为)
传播行为 | 含义 |
---|---|
PROPAGATION_REQUIRED(XML文件中为REQUIRED) | 表示当前方法必须在一个具有事务的上下文中运行,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。(如果被调用端发生异常,那么调用端和被调用端事务都将回滚) |
PROPAGATION_SUPPORTS(XML文件中为SUPPORTS) | 表示当前方法不必需要具有一个事务上下文,但是如果有一个事务的话,它也可以在这个事务中运行 |
PROPAGATION_MANDATORY(XML文件中为MANDATORY) | 表示当前方法必须在一个事务中运行,如果没有事务,将抛出异常 |
PROPAGATION_NESTED(XML文件中为NESTED) | 表示如果当前方法正有一个事务在运行中,则该方法应该运行在一个嵌套事务中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。如果封装事务不存在,则同PROPAGATION_REQUIRED的一样 |
PROPAGATION_NEVER(XML文件中为NEVER) | 表示当方法务不应该在一个事务中运行,如果存在一个事务,则抛出异常 |
PROPAGATION_REQUIRES_NEW(XML文件中为REQUIRES_NEW) | 表示当前方法必须运行在它自己的事务中。一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。 |
PROPAGATION_NOT_SUPPORTED(XML文件中为NOT_SUPPORTED) | 表示该方法不应该在一个事务中运行。如果有一个事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行 |
4Spring事务支持
.spring提供了很多内置事务管理器,支持不同数据源。常见的有三大类
事务管理器 | 支持的数据源 |
---|---|
DataSourceTransactionManager | :org.springframework.jdbc.datasource包下,数据源事务管理类,提供对单个javax.sql.DataSource数据源的事务管理,只要用于JDBC,Mybatis框架事务管理。 |
HibernateTransactionManager | org.springframework.orm.hibernate3包下,数据源事务管理类,提供对单个org.hibernate.SessionFactory事务支持,用于集成Hibernate框架时的事务管理;注意:该事务管理器只支持Hibernate3+版本,且Spring3.0+版本只支持Hibernate 3.2+版本; |
JtaTransactionManager | 位于org.springframework.transaction.jta包中,提供对分布式事务管理的支持,并将事务管理委托给Java EE应用服务器,或者自定义一个本地JTA事务管理器,嵌套到应用程序中。 |
PlatformTransactionManager接口定义如下
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;//返回一个已经激活的事务或创建一个新的事务(具体由TransactionDefinition参数定义的事务属性决定),返回的TransactionStatus对象代表了当前事务的状态,其中该方法抛出TransactionException(未检查异常)表示事务由于某种原因失败。
void commit(TransactionStatus status) throws TransactionException;//用于提交TransactionStatus参数代表的事务。
void rollback(TransactionStatus status) throws TransactionException;//用于回滚TransactionStatus参数代表的事务。
}
TransactionDefinition接口定义如下:
[java] view plain copy
public interface TransactionDefinition {
int getPropagationBehavior(); //返回定义的事务传播行为
int getIsolationLevel(); //返回事务隔离级别
int getTimeout(); //返回定义的事务超时时间
boolean isReadOnly(); //返回定义的事务是否是只读的
String getName(); //返回事务名称
}
TransactionStatus接口定义如下:
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction(); //返回当前事务是否是新的事务
boolean hasSavepoint(); //返回当前事务是否有保存点
void setRollbackOnly(); //设置事务回滚
boolean isRollbackOnly(); //设置当前事务是否应该回滚
void flush(); //用于刷新底层会话中的修改到数据库,一般用于刷新如Hibernate/JPA的会话,可能对如JDBC类型的事务无任何影响;
boolean isCompleted(); //返回事务是否完成
}
四、基于xml的事物管理
<?xml version="1.0" encoding="UTF-8"?>
<!-- 导入约束 :xsd -->
<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
">
<!-- 创建Service交给spring管理 -->
<bean id="accountService" class="cn.it.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 创建Dao交给spring管理 -->
<bean id="accountDao" class="cn.it.dao.impl.AccountDaoImpl2">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!-- 配置jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 注入基本元素,配置spring内置连接池对象 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!-- 基本元素赋值 -->
<property name="username" value="root"></property>
<property name="password" value="root"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///springdb"></property>
</bean>
<!-- 一、配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 二、配置事务通知 : tx的名称空间
对定位到的方法,添加不同的事务支持情况
1)id:事务通知的唯一标识
2)transaction-manager:引用事务管理器的id,
当事务管理器id采用默认的transactionManager时可以不写
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--三、配置service实现类中,每一个方法对事务的支持情况
1)propagation:传播行为
2)read-only:是否只读
3)name : 方法名称
可以支持通配符的写法(*:代表任意字符串)
-->
<!-- 方式一 -->
<tx:method name="*" propagation="REQUIRED" read-only="false" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<!-- 方式二 -->
<!--
<tx:method name="save*" propagation="REQUIRED" read-only="false" />
<tx:method name="update*" propagation="REQUIRED" read-only="false" />
<tx:method name="delete*" propagation="REQUIRED" read-only="false" />
<tx:method name="insert*" propagation="REQUIRED" read-only="false" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" /> -->
</tx:attributes>
</tx:advice>
<!-- 四、配置aop:定位需要添加事务的类和方法 -->
<aop:config>
<!-- 定义切入点 -->
<aop:pointcut expression="execution(* cn.it.service.impl.*.*(..))" id="pt" />
<!-- 定义事务通知 将切入点和事务通知联系起来 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
</aop:config>
</beans>
五、基于xml结合anno|注解的事物管理
1、开启对事物注解的支持:tx:annotation-driven
2、配置事务管理器,交给spring管理
3、在各个service类和方法上绑定通知
一般用于涉及增删改
@Transactional(propagation=Propagation.REQUIRD,readOnly=false)
一般用于查询
@Transactional(propagation=Propagation.REQUIRD,readOnly=true)
使用原则:事务通知的配置遵循就近原则,可以配置在类上,表示该类采用的通知,也可以配置在方法上,表示该方法采用的通知,所以我们可以在类上配置该类使用较多的通知类型,然后在使用其他通知类型的方法上单独配置不同的通知。
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!-- 导入约束 :xsd -->
<!-- 导入约束 :xsd -->
<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
">
<!-- 开启对注解的支持 IOC AOP 事物tx-->
<!-- 开启包扫描 -->
<context:component-scan base-package="cn.it"></context:component-scan>
<!-- 开启aop注解支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 开启对事物注解的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 二、配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 三、在各个service类和方法上绑定通知 -->
<!-- 配置jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 注入基本元素,配置dbcp连接池对象 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!-- 基本元素赋值 -->
<property name="username" value="root"></property>
<property name="password" value="root"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///springdb"></property>
</bean>
<!-- 配置c3p0连接池 -->
<!-- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="root"></property>
<property name="password" value="111111"></property>
<property name="jdbcUrl" value="jdbc:mysql:///springdb"></property>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
</bean> -->
<!-- 配置spring内置连接池对象 -->
<!-- <bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="username" value="root"></property>
<property name="password" value="root"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///springdb"></property>
</bean> -->
</beans>
service
package cn.it.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import cn.it.dao.AccountDao;
import cn.it.domain.Account;
import cn.it.service.AccountService;
/**
* 配置事务
* @Transactional
* propagation : 传播行为
* readOnly :是否只读
* * 可以配置到类上:表明此类当中的所有方法都具有此事务
* * 可以配置到方法上:表明此方法具有此事务
* (就近原则)
*/
@Service("accountService")
@Transactional( propagation=Propagation.REQUIRED,readOnly=false)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
/**
* 转账:
* 从sourceName账户转出 money 元 给 targetName账户
*/
public void transfer(String sourceName, String targetName, float money) {
Account sourceAcc = findAccountByName(sourceName);
Account targetAcc = findAccountByName(targetName);
sourceAcc.setMoney(sourceAcc.getMoney() + money);
targetAcc.setMoney(targetAcc.getMoney() - money);
updateAccount(sourceAcc);
//用于测试事务
//int i = 1 / 0;
updateAccount(targetAcc);
}
/**
* 根据用户名修改账户金额
*/
@Override
public int updateAccount(Account account) {
return accountDao.updateAccount(account);
}
/**
* 根据用户名查找账户
*/
@Transactional(propagation=Propagation.REQUIRED,readOnly=true)
public Account findAccountByName(String name) {
return accountDao.findAccountByName(name);
}
}
六、纯anno|注解的事物处理
1.@EnableTransactionManagement:开启对事务管理的注解支持
2.@Bean 创建事物管理器,交给spring管理
3.@Transactional 在service层类及方法上绑定通知类型
* propagation : 传播行为
* readOnly :是否只读
我们需要定义一个配置类
/**
* @EnableTransactionManagement:开启对事务管理的注解支持
*/
@Configuration
@ComponentScan("cn.it")
@Import(jdbcConfig.class)
@EnableTransactionManagement
public class SpringConfig {
/**
* 创建事物管理器,交给spring管理
*
*/
@Bean
public PlatformTransactionManager TransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}