您的位置:首页 > 编程语言 > Java开发

5Spring事务

2016-07-09 16:57 232 查看
Spring支持的事务策略

1PlatformTransactionManager接口

java EE应用的传统事务有两种策略:全局事务和本地(局部)事务,全局事务由应用服务器管理,它是基于JTA事务;局部事务是和资源相关的,比如一个和JDBC连接关联的事务。

Spring事务策略是通过PlatformTransactionManager接口体现的,该接口是Spring事务策略的核心。Spring具体事务管理由PlatformTransactionManager接口的不同实现类来完成,应用程序面向与平台无关的接口编程,当底层采用不同的持久层技术时,系统只需要使用不同的PlatformTransactionManager实现类即可,而这种切换通常由Spring容器负责管理,应用程序既无须与具体的事务API耦合,也无需与特定实现类耦合,从而将应用和持久化技术,事务API彻底分离开来。PlatformTransactionManager代表事务管理接口,它并不知道底层到底如何管理事务,它只要求事务管理需要提供开始事务(getTransaction()),提交事务(commit())和回滚事务(rollback())三个方法。

public interface PlatformTransactionManager {

TransactionStatus getTransaction(TransactionDefinition definition)

throws TransactionException;

void commit(TransactionStatus status) throws TransactionException;

void rollback(TransactionStatus status) throws TransactionException;

}

2TransactionStatus接口

在PlactformTransactionManager接口内,包含一个getTransaction(TransactionDefinition definition)方法,该方法根据一个TransactionDefinition参数,返回一个TransactionStatus对象。TransactionStatus对象表示一个事务,TransactionStatus被关联在当前执行的线程上。TransactionStatus提供了简单的控制事务执行和查询事务状态的方法,这些方法在所有的事务API都是相同的。TransactionStatus接口的源代码如下:

public interface TransactionStatus{

//判断事务是否为新建的事务

boolean isNewTransaction();

//设置事务回滚

void setRollbackOnly();

//查询事务是否已有回滚标志

boolean isRollbackOnly();

}

/*

TransactionDefinition接口定义一个事务规则,该接口必须指定如下几个属性值:

事务隔离:当前事务和其他事务的隔离程度。例如:这个事务能否看到其他事务未提交的数据等。

事务传播:通常事务中执行的代码都会在当前事务中运行,但如果一个事务上下文已经存在,有几个选项可指定该事务性方法执行行为。

事务超时:事务在超时前能运行多久,也就是事务的最长持续时间。如果事务一直没有被提交或回滚,将在超出该时间后,系统自动回滚事务。

只读状态:只读事务不修改任何数据,在某些情况下,只读事务时非常有用的优化。

*/

下面是采用不同的持久层访问环境,及其对应的PlatformTransactionManager实现类的配置。如JDBC数据源的局部事务策略配置文件如下:

<?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: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/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

<!--定义数据源Bean,使用C3P0数据库连接池实现-->

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">

<property name="driverClass">

<value>oracle.jdbc.driver.OracleDriver</value>

</property>

<property name="jdbcUrl">

<value>jdbc:oracle:thin:username/password@127.0.0.1:1521:oral</value>

</property>

<property name="user">

<value>username</value>

</property>

<property name="password">

<value>password</value>

</property>

<!-- 连接池中保留的最小连接数。 -->

<property name="minPoolSize">

<value>0</value>

</property>

<!-- 连接池中保留的最大连接数,默认值为15。 -->

<property name="maxPoolSize">

<value>15</value>

</property>

<!-- 初始化时获得的连接数,介于minPoolSize和maxPoolSize之间,默认为3。 -->

<property name="initialPoolSize">

<value>3</value>

</property>

<!-- 最大空闲时间,${指定秒数}秒内未使用则连接被丢弃。若为0则永不丢弃。默认为0。 -->

<property name="maxIdleTime">

<value>0</value>

</property>

<!-- 当连接池中的连接耗尽时,c3p0一次同时获取的连接数。默认为3。 -->

<property name="acquireIncrement">

<value>3</value>

</property>

<!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的 statements

属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。

如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。默认为0。 -->

<property name="maxStatements">

<value>0</value>

</property>

<!-- 每${指定秒数}秒检查所有连接池中的空闲连接。默认为0。 -->

<property name="idleConnectionTestPeriod">

<value>0</value>

</property>

<!-- 定义在从数据库获取新连接失败后重复尝试的次数。默认为30。 -->

<property name="acquireRetryAttempts">

<value>30</value>

</property>

<!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效

保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试

获取连接失败后该数据源将声明已断开并永久关闭。默认为false。-->

<property name="breakAfterAcquireFailure">

<value>false</value>

</property>

<!--因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的

时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable

等方法来提升连接测试的性能。默认为false。 -->

<property name="testConnectionOnCheckout">

<value>false</value>

</property>

</bean>

<!--配置JDBC数据源的局部事务管理器,使用DataSourceTransactionManager类-->

<!--该类实现了PlatformTransactionManager接口,是针对采用数据源连接的特定实现-->

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<!--配置DataSourceTransactionManager时需要依赖注入DataSource的引用-->

<property name="dataSource" ref="dataSource"/>

</bean>

</beans>

上面我们采用局部事务管理器:必须先定义一个JDBC DataSource,然后使用Spring的DataSourceTransactionManager,并传入指向DataSource的引用。

如果采用JTA全局事务管理器,则spring配置文件如下:

<!--使用JtaTransactionManager类,该类实现PlatformTransactionManager接口-->

<!--针对采用全局事务管理的特定实现-->

<jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

JtaTransactionManager不需要知道 DataSource 和其他特定的资源,因为它将使用容器提供的全局事务管理。

从上面的配置文件可以看出,当采用Spring事务管理策略时,应用程序无需与具体的事务耦合,Spring提供两种事务管理方式:

编程式事务管理:即使利用Spring编程式事务时,程序也可以直接获取容器中的transactionManager Bean,该Bean总是PlatformTransactionManager的实例,所以可以通过该接口所提供的3个方法来开始事务,提交事务和回滚事务。

3声明式事务

声明式事务管理:无须在java程序书写任何的事务操作代码,而是通过xml文件中为业务组件配置事务代理(AOP代理的一种),AOP为事务代理所植入的增强处理也由Spring提供,在目标方法执行之前,织入开始事务;在目标方法执行之后,织入结束事务。

当使用编程式事务时,开发者使用的是Spring事务抽象(面向PlatformTransactionManager接口编程),而无需使用任何具体底层事务API,Spring事务管理将代码从底层具体的事务API中抽象出来,该抽象能以任何底层事务为基础。

Spring的编程式事务还可以通过TransactionTemplater类完成,该类提供了一个execute(TransactionCallback action)方法,可以以更加简洁的方式来进行事务操作。

当使用声明式事务,不依赖Spring或任何其他事务API。Spring的声明式事务无须任何额外的容器支持,Spring容器本身管理声明式事务。

我们都推荐采用声明式事务策略。使用声明式事务策略的优势十分明显:

声明式事务能大大降低开发者的代码书写量,而声明式事务几乎不影响应用的代码。

应用程序代码无需任何事务处理代码,可以更专注于业务逻辑的实现。

Spring则可对任何POJO的方法提供事务处理,而且Spring的声明式事务管理无需任何容器的支持,可在任何环境下使用。

声明式事务的例子

// 我们想做成事务性的服务接口:

package x.y.service;

public interface IFooService {

Foo getFoo(String fooName);

Foo getFoo(String fooName, String barName);

void insertFoo(Foo foo);

void updateFoo(Foo foo);

}

// 上述接口的一个实现

package x.y.service;

public class DefaultFooService implements IFooService {

public Foo getFoo(String fooName) {

throw new UnsupportedOperationException();

}

public Foo getFoo(String fooName, String barName) {

throw new UnsupportedOperationException();

}

public void insertFoo(Foo foo) {

throw new UnsupportedOperationException();

}

public void updateFoo(Foo foo) {

throw new UnsupportedOperationException();

}

}

例中实现类(DefaultFooService)的每个方法在其方法体中抛出 UnsupportedOperationException 的做法是为了了解事务被创建出来, 响应 UnsupportedOperationException 的抛出,然后回滚。

IFooService的前两个方法(getFoo(String) 和getFoo(String, String))必须执行在只读事务上下文中,其他的方法(insertFoo(Foo)和 updateFoo(Foo))必须执行在可读写事务上下文中。那么我们的配置文件如下:

<?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: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/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

<bean id="fooService" class="x.y.service.DefaultFooService"/>

<!-- 声明式事务通知 -->

<tx:advice id="txAdvice" transaction-manager="txManager">

<!-- the transactional semantics... -->

<tx:attributes>

<!-- 配置以get开头的方法只读 -->

<tx:method name="get*" read-only="true"/>

<!-其他方法采用默认的方式 -->

<tx:method name="*"/>

</tx:attributes>

</tx:advice>

<!-- 配置切入点它匹配 FooService接口定义的所有操作 -->

<aop:config>

<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.IFooService.*(..))"/>

<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>

</aop:config>

<!-- 配置数据源 -->

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>

<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>

<property name="username" value="scott"/>

<property name="password" value="tiger"/>

</bean>

<!-- 配置局部事务管理器-->

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="dataSource"/>

</bean>

<!-- other <bean/> definitions here -->

</beans>

上面的配置首先我们要把一个服务对象('fooService' bean)做成事务性的, 我们想施加的事务语义封装在<tx:advice/>定义中,<tx:advice/> “把所有以 'get' 开头的方法看做执行在只读事务上下文中,其余的方法执行在默认语义的事务上下文中,其中的 'transaction-manager' 属性被设置为一个指向局部事务管理器DataSourceTransactionManager(这里指'txManager')。我们用一个通知器(advisor)把fooServiceOperation切入点与
'txAdvice' 绑定在一起。当IFooService接口定义的所有操作执行时,'txAdvice'定义的通知逻辑将被执行。

这样完成了为'fooService' bean创建一个代理对象,这个代理对象被装配了事务通知,所以当它的相应方法被调用时,一个事务将被启动、挂起、被标记为只读,或者其它(根据该方法所配置的事务语义)。

4回滚

如何使用一个简单的声明式配置来控制事务的回滚。在Spring框架的事务架构里指出当context的事务里的代码抛出 Exception 时事务进行回滚。

注意Spring框架的事务基础架构代码将默认地是针对unchecked exception回滚,也就是默认对RuntimeException()异常极其子类进行事务回滚。当抛出一个 RuntimeException 或其子类例的实例时。(Errors也一样默认地标识事务回滚。)从事务方法中抛出的Checked exceptions将不被标识进行事务回滚。不论是否配置了rollback-for对于unchecked exception回滚Spring始终会回滚。

可以配置哪些 Exception类型将被标识进行事务回滚。下面的XML配置片断里示范了如何配置一个用于回滚的checked exception、应用程序特定的Exception类型:

在txAdive中增加rollback-for,里面写自己的exception:NoProductInStockException,

<tx:advice id="txAdvice" transaction-manager="txManager">

<tx:attributes>

<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>

<tx:method name="*"/>

</tx:attributes>

</tx:advice>

有时候你不想在异常抛出的时候回滚事务,就可以使用“不回滚规则”。 在下面的例子中,我们告诉Spring 框架即使遇到没有经过处理的InstrumentNotFoundException异常,也不要回滚事务。

<tx:advice id="txAdvice" transaction-manager="txManager">

<tx:attributes>

<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>

<tx:method name="*"/>

</tx:attributes>

</tx:advice>

下面这种配置中,除了InstrumentNotFoundException这种类型的异常不会导致事务回滚以外,其他任何类型的异常都会。

<tx:advice id="txAdvice" transaction-manager="txManager">

<tx:attributes>

<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>

</tx:attributes>

</tx:advice>

你有许多服务对象,你想为他们分别设置完全不同的事务语义。 在Spring中,你可以通过分别定义特定的 <aop:advisor/>元素, 让每个advisor采用不同的 'pointcut' 和 'advice-ref' 属性,来达到目的。如:

<aop:config>

<aop:pointcut id="defaultServiceOperation"

expression="execution(* x.y.service.*Service.*(..))"/>

<aop:pointcut id="noTxServiceOperation"

expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>

<aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>

<aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>

</aop:config>

<bean id="fooService" class="x.y.service.DefaultFooService"/>

<bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>

<tx:advice id="defaultTxAdvice" transaction-manager="txManager">

<tx:attributes>

<tx:method name="get*" read-only="true"/>

<tx:method name="*"/>

</tx:attributes>

</tx:advice>

<tx:advice id="noTxAdvice">

<tx:attributes>

<tx:method name="*" propagation="NEVER"/>

</tx:attributes>

</tx:advice>

</beans>

5<tx:advice/>有关的配置

通过<tx:advice/>标签来指定不同的事务性设置。默认的 <tx:advice/>设置如下:

事务传播设置 是 REQUIRED

隔离级别是DEFAULT

事务是 读/写

事务超时默认是依赖于事务系统的,或者事务超时没有被支持。

任何 RuntimeException 将触发事务回滚,但是任何checked Exception将不触发事务回滚。

<tx:method/>各种属性设置总结如下:

name,必需属性;与事务属性关联的方法名。通配符(*)可以用来指定一批关联到相同的事务属性的方法。

propagation,默认值是REQUIRED,事务传播行为。

isolation,默认值DEFAULT,事务隔离级别。

timeout,默认是-1,事务超时的时间(以秒为单位)。

read-only,默认false,事务是否只读。

rollback-for,将被触发进行回滚的异常,以逗号分开。如:'com.foo.MyBusinessException,ServletException'

no-rollback-for,不被触发进行回滚的异常,以逗号分开。 如:'com.foo.MyBusinessException,ServletException'

6使用@Transactional

除了基于XML文件的声明式事务配置外,也可以采用基于注解式的事务配置方法。

首先在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: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/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

<!-- 服务对象('fooService' bean)将做成事务性的 -->

<bean id="fooService" class="x.y.service.DefaultFooService"/>

<!-- 指明通过注解声明事务,并有txManager来管理事务-->

<tx:annotation-driven transaction-manager="txManager"/>

<!-- 配置事务管理 -->

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="dataSource"/>

</bean>

<!--或者

<bean id="txManager"

class="org.springframework.orm.hibernate3.HibernateTransactionManager">

<property name="sessionFactory" ref="sessionFactory"/>

</bean>

-->

<!-- other <bean/> definitions here -->

</beans>

下面我们将DefaultFooService类做成事务性的:

package x.y.service;

@Transactional

public class DefaultFooService implements IFooService {

public Foo getFoo(String fooName) {

throw new UnsupportedOperationException();

}

public Foo getFoo(String fooName, String barName) {

throw new UnsupportedOperationException();

}

public void insertFoo(Foo foo) {

throw new UnsupportedOperationException();

}

public void updateFoo(Foo foo) {

throw new UnsupportedOperationException();

}

}

@Transaction注解可以被应用于接口定义和接口方法,类定义和类的public方法上。但是只是使用@Transactional注解并不会启用事务行为,它仅仅是一种元数据,只要上面的<tx:annotation-driven transaction-manager="txManager"/>元素的出现才开启了事务行为。

Spring团队的建议是你只在具体的类上使用 @Transactional 注解, 而不要注解在接口上。你当然可以在接口(或接口方法)上使用 @Transactional 注解, 但是这只有在你使用基于接口的代理时它才会生效。因为注解是 不能继承的,这就意味着如果你正在使用基于类(CGLib)的代理时,事务的设置将不能被基于类的代理所识别。

注意:在代理模式下(默认的情况),只有从代理传过来的"外部"方法调用才会被拦截。 这就意味着"自我调用"是不会触发事务的,比如说,一个在目标对象中调用改目标对象其他方法的方法是不会触发一个事务的,即使这个方法被标记为 @Transactional!

<tx:annotation-driven/>的设置

属性transaction-manager,默认取值transactionManager,使用事务管理器的名字,

属性mode,默认取值proxy,默认的模式"proxy"会用Spring的AOP框架来代理注解过的bean。

在<tx:annotation-driven/>元素上的"proxy-target-class" 属性 控制了有什么类型的事务性代理会为使用@Transactional来注解的类创建代理。如果"proxy-target-class"属性被设为"true",那么基于类的代理就会被创建。如果"proxy-target-class"属性被设为"false"或者没设,那么会创建基于接口的标准JDK代理。

与<aop:aspectj-autoproxy />元素上的"proxy-target-class" 属性一个意思。

在多数情况下,方法的事务设置将被优先执行。在下列情况下,例如:DefaultFooService 类在类的级别上被注解为只读事务,但是这个类中的 updateFoo(Foo) 方法的 @Transactional 注解的事务设置将优先于类级别注解的事务设置。

@Transactional(readOnly = true)

public class DefaultFooService implements FooService {

public Foo getFoo(String fooName) {

// do something

}

@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)

public void updateFoo(Foo foo) {

// do something

}

}

@Transactional有关的设置

@Transactional 注解的各种属性设置总结如下

propagation,枚举型:Pragation,可选的传播性设置。如 @Transactional(readOnly=false, propagation=Propagation.REQUIRES_NEW)

isolation,枚举型:Isolation,可选的隔离性级别(默认值ISONLATION_DEFAULT)。

readOnly,布尔型,设置事务的读写型。

timeout,int型(单位秒),事务超时时间。

rollbackFor,一组Throwable的子类,一组异常类,遇到时必须进行回滚。默认情况下checked exceptions不进行回滚,仅unchecked exceptions(即RuntimeException的子类)才进行事务回滚。

rollbackForClassname,一组Throwable的子类名字,一组异常类名,遇到时进行回滚。

noRollbackFor,一组Throwable的子类;一组异常类,遇到时不进行回滚。

noRollbackForClassname,一组Throwable的子类名字;一组异常类名字,遇到时不进行回滚。

默认的@Transactional设置如下:

事务传播设置是:PROPAGATION_REQUIRED

事务隔离级别是:ISOLATION_DEFAULT

事务是: 读/写

事务超时:默认是依赖于事务系统的,或者事务超时没有被支持。

任何RuntimeException,将触发事务回滚,但是任何checked Exception将不触发事务回滚

这些默认的设置当然也是可以被改变的。

@Transactional

在需要事务管理的地方加@Transactional 注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。

但是,Spring团队的建议是你在具体的类(或类的方法)上使用,@Transactional注解。在多数情形下,方法的事务设置将优先于类的事务设置执行。

propagation,可选的传播性设置,@Transactional(propagation = Propagation.REQUIRED)

Isolation,可选的隔离性级别(默认值:ISOLATION_DEFAULT)

@Transactional(

propagation = Propagation.REQUIRED,

isolation = Isolation.DEFAULT,

rollbackFor = Exception.class

)

只读型事务

@Transactional(readOnly=true)

事务超时int型(以秒为单位)

@Transactional(timeout=100)

rollbackFor,抛出Exception异常时,记录回滚。取值为一组异常类,遇到时必须进行回滚。默认情况下checked exceptions不进行回滚,仅unchecked exceptions(即RuntimeException的子类)才进行事务回滚。

@Transactional(rollbackFor = Exception.class)

public void test13() throws Exception {

}

noRollbackFor,抛出Exception异常时,记录不回滚。

@Transactional(noRollbackFor = CustomException.class)

public void test14() throws Exception {

}

//事务传播属性

@Transactional(propagation=Propagation.REQUIRED)//如果有事务,那么加入事务,没有的话新创建一个(默认)

@Transactional(propagation=Propagation.NOT_SUPPORTED)//这个方法不开启事务,以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

@Transactional(propagation=Propagation.REQUIREDS_NEW)//不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务。(新的事务执行过程遇到异常不会影响老的事务的执行)

@Transactional(propagation=Propagation.MANDATORY)//必须在一个已有的事务中执行,否则抛出异常

@Transactional(propagation=Propagation.NEVER)//不能在一个事务中执行,就是当前必须没有事务,否则抛出异常

@Transactional(propagation=Propagation.SUPPORTS)//其他bean调用这个方法,如果在其他bean中声明了事务,就是用事务。没有声明,就不用事务。

@Transactional(propagation=Propagation.NESTED)//如果一个活动的事务存在,则运行在一个嵌套的事务中,如果没有活动的事务,则按照REQUIRED属性执行,它使用一个单独的事务。这个事务拥有多个回滚的保存点,内部事务的回滚不会对外部事务造成影响,它只对DataSource TransactionManager事务管理器起效。

@Transactional(propagation=Propagation.REQUIRED,readOnly=true)//只读,不能更新,删除

@Transactional(propagation=Propagation.REQUIRED,timeout=30)//超时30秒

@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT)//数据库隔离级别

7Spring事务传播特性

在Spring中针对传播特性的多种配置我们大多数情况下只用其中的一种:PROPGATION_REQUIRED,这个配置项的意思是说当我调用service层的方法的时候开启一个事务(具体调用那一层的方法开始创建事务,要看你的aop的配置),那么在调用这个service层里面的其他的方法的时候,如果当前方法产生了事务就用当前方法产生的事务,否则就创建一个新的事务。这个工作使由Spring来帮助我们完成的。

Spring在TransactionDefinition接口中规定了7种类型的事务传播行为:

PROPAGATION_REQUIRED,如果没有事务,就新建一个事务,如果已经存在一个事务中,就加入到这个事务中。这是最常用的选择。

PROPAGATION_SUPPORTS,支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY,使用当前的事务,如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW,新建事务,如果当前存在事务,把当前事务挂起。新建的事务执行完毕,当前事务再继续执行。

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

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

当使用PROPAGATION_NESTED时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制。

PROPAGATION_REQUIRES_NEW和PROPAGATION_NESTED的区别:

PROPAGATION_REQUIRES_NEW启动一个新的,不依赖于环境的"内部"事务,这个事务将被完全commited或rolled back而不依赖于外部事务,它拥有自己的隔离范围,自己的锁等,当内部事务开始执行时,外部事务将被挂起,内部事务结束时,外部事务将继续执行。

PROPAGATION_NESTED开始一个"嵌套事务,它是已经存在事务的一个真正的子事务,嵌套事务开始执行时,它将取得一个savepoint,如果这个嵌套事务失败,将回滚到此savepoint,嵌套事务是外部事务的一部分,只有外部事务结束后它才会被提交。

所以它们两个最大的区别是PROPAGATION_REQUIRES_NEW完全是一个新的事务,而PROPAGATION_NESTED则是外部事务的子事务,如果外部事务commit,嵌套事务也会被commit,这个规则同样适用于roll back。

**********************************

事务是现代数据库理论中的核心概念之一,如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务。

当所有的步骤像一个操作一样被完整地执行,我们称该事务被提交。

由于其中的一部分或多步执行失败,导致没有步骤被提交,则事务必须回滚(到最初的系统状态)。

事务必须服从ISO/IEC所制定的ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability):

事务的原子性

整个数据库事务是不可分割的工作单元,只有事务中所有的操作执行成功,才算整个事务成功,事务中任何一个SQL语句执行失败,那么已经执行的陈宫的SQL也必须撤销,数据库状态应该退回到执行事务前的状态。

一致性

数据库事务不能破坏关系数据的完整性以及义务逻辑上的一致性。

隔离性

表示在事务执行过程中对数据的修改,在事务提交之前对其他事务不可见。在并发环境中,当不同的事务通知操作相同的数据时,每个事务都有各自的完整数据空间。数据库管理系统采用锁机制实现事务隔离性,当多个事务同时更新数据库中相同的数据时,只容许持有锁的事务能更新改数据。

持久性表示只要事务成功结束,它对数据库所做的更新就必须永久保存下来。

并发问题:

脏读:指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。

幻读:指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

不可重复读: 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

数据库系统用锁实现事务的隔离性

共享锁:共享锁用于读数据操作,它是非独占的,容许其他事务同时读取其锁定的资源,但不容许其他事务更新它。

加锁条件:当一个事务执行SELECT语句时,数据库系统会为这个事务分配一把共享锁,来锁定被查询的数据。

解锁条件:在默认情况下,数据被读取后,数据库系统立即解除共享锁。

与其他锁的兼容性:如果数据库资源上放置了共享锁,还能再放置共享锁和更新锁。

更新锁:更新锁在更新操作的初始化阶段用来锁定可能要被修改的资源,

加锁条件:当一个事务执行UPDATE语句时,数据库系统会先为事务分配一把更新锁。

解锁条件:当读取数据完毕,执行更新操作时,会把更新升级为独占锁。

更新锁与共享锁是兼容的,一个资源可以同时放置更新锁和共享锁。但是最多只能放置一把更新锁。这样,当多个事务更新相同的数据时,只有一个事务能获得更新锁,然后再把更新升级为独占锁,其他事务必须等到前一个事务结束后,才能获得更新锁。

独占锁:适用于修改数据的场合,它所修改的资源,其他事务不能读取也不能修改。

加锁条件:当一个事务执行INSERT,UPDATE或DELETE语句时,数据库系统会自动对SQL语句操纵的数据库资源使用独占锁,如果改数据资源已经有其他锁存在时,无法对其再放置独占锁。

解锁条件:独占锁一直到结束才能被解除。

数据库的隔离级别:

串行化(Serializable),事务具有排他的读写权限,不同的事务不能同时读取或修改相同的数据,此级别可以避免脏读,不可重复读和幻读。这是最严格的隔离级别。

可重复读(Repeated Read),事务不能修改其他事务读取的数据,此级别可以避免脏读和不可重复读,但是不能避免幻读的发生。

读已提交数据(Read commited),事务不能读取未被提交的数据,正在由其他事务进行修改的数据不能读取。此级别可以避免脏读,但是不能避免不可重复读和幻读的发生。

读未提交数据(Read Uncommited),事务可以读取未被提交的数据,此级别可能出现脏读,不可重复读和幻读现象。

什么是事务的传播特性

在SSH项目中,我们一般都是将事务设置在Service层那么当我们调用Service层的一个方法的时候它能够保证我们的这个方法中执行的所有的对数据库的更新操作保持在一个事务中,在事务层里面调用的这些方法要么全部成功,要么全部失败。那么事务的传播特性也是从这里说起的。如果我们在这个Service层的这个方法中除了调用Dao层的方法之外,还调用了其他Service层的方法,我们必须保证

在我们方法里调用的其他Service层的方法于我们本身的方法处在一个事务中,否则就不能保证事务的一致性。事务传播的特性就是解决这个问题。在Spring我们将事务传播特性设置为PROPGATION_REQUIRED,就可以解决上面的问题,保证在它们在一个事务中。

******************

Spring的声明式事务,AOP是一般针对service层(业务层),有些异常如DataAccessException异常,在Service和dao层捕获不到。只有在Service层外捕获,因为这些异常被Spring处理了,有些异常如NumberFormatException,我们就可以捕获到。

*********************

JTA

Java Transaction API(Java事务API)JTA主要用于分布式的多个数据源的事务,提供一个"事务处理监视器(TPM)"来管理和协调这些数据源之间的事务操作,它必须执行两阶段提交(2PC)协议;JDBC的Connection提供的单个数据源的事务(本地事务);

后者因为只涉及到一个数据源,所以其事务可以由数据库自己单独实现。JTA事务因为其分布式和多数据源的特性,其最大的特点是调用UserTransaction接口的begin,commit和rollback方法来完成事务范围的界定,事务的提交和回滚。JTA Transaction可以实现同一事务对应不同的数据库,但是它仍然无法实现事务的嵌套。此外,JTA的UserTransaction通常需要从JNDI获得,这意味着我们为了JTA,需要同时使用JNDI 和 JTA。

两阶段提交(2PC)协议

准备阶段:TPM向所有RM(数据库)确认状态,是否可以提交或回滚。

提交阶段:TPM确认提交之后,向所有RM发出提交或回滚指令。

UserTransaction接口

javax.transaction.UserTransaction接口使用应用能够变成控制事务边界(即什么时候开始,挂起,重新开始,提交或回滚),这个几口可以由java客户端程序和EJB来使用。

java客户端程序首先使用JNDI来获得UserTransaction对象的引用,然后使用该对象的方法完成事务。

JPA

JPA(Java Persistence API)是Sun官方提出的Java持久化规范。JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据。他的出现主要是为了简化现有的持久化开发工作和整合ORM技术,结束现在Hibernate,TopLink,JDO等ORM框架各自为营的局面。值得注意的是,JPA是在充分吸收了现有Hibernate,TopLink,JDO等ORM框架的基础上发展而来的,具有易于使用,伸缩性强等优点。从目前的开发社区的反应上看,JPA受到了极大的支持和赞扬,其中就包括了Spring与EJB3.0的开发团队。着眼未来几年的技术走向,JPA作为ORM领域标准化整合者的目标应该不难实现。

JPA的优势

JPA并不是一项技术,而是一种标准,因为JPA只是一套接口,本身不能完成任何事情。JPA只是规范了Java持久化的官方标准。JPA有以下几个优点:

可持久化Java对象。JPA能够直接持久化复杂的Java对象,并能够使用JPQL语言进行复杂的查询。JPQL是JPA专用的查询语言,是类似于SQL的面向对象的查询语言。

使用简单。JPA使用注释(Annotation)定义Java对象与关系数据库之间的映射,而传统的ORM多使用xml配置文件。JPA使用起来比ORM要方便。使用JPA不用关注底层使用什么数据库。

规范标准化。JPA是JCP组织发布的,是Java官方规定的统一的API。目前已经有多种框架实现JPA标准。使用了JPA的系统可以自由选择遵循JPA标准的框架,并能够自由更换。

事务性、大数据量。JPA底层使用关系数据库进行存储,因此具备关系数据库的特点,例如事务性、数据完整性、并发访问、大数据量等。

目前已经有多个ORM开源框架支持JPA,如Hibernate,TopLink,OpenJPA等等。

从javaEE5规范开始将对象持久化从EJB中分离出来形成单独的API框架,简称JPA。JPA2.0是基于J2EE6的。

JAP与Hibernate的关系

JPA和Hibernate之间的关系,可以简单的理解为JPA是标准接口,Hibernate是实现。那么Hibernate是如何实现与JPA的这种关系的呢。Hibernate主要是通过三个组件来实现的,及hibernate-annotation、hibernate-entitymanager和hibernate-core。

hibernate-annotation是Hibernate支持annotation方式配置的基础,它包括了标准的JPA annotation以及Hibernate自身特殊功能的annotation。

hibernate-core是Hibernate的核心实现,提供了Hibernate所有的核心功能。它依赖hibernate-annotation以及hibernate-jpa-2.0-api。

hibernate-entitymanager实现了标准的JPA,可以把它看成hibernate-core和JPA之间的适配器,它并不直接提供ORM的功能,而是对hibernate-core进行封装,使得Hibernate符合JPA的规范

目前hibernate3.6开始支持JPA2。

*****************

DataSourceTransactionManager 与HibernateTransactionManager的不同:

HibernateTransactionManager能够保证 jdbctemplate所获得con与hibernate获得的con在同一个事务中,所以使用HibernateTransactionManager进行事务管理能够保证在程序中hibernateTemplate 和jdbcTemplate执行的方法处于同一事务。

DataSourceTransactionManager 却不行,他只管理从dataSource数据源获得的con的事务,无法管理hibernate的Session中con的事务。

*********************************

Spring 配置动态数据源

Spring的DynamicDataSource是继承与AbstractRoutingDataSource,而 AbstractRoutingDataSource又是继承于 org.springframework.jdbc.datasource.AbstractDataSource,AbstractDataSource 实现了统一的DataSource接口,所以DynamicDataSource同样可以当一个DataSource使用。

首先配置两个数据库

<bean id="dataSourceA" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

<property name="driverClassName" value="${jdbc.driverClassName}" />

<property name="url" value="${jdbc.url}" />

<property name="username" value="${jdbc.username}" />

<property name="password" value="${jdbc.password}" />

<property name="maxActive" value="${jdbc.maxActive}" />

<property name="maxWait" value="${jdbc.maxWait}" />

</bean>

<bean id="dataSourceB" class="org.apache.commons.dbcp.BasicDataSource">

<property name="driverClassName" value="${vjdbc.driverClassName}" />

<property name="url" value="${vjdbc.url}" />

<property name="username" value="${vjdbc.username}" />

<property name="password" value="${vjdbc.password}" />

<property name="maxActive" value="${jdbc.maxActive}" />

<property name="maxWait" value="${jdbc.maxWait}" />

</bean>

再配置一个dataSource 管理 key 值和value值对应,默认选择dataSourceA ,其他配置按照正常的spring mvc 配置即可。

<bean id="dataSource" class="com.broadengate.util.DynamicDataSource">

<!-- 通过key-value的形式来关联数据源 -->

<property name="targetDataSources">

<map key-type="java.lang.String">

<entry value-ref="dataSourceA" key="dataSourceA"></entry>

<entry value-ref="dataSourceB" key="dataSourceB"></entry>

</map>

</property>

<property name="defaultTargetDataSource" ref="dataSourceA" >

</property>

</bean>

sessionFactory 中使用 dataSource做数据源(和配置单个数据源一样)。

<bean id="sqlSessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

<property name="dataSource" ref="dataSource" />

....

</bean>

新建一个DynamicDataSource类继承AbstractRoutingDataSource。

public class DynamicDataSource extends AbstractRoutingDataSource{

public static final String DATA_SOURCE_A = "dataSourceA";

public static final String DATA_SOURCE_B = "dataSourceB";

private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

public static void setCustomerType(String customerType) {

contextHolder.set(customerType);

}

public static String getCustomerType() {

return contextHolder.get();

}

public static void clearCustomerType() {

contextHolder.remove();

}

@Override

protected Object determineCurrentLookupKey() {

return getCustomerType();

}

}

最后使用切换数据源,这一步必须在进入业务层之前切换,也就是controller类中进行切换。

DynamicDataSource.setCustomerType(DynamicDataSource.DATA_SOURCE_A);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring