一.在学习Spring事务管理之前,我们首先要知道为什么要使用事务管理
我们用一个老套的例子来引入:取钱。
比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱;然后ATM出1000元钱。这两个步骤必须是要么都执行要么都不执行。如果银行卡扣除了1000块但是ATM出钱失败的话,你将会损失1000元;如果银行卡扣钱失败但是ATM却出了1000块,那么银行将损失1000元。所以,如果一个步骤成功另一个步骤失败对双方都不是好事,如果不管哪一个步骤失败了以后,整个取钱过程都能回滚,也就是完全取消所有操作的话,这对双方都是极好的。
事务就是用来解决类似问题的。事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。
在企业级应用程序开发中,事务管理必不可少的技术,用来确保数据的完整性和一致性。
二.看一下spring transaction的几种实现方式,先展示下如何实现,然后再对重点进行分析。
1)全注解方式
<?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-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:property-placeholder location="classpath:db.properties"/>
<context:component-scan base-package="wangcc"></context:component-scan>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
</beans>
我们来分析一下配置文件
<context:property-placeholder location="classpath:db.properties"/>
如果想要在Spring配置文件中使用${jdbc.url}等属性文件中所配置的属性。就需要在配置文件中配置一句 使得Spring配置文件可以使用db.propertis属性文件
这里放下配置文件
jdbc.driver=oracle.jdbc.driver.OracleDriver
jdbc.url=
jdbc.username=
jdbc.password=
# Time to wait for an open connection before timing out
# (in milliseconds)
cpool.checkoutTimeout=5000
# Connection pool size
cpool.minPoolSize=5
cpool.maxPoolSize=20
# How long to keep unused connections around(in seconds)
# Note: MySQL times out idle connections after 8 hours(28,800 seconds)
# so ensure this value is below MySQL idle timeout
cpool.maxIdleTime=3600
# How long to hang on to excess unused connections after traffic spike
# (in seconds)
cpool.maxIdleTimeExcessConnections=1800
# Acquiring new connections is slow, so eagerly retrieve extra connections
# when current pool size is reached
cpool.acquireIncrement=5
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
我们需要配置dataSource数据源,数据源的出现是为了提高数据库的操作,避免使用原生的jdbc进行频繁的数据库操作。数据库连接池的出现可以减少频繁的数据库操作。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
接下来需要配置transactionManager,我们这里使用的是Mybatis作为数据库持久层。配置的就是DataSourceTransactionManager。
如果使用的是原生的JDBC,则配置的是JTATransactionManager。
如果是用Hibernate,则配置的是HibernateManager。
接下来就应该是配置事务管理了,在这里是全注解配置所以只需在配置文件中加入
<tx:annotation-driven transaction-manager="transactionManager"/>
由于我们的持久层用的是Mybatis,接下来就需要配置Mybatis的相关配置了。
由于在配置数据源DataSource时已经配置了数据库的相关配置,所以Mybatis配置文件就显得十分简洁,只需要配置一些别名,全局设置以及一些Mapper的XML配置文件,不需要配置environment等配置了。在这列一下mybatis配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="User" type="wangcc.entity.User"/>
</typeAliases>
<mappers>
<mapper resource="wangcc/mapper/UserMapper.xml"/>
</mappers>
</configuration>
在配置SqlSessionFactory的时候需要将DataSource注入。并且需要将mybatis配置文件注入。
最后配置SqlSessionTemplate
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
下面列一下java代码
dao层代码
package wangcc.dao.impl;
import java.util.HashMap;
import java.util.List;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import wangcc.dao.UserDao;
import wangcc.entity.User;
@Repository("userdao")
public class UserDaoImpl implements UserDao {
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
public SqlSessionTemplate getSqlSessionTemplate() {
return sqlSessionTemplate;
}
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
@Override
public void save(User user) {
// TODO Auto-generated method stub
this.sqlSessionTemplate.insert("UserMapper.insertUser", user);
}
@Override
public List<User> getUserList() {
// TODO Auto-generated method stub
return this.sqlSessionTemplate.selectList("UserMapper.getAllUser");
}
@Override
public void update(User user) {
// TODO Auto-generated method stub
this.sqlSessionTemplate.update("UserMapper.updateUser", user);
}
@Override
public void delete(Integer id) {
// TODO Auto-generated method stub
this.sqlSessionTemplate.delete("UserMapper.deleteUser", id);
}
@Override
public List<User> getUserByOrder(HashMap<String,Object> params) {
// TODO Auto-generated method stub
return this.sqlSessionTemplate.selectList("UserMapper.getUserByOrder", params);
}
@Override
public User getUserById(Integer id) {
// TODO Auto-generated method stub
return this.sqlSessionTemplate.selectOne("UserMapper.getUserById", id);
}
}
package wangcc.dao;
import java.util.HashMap;
import java.util.List;
import wangcc.entity.User;
public interface UserDao {
public void save(User user);
public List<User> getUserByOrder(HashMap<String,Object> params);
public User getUserById(Integer id);
public List<User> getUserList();
public void update(User suer);
public void delete(Integer id);
}
实体类
package wangcc.entity;
import java.util.Date;
public class User {
private Integer id;
private String name;
private Date birthday;
private double salary;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
mapper配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="UserMapper">
<select id="getUserById" parameterType="Integer" resultType="User">
select * from wangcc_user where id=#{id}
</select>
<select id="getAllUser" resultType="User">
select * from wangcc_user
</select>
<insert id="insertUser" parameterType="User" >
insert into wangcc_user (name,birthday,salary) values(#{name},#{birthday},#{salary})
</insert>
<select id="getUserByOrder" parameterType="map" resultType="User">
select * from wangcc_user where 1=1
<if test="orderKey!=null">
</if>
order by ${orderKey}
</select>
<update id="updateUser" parameterType="User">
update wangcc_user
<trim prefix="set" suffixOverrides=",">
<if test="name!=null">
name=#{name}
</if>
<if test="birthday!=null">
birthday=#{birthday}
</if>
<if test="salary!=null">
salary=#{salary}
</if>
</trim>
where id=#{id}
</update>
<delete id="deleteUser" parameterType="Integer">
delete wangcc_user where id=#{id}
</delete>
</mapper>
Service层
package wangcc.service;
import java.util.HashMap;
import java.util.List;
import wangcc.entity.User;
public interface UserService {
public void save(User user);
public List<User> getUserByOrder(HashMap<String,Object> params);
public User getUserById(Integer id);
public List<User> getUserList();
public void update(User user);
public void delete(Integer id);
}
package wangcc.service.impl;
import java.util.HashMap;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import wangcc.dao.UserDao;
import wangcc.dao.impl.UserDaoImpl;
import wangcc.entity.User;
import wangcc.service.UserService;
@Component("userService")
public class UserServiceImpl implements UserService{
@Autowired
@Qualifier("userdao")
private UserDao userdao;
@Transactional
public void save(User user) {
// TODO Auto-generated method stub
userdao.save(user);
throw new RuntimeException("跳出");
}
@Override
public List<User> getUserByOrder(HashMap<String, Object> params) {
// TODO Auto-generated method stub
return userdao.getUserByOrder(params);
}
@Override
public User getUserById(Integer id) {
// TODO Auto-generated method stub
return userdao.getUserById(id);
}
@Override
public List<User> getUserList() {
// TODO Auto-generated method stub
return userdao.getUserList();
}
@Override
public void update(User user) {
// TODO Auto-generated method stub
userdao.update(user);
}
@Override
public void delete(Integer id) {
// TODO Auto-generated method stub
userdao.delete(id);
}
}
工具类
package wangcc.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateUtil {
public static final String DEFAULT_DATE="yyyy-MM-dd";
public static Date StringtoDate(String format,String time) {
SimpleDateFormat sdf = new SimpleDateFormat(format);
Date date = null;
try {
date = sdf.parse(time);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return date;
}
public static void main(String []args)
{
Date date=StringtoDate(DEFAULT_DATE, "1996-02-27");
System.out.println(date);
}
}
测试类
package wangcc.test;
import java.sql.Date;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import wangcc.entity.User;
import wangcc.service.UserService;
import wangcc.util.DateUtil;
public class Test {
public static void main(String[] args){
BeanFactory factory=new ClassPathXmlApplicationContext("application-transcation.xml");
UserService userService=(UserService) factory.getBean("userService");
User user=new User();
user.setName("w1cccc");
user.setBirthday(DateUtil.StringtoDate(DateUtil.DEFAULT_DATE, "1996-01-27"));
user.setSalary(30000.0);
userService.save(user);
}
}
2)使用aop方式
给出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" xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:property-placeholder location="classpath:db.properties"/>
<context:component-scan base-package="wangcc"></context:component-scan>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 声明事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<aop:config>
<aop:pointcut expression="execution(* wangcc.service.impl.*.*(..))" id="serviceMethod"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" propagation="REQUIRED"/>
<!-- rollback-for对于checked Exception而言 这里一般为自定义的Exception 继承Exception父类 -->
<tx:method name="*" propagation="REQUIRED" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
</beans>
我们来分析一下配置文件
首先我们关注一下aop配置代码段
<aop:config>
<aop:pointcut expression="execution(* wangcc.service.impl.*.*(..))" id="serviceMethod"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod"/>
</aop:config>
在配置中配置了pointcut和advisor,在学习Spring AOP中我们说过了通知和切点共同定义了切面。也就是说AOP切面是由pointcut和advisor共同构成。advisor通知定义了切面是什么以及何时使用,描述切面要完成的工作和何时需要执行这个工作。pointcut切入点则通知了故事发生的地点,例如某个类或某个方法。
aop配置还有另一种方法,并不包括advisor
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="userManager" class="wangcc.dao.UserManagerImpl">
</bean>
<bean id="xmlHandler" class="wangcc.aop.AdviseHandler"></bean>
<aop:config>
<aop:aspect id="aspect" ref="xmlHandler">
<aop:pointcut expression="execution(* wangcc.dao.*.find*(..))" id="pointUser"/>
<!-- 注意 before after around 三者配置的先后顺序将会影响程序的执行顺序
以pjp.proceed();为界
当before after around 顺序时
after 对应函数会在pjp.proceed();之后的代码块之前执行
当before around after 顺序时
正常流转
当after before around 顺序时
after 对应函数会在pjp.proceed();之后的代码块之前执行
当after around before 顺序时
before 对应函数会在pjp.proceed();执行之前执行
before 对应函数会在pjp.proceed();之后的代码块执行之前执行
-->
<aop:after method="doAfter" pointcut-ref="pointUser"/>
<aop:around method="doAround" pointcut-ref="pointUser"/>
<aop:before method="doBefore" pointcut-ref="pointUser"/>
<aop:after-returning method="doAfterReturn" pointcut-ref="pointUser"/>
<aop:after-throwing method="doAfterThrow" pointcut-ref="pointUser" throwing="ex"/>
</aop:aspect>
</aop:config>
</beans>
java文件没有什么改变。
3)interceptor拦截器方法
<?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:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:property-placeholder location="classpath:db.properties"/>
<context:component-scan base-package="wangcc"></context:component-scan>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 声明事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>*Service</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
</beans>
4)代理工厂配置
<?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:property-placeholder location="classpath:db.properties"/>
<context:component-scan base-package="wangcc"></context:component-scan>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 声明事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务 -->
<!-- 需要实施事务增强的目标业务BEAN -->
<bean id="userServiceTarget" class="wangcc.service.impl.UserServiceImpl">
</bean>
<!--使用事务代理工厂类为目标业务Bean提供事务增强 -->
<bean id="userServiceFactory" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" >
<property name="transactionManager" ref="transactionManager" />
<property name="target" ref="userServiceTarget" />
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
</beans>
Spring事务配置的几种方法大体介绍到这里。
三.我们再来看下事物管理的属性配置
1)事务的传播属性Propagation (事务的传播属性)
Propagation : key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用:
PROPAGATION_REQUIRED–支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS–支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY–支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW–新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED–以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER–以非事务方式执行,如果当前存在事务,则抛出异常。
ServiceA {
void methodA() {
try {
ServiceB.methodB();
} catch (SomeException) {
}
}
}
1: PROPAGATION_REQUIRED
加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务
比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,
ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA
的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被
提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚
2: PROPAGATION_SUPPORTS
如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行
3: PROPAGATION_MANDATORY
必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常,也就是如果调用他的方法不是事务的,那么久会抛出异常。
4: PROPAGATION_REQUIRES_NEW
这个就比较绕口了。 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,
那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,
他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在
两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,
如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。
5: PROPAGATION_NOT_SUPPORTED
当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,
那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。
6: PROPAGATION_NEVER
不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,
那么ServiceB.methodB就要抛出异常了。
7: PROPAGATION_NESTED
理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,
而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。
而Nested事务的好处是他有一个savepoint。
ServiceA {
/**
* 事务属性配置为 PROPAGATION_REQUIRED
*/
void methodA() {
try {
//savepoint
ServiceB.methodB(); //PROPAGATION_NESTED 级别
} catch (SomeException) {
// 执行其他业务, 如 ServiceC.methodC();
}
}
}
也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA也会回滚到savepoint点上,ServiceA.methodA可以选择另外一个分支,比如
ServiceC.methodC,继续执行,来尝试完成自己的事务。
但是这个事务并没有在EJB标准中定义。
2)Isolation Level(事务隔离等级):
隔离级别
1. 脏读(事务没提交,提前读取) :脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
SERIALIZABLE(序列化)
添加范围锁(比如表锁,页锁等,关于range lock,我也没有很深入的研究),直到transaction A结束。以此阻止其它transaction B对此范围内的insert,update等操作。
幻读,脏读,不可重复读等问题都不会发生。
REPEATABLE READ(可重复读)
对于读出的记录,添加共享锁直到transaction A结束。其它transaction B对这个记录的试图修改会一直等待直到transaction A结束。
可能发生的问题:当执行一个范围查询时,可能会发生幻读。
READ COMMITTED(提交读)
在transaction A中读取数据时对记录添加共享锁,但读取结束立即释放。其它transaction B对这个记录的试图修改会一直等待直到A中的读取过程结束,而不需要整个transaction A的结束。所以,在transaction A的不同阶段对同一记录的读取结果可能是不同的。
可能发生的问题:不可重复读。
READ UNCOMMITTED(未提交读)
不添加共享锁。所以其它transaction B可以在transaction A对记录的读取过程中修改同一记录,可能会导致A读取的数据是一个被破坏的或者说不完整不正确的数据。
另外,在transaction A中可以读取到transaction B(未提交)中修改的数据。比如transaction B对R记录修改了,但未提交。此时,在transaction A中读取R记录,读出的是被B修改过的数据。
可能发生的问题:脏读。
Dirty reads non-repeatable reads phantom reads
Serializable 不会 不会 不会
REPEATABLE READ 不会 不会 会
READ COMMITTED 不会 会 会
Read Uncommitted 会 会 会
Spring Isolation 属性一共支持五种事务设置,具体介绍如下:
l DEFAULT 使用数据库设置的隔离级别 ( 默认 ) ,由 DBA 默认的设置来决定隔离级别 .
l READ_UNCOMMITTED 会出现脏读、不可重复读、幻读 ( 隔离级别最低,并发性能高 )
l READ_COMMITTED 会出现不可重复读、幻读问题(锁定正在读取的行)
l REPEATABLE_READ 会出幻读(锁定所读取的所有行)
l SERIALIZABLE 保证所有的情况不会发生(锁表)
不可重复读的重点是修改 :
同样的条件 , 你读取过的数据 , 再次读取出来发现值不一样了
幻读的重点在于新增或者删除
同样的条件 , 第 1 次和第 2 次读出来的记录数不一样
3)Readonly
事务属性中的readOnly标志表示对应的事务应该被最优化为只读事务。
这是一个最优化提示。在一些情况下,一些事务策略能够起到显著的最优化效果,例如在使用Object/Relational映射工具(如:Hibernate或TopLink)时避免dirty checking(试图“刷新”)。
4)Timeout
在事务属性中还有定义“timeout”值的选项,指定事务超时为几秒。在JTA中,这将被简单地传递到J2EE服务器的事务协调程序,并据此得到相应的解释