学习Spring transaction 事务管理

一.在学习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. 脏读(事务没提交,提前读取) :脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。

  1. 不可重复读(两次读的不一致) :是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。
  2. 幻读 : 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。
  3. 第一类更新丢失(回滚丢失) :
    当2个事务更新相同的数据源,如果第一个事务被提交,而另外一个事务却被撤销,那么会连同第一个事务所做的跟新也被撤销。也就是说第一个事务做的跟新丢失了。
  4. 第二类更新丢失(覆盖丢失) :
    第二类更新丢失实在实际应用中经常遇到的并发问题,他和不可重复读本质上是同一类并发问题,通常他被看做不可重复读的特例:当2个或这个多个事务查询同样的记录然后各自基于最初的查询结果更新该行时,会造成第二类丢失更新。因为每个事务都不知道不知道其他事务的存在,最后一个事务对记录做的修改将覆盖其他事务对该记录做的已提交的跟新…
    数据库系统有四个隔离级别(大多数数据库默认级别为read commited)。对数据库使用何种隔离级别要审慎分析,因为
  5. 维护一个最高的隔离级别虽然会防止数据的出错,但是却导致了并行度的损失,以及导致死锁出现的可能性增加。
  6. 然而,降低隔离级别,却会引起一些难以发现的bug。

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服务器的事务协调程序,并据此得到相应的解释

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值