Spring事务管理

一。事务回顾

1.1。什么是事务

事务指的是逻辑上的一组操作,这组操作要么全部成功,要么全部失败。

异常情况发生,需要保证:【1】张三将钱转出,李四收到钱。【2】张三钱未成功转出,李四也未收到钱。

1.2。事务特性

事务有4大特性:原子性,一致性,隔离性,持久性。

1.2.1。原子性

原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

//物理中强调原子是最小单位,不可分割。如张三向李四转钱,张三转出与李四转入这一组操作具有原子性,不可分割。

1.2.2。一致性

一致性指事务前后数据的完整性必须保持一致。

如:张三有2000元,李四有2000元。共计4000元。张三向李四转1000元,无论转账成功与否,合计一定要为4000元。保证数据前后一致性

1.2.3。隔离性

隔离性指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离。

如上图所示:事务A修改了张三的账户记录,此时事务B也修改张三的账户。此时会出现问题:重复修改或是事务A的修改被事务B覆盖。

所以需要保证事务A不受事务B干扰。

解决方案:数据库都会设置一些事务的隔离级别,可通过数据库的事务隔离级别控制一个事务不被另一个事务所干扰。

1.2.4。持久性

持久性是指一个事务一旦被提交,它对数据库数据的改变就是永久性的,即使数据库发生故障也不应该对其有任何影响。

二。Spring事务管理的一组API

2.1。Spring接口

以上,PlatformTransactionManager事务管理器是用来真正管理事务的一个接口。它里面包含了像事务的提交,回滚等信息。

事务管理器 & 事务定义信息 & 事务具体运行状态之间是有联系的:

2.2。PlatformTransactionManager接口(平台事务管理器)

Spring为不同的持久层框架提供了不同的PlatformTransactionManager接口实现

2.3。TransactionDefinition定义事务隔离级别(隔离,传播,超时,只读)isolation

若不考虑事务隔离性,会引发安全问题如下:

1。脏读:

一个事务读取了另一个事务改写但还未提交的数据。如果这些数据被回滚,则读到的数据是无效的。导致查询结果不一致。

2。不可重复读:

一个事务读取了另一个事务改写又提交的数据,导致多次读取同一数据返回的结果有所不同。

3。幻读/虚读:

一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录。

隔离级别就是用来解决脏读,不可重复读及幻读问题的。

事务的4种隔离级别:级别低-》高(READ_UNCOMMITED->SERIALIZABLE)

注意:SERIALIZABLE是串行的,不可能出现并发情况。因此不会发生脏读,不可重复读及幻读情况。

Spring提供default默认隔离级别,即数据库用什么隔离级别,default就是什么隔离级别。

 mysql默认采用REPEATABLE_READ隔离级别

   oracle默认采用READ_COMMITED隔离级别

2.4。TransactionDefinition定义事务的传播行为(隔离,传播,超时,只读)

1。什么是事务的传播(propagation)行为?

事务的传播行为主要用来解决一些问题,

此时出现一个复杂情况:调用Service1.aaa()与Service2.bbb()才能够完成一个业务。

此时Service1.aaa()&Service2.bbb()均有事务,那么事务应用哪个?应用事务的传播行为。

事务的传播行为:用于解决业务层方法之间的调用,事务是如何进行传递的。

2。事务的7种传播行为(3类,重点记第1个)

说明:

1。PROPAGATION_REQUIRED:支持当前事务,如果存在,则用当前事务,不存在,则创建一个新的事务。

如上Service1.aaa()与Service2.bbb()。若Service2.bbb()定义传播行为是PROPAGATION_REQUIRED,

当Service1.aaa()有事务,则Service2.bbb()使用Service1.aaa()事务。即它们在同一事务中。要么均执行成功,要么均回滚。

当Service1.aaa()没有事务,则Service2.bbb()会创建一个新的事务。则Service2.bbb()事务回滚不会影响Service1.aaa

以下三种事务为同一组事务类型:

PROPAGATION_REQUIRED:支持当前事务,如果不存在则创建一个。

PROPAGATION_SUPPORTS:支持当前事务,如果不存在,则不使用事务。

PROPAGATION_MANDATORY:支持当前事务,如果不存在,则抛出异常。

相同点:

均为支持当前事务,即如果Service1.aaa有事务,则使用Service1.aaa的事务。

不同点:

PROPAGATION_REQUIRED:当Service1.aaa没有事务时,则新创建一个事务。

PROPAGATION_SUPPORTS:当Service1.aaa没有事务时,则不使用事务(即始Service.bbb有事务也不生效)。

PROPAGATION_MANDATORY:当Service1.aaa没有事务时,则抛出异常。

2。PROPAGATION_REQUIRES_NEW:如果有事务存在,则挂起当前事务,创建一个新的事务。

如上Service1.aaa()与Service2.bbb()。若Service2.bbb()定义传播行为是PROPAGATION_REQUIRES_NEW,

当Service1.aaa()有事务,则Service1.aaa事务会被挂起,Service2.bbb会新起一个事务。即它们不在同一事务中。

以下三种事务为同一组事务类型:

PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 

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

相同点:

嵌套的方法均不在同一个事务中,若当前(Service1.aaa)存在事务,则将该事务挂起。

不同点:

PROPAGATION_REQUIRES_NEW:创建一个新的事务。(Service2.bbb创建一个新的事务)

PROPAGATION_NOT_SUPPORTED:不使用事务。(Service2.bbb不使用事务)

PROPAGATION_NEVER:抛出异常。(Service2.bbb抛出异常)

3。PROPAGATION_NESTED:如果当前事务存在,则嵌套事务执行(复杂)

当Service1.aaa()有事务时,执行完Service1.aaa()时,会使用事务设置一个保存点(savepoint)。再执行Service2.bbb()时,若没有异常,则一起提交,

若有异常,则根据自定义设置,可以回滚到事务保存点,也可以回滚到最初始状态。可自己控制。

2.3。TransactionStatus接口(事务状态)

三。转账环境搭建

3.1。Spring支持两种方式事务管理:

1。编程式的事务管理

(1)。在实际应用中很少使用。

(2)。通过TransactionTemplate手动管理事务

2。使用XML配置声明式事务

(1)。开发中推荐使用(代码侵入性最小)

(2)。Spring的声明式事务是通过AOP实现的。

3.2。转账环境准备

1。数据库准备

2。创建Web项目

引入相应的JAR包及配置文件。

JAR包:

(1)。连接数据库需要引入数据库驱动包,连接池(c3p0)相关JAR包。

(2)。Spring相关jar包

(3)。日志包

配置文件:

(1)。log4j.properties:日志记录

(2)。jdbc.properties:数据库连接配置文件

(3)。applicationContext.xml:Spring核心文件

创建package,写业务逻辑:

(1)定义interface/AccountService:业务层接口

(2)定义实现类/AccountServiceImpl:业务层实现类,调用DAO层

(3)操作数据库Dao层接口

(4)DAO层接口实现类

(5)Spring完成一些相关配置

【1】。连接数据库(引入外部属性文件)& c3p0连接池

【2】配置业务层类

【3】配置DAO类:在DAO里面直接注入连接池即可。只要给它创建连接池,它就会为我们创建JDBC的模版。

此时即可以在Dao的实现类中进行编写,操作数据库。

(6)创建测试类

模拟service实现类异常,依然写入成功。扣钱与加钱操作应在同一个事务中。需要进行相应的事务管理。

四。Spring的编程式事务管理

1。在Spring核心配置文件加入代码:

上述:

真正进行事务管理的类是transactionManager,所以需要将真正事务管理的类给我们的transactionTemplate(模版)。

将底层代码简化。(<property name="transactionManager" ref="transactionManager">)

编程式事务管理:就是需要我们在使用事务时手动编写代码。

2。业务层类需要进行事务管理AccountServiceImpl。加入代码

3。Spring配置文件注入事务管理模版

4。业务层类AccountServiceImpl,转出与转入应该在一个事务当中。

修改如下所示:

再执行测试类进行测试。发现转入与转出被绑订到一个事务中,要么全部成功,要么全部失败。

五。Spring的声明式事务管理

Spring声明式事务管理是基于AOP的思想完成的。即再需要执行转账前与转账后需要做一些事情,正是AOP思想。

1。引入AOP及AOP联盟的包。(AOP思想本身不是Spring提出的,而是由AOP联盟提出的)

2。创建声明式事务测试类:(web/service/dao同原始)

3。Spring核心配置文件配置事务管理器

5.1。声明式事务管理方式一:基于TransactionProxyFactoryBean方式

配置业务层代理,即可以对业务层进行增强。(spring传统方式TransactionProxyFactoryBean事务代理工厂类)

说明:

1。业务层代理是对service 进行增强。

2。代理类对目标类要产生代理增强,如何增强,即对事务的一个增强。

3。事务管理器真正管理事务。如何管理,定义事务的一些属性,如传播行为,隔离级别等。

若属性增加readOnly,则代表事务为只读的,不能被修改等操作。一旦修改则抛出异常(上述转账)

若属性增加+Exception(+具体异常名称),则上述先转出操作成功执行,异常,后续转入操作不执行。

此时业务层不需要修改,因为采用的是AOP的思想。

需要修改点。测试类:(不能注入service,因为service是没有被增强的,不进行事务管理的。需要注入service代理类,才可以进行事务管理)

总结:

上述不建议使用,因为每增加一个Service就要配置相应的TransactionProxyFactoryBean,这样使得XML旁大。不便于维护。

5.2。声明式事务管理方式二:基于AspectJ的XML方式(公司应用)

AspectJ是Spring在进行AOP开发中,为了简化AOP编程的一个内容。AspectJ本身是一个开源的第三方的一个AOP开发框架。

1。引入AspectJ相应JAR开发包,包括AspectJ和Spring&AspectJ整合包。

2。只需要修改spring配置文件,而不需要修改其他。

(上述方式一需要修改测试类service注入为代理注入,此处不需要。为自动代理。类生成过程中本身就增强了,即为代理对象。不需要再注入代理对象。)

<tx_attributes>属性有如下配置:

项目实例:--主从分离

Step0:读取数据源属性文件。在Spring配置文件中引入属性文件

 <util:properties id="baseConfig" location="classpath:config.properties" />
<util:properties id="imConfig" location="classpath:important.properties"/>

在属性文件config.properties中配置相应数据源连接信息

【1】。config.properties定义

 jdbc_trace.master01.url=${jdbc_url}
jdbc_trace.slave01.url=${jdbc_url}

【2】。pom.xml中配置mysql数据源

 <profiles>
<profile>
<id>development</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<!--mysql数据源配置-->
<jdbc_url><![CDATA[jdbc:mysql://192.168.x.x:3306/dbName]]></jdbc_url>
<!--mysql第二个数据源配置-->
<jdbc_bigdata_url><![CDATA[jdbc:mysql://192.168.y.y:3306/dbName]]></jdbc_bigdata_url>
<jdbc_username>ocsuser</jdbc_username>
<jdbc_password>ocspasswd</jdbc_password>
</properties>
</profile>
</profiles>

【3】。important.properties定义

 jdbc_trace.master01.username=mysql_rw
jdbc_trace.master01.password=mysql_pwd
jdbc_trace.slave01.username=mysql_rw
jdbc_trace.slave01.password=mysql_pwd

Step1:定义JDBC数据源

【1】主库dataSource,配置c3p0连接池

 <!-- masterDataSource -->
<bean id="master01DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="#{baseConfig['jdbc_trace.master01.url']}"/>
<!--<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/escort" />-->
<property name="user" value="#{imConfig['jdbc_trace.master01.username']}"/>
<property name="password" value="#{imConfig['jdbc_trace.master01.password']}"/>
<property name="minPoolSize" value="5"/>
<property name="maxPoolSize" value="20"/>
<!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime" value="300"/>
<property name="acquireIncrement" value="2"/>
<property name="maxStatements" value="1000"/>
<property name="initialPoolSize" value="2"/>
<property name="idleConnectionTestPeriod" value="240"/>
<property name="acquireRetryAttempts" value="30"/> <!--定义所有连接测试都执行的测试语句,比默认配置效率高-->
<property name="preferredTestQuery" value="select 1" />
<!--每过一段时间检查所有连接池中的空闲连接。【单位为秒】Default 0 (0代表不探测)
它保证连接池会每隔一定时间对空闲连接进行一次测试,
从而保证有效的空闲连接能每隔一定时间访问一次数据库
-->
<property name="IdleConnectionTestPeriod" value="55"/>
<!-- 因为弹性数据库 链接1分钟内不活动会被弹性数据库回收,所以配置如下两个参数 -->
<!--如果设为true那么在取得连接的同时将校验连接的有效性。默认为false。-->
<property name="testConnectionOnCheckin" value="true" />
<!--因性能消耗大请只在需要的时候使用它。
如果设为true那么在每个connection提交的时候都 将校验其有效性。
建议使用 idleConnectionTestPeriod或automaticTestTable
等方法来提升连接测试的性能。默认为false;
-->
<property name="testConnectionOnCheckout" value="true" />
</bean>

【2】从库dataSource,配置c3p0连接池

 <!-- slaveDataSource -->
<bean id="slave01DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="#{baseConfig['jdbc_trace.slave01.url']}"/>
<!--<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/escort" />-->
<property name="user" value="#{imConfig['jdbc_trace.slave01.username']}"/>
<property name="password" value="#{imConfig['jdbc_trace.slave01.password']}"/>
<property name="minPoolSize" value="5"/>
<property name="maxPoolSize" value="20"/>
<!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime" value="300"/>
<property name="acquireIncrement" value="2"/>
<property name="maxStatements" value="1000"/>
<property name="initialPoolSize" value="2"/>
<property name="idleConnectionTestPeriod" value="240"/>
<property name="acquireRetryAttempts" value="30"/> <!--定义所有连接测试都执行的测试语句,比默认配置效率高-->
<property name="preferredTestQuery" value="select 1" />
<!--每过一段时间检查所有连接池中的空闲连接。【单位为秒】Default 0 (0代表不探测)
它保证连接池会每隔一定时间对空闲连接进行一次测试,
从而保证有效的空闲连接能每隔一定时间访问一次数据库
-->
<property name="IdleConnectionTestPeriod" value="55"/>
<!-- 因为弹性数据库 链接1分钟内不活动会被弹性数据库回收,所以配置如下两个参数 -->
<!--如果设为true那么在取得连接的同时将校验连接的有效性。默认为false。-->
<property name="testConnectionOnCheckin" value="true" />
<!--因性能消耗大请只在需要的时候使用它。
如果设为true那么在每个connection提交的时候都 将校验其有效性。
建议使用 idleConnectionTestPeriod或automaticTestTable
等方法来提升连接测试的性能。默认为false;
-->
<property name="testConnectionOnCheckout" value="true" />
</bean>

【3】引入主库与从库自定义的dataSource(c3p0连接池)

 <!-- 定义数据源,使用自己实现的数据源 -->
<bean id="dataSource" class="com.common.datasource.DynamicDataSource">
<!-- 设置多个数据源 -->
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- 这个key需要和程序中的key一致 -->
<entry key="master" value-ref="master01DataSource"/>
<entry key="slave" value-ref="slave01DataSource"/>
</map>
</property>
<!-- 设置默认的数据源,这里默认走写库 -->
<property name="defaultTargetDataSource" ref="master01DataSource"/>
</bean>

注意:

DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全。由DynamicDataSourceHolder完成。

自定义DynamicDataSource(com.common.datasource.DynamicDataSource)如下所示:

 package com.common.datasource;

 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

 /**
* 定义动态数据源,实现通过集成spring提供的AbstractRoutingDataSource,只需要实现determineCurrentLookupKey方法即可
*
* 由于DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全,由DynamicDataSourceHolder完成。
*/
public class DynamicDataSource extends AbstractRoutingDataSource { @Override
protected Object determineCurrentLookupKey() {
// 使用DynamicDataSourceHolder保证线程安全,并且得到当前线程中的数据源key
return DynamicDataSourceHolder.getDataSourceKey();
}
}

DynamicDataSourceHolder.java

 package com.common.datasource;

 /**
* 使用ThreadLocal技术来记录当前线程中的数据源的key
*/
public class DynamicDataSourceHolder {
//写库对应的数据源key
private static final String MASTER = "master"; //读库对应的数据源key
private static final String SLAVE = "slave"; //使用ThreadLocal记录当前线程的数据源key
private static final ThreadLocal<String> holder = new ThreadLocal<String>(); /**
* 设置数据源key
* @param key
*/
public static void putDataSourceKey(String key) {
holder.set(key);
} /**
* 获取数据源key
* @return
*/
public static String getDataSourceKey() {
return holder.get();
} /**
* 标记写库
*/
public static void markMaster(){
putDataSourceKey(MASTER);
} /**
* 标记读库
*/
public static void markSlave(){
putDataSourceKey(SLAVE);
}
}

Step2:定义事务管理器(必须),事务管理器才是真正管理事务。

 <!-- 定义事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

Step3:配置事务通知(增强:如事务隔离级别及传播行为,只读等)

 <!-- 对事务管理器进行增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="insert*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="create*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="execute*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="do*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="query*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>

Step4:配置切面(即事务应用范围)

 <!-- 定义AOP切面处理器 -->
<bean class="com.common.datasource.DataSourceAspectTx" id="dataSourceAspect">
<!-- 指定事务策略 -->
<property name="txAdvice" ref="txAdvice"/>
<!-- 指定slave方法的前缀(非必须) -->
<property name="slaveMethodStart" value="query,find,get,select"/>
</bean>
<aop:config expose-proxy="true" proxy-target-class="true">
<!-- 定义切面,所有的service的所有方法 -->
<aop:pointcut id="txPointcut"
expression="execution(* com.dao..*.service..*.*(..))
or execution(* com.service..*.*(..))
or execution(* com.tx.service..*.*(..))
or execution(* com.common.service.impl.BaseServiceImpl.*(..))"/>
<!-- 应用事务策略到Service切面 -->
<aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice"/>
<!-- 将切面应用到自定义的切面处理器上,-9999保证该切面优先级较高执行 -->
<aop:aspect ref="dataSourceAspect" order="-9999">
<aop:before method="before" pointcut-ref="txPointcut"/>
</aop:aspect>
</aop:config>

自定义AOP切面处理器DataSourceAspectTx.java

 package com.common.datasource;

 import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.ReflectionUtils; import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; /**
* AOP切面处理器
*/
public class DataSourceAspectTx {
private final Logger logger = LoggerFactory.getLogger(DataSourceAspectTx.class);
private List<String> slaveMethodPattern = new ArrayList<String>(); private static final String[] defaultSlaveMethodStart = new String[]{ "query", "find", "get" }; private String[] slaveMethodStart; /**
* 读取事务管理中的策略
*/
@SuppressWarnings("unchecked")
public void setTxAdvice(TransactionInterceptor txAdvice) throws Exception {
if (txAdvice == null) {
// 没有配置事务管理策略
return;
}
//从txAdvice获取到策略配置信息
TransactionAttributeSource transactionAttributeSource = txAdvice.getTransactionAttributeSource();
if (!(transactionAttributeSource instanceof NameMatchTransactionAttributeSource)) {
return;
}
//使用反射技术获取到NameMatchTransactionAttributeSource对象中的nameMap属性值
NameMatchTransactionAttributeSource matchTransactionAttributeSource = (NameMatchTransactionAttributeSource) transactionAttributeSource;
Field nameMapField = ReflectionUtils.findField(NameMatchTransactionAttributeSource.class, "nameMap");
nameMapField.setAccessible(true); //设置该字段可访问
//获取nameMap的值
Map<String, TransactionAttribute> map = (Map<String, TransactionAttribute>) nameMapField.get(matchTransactionAttributeSource); //遍历nameMap
for (Map.Entry<String, TransactionAttribute> entry : map.entrySet()) {
if (!entry.getValue().isReadOnly()) {//判断之后定义了ReadOnly的策略才加入到slaveMethodPattern
continue;
}
slaveMethodPattern.add(entry.getKey());
}
} /**
* 在进入Service方法之前执行
* @param point 切面对象
*/
public void before(JoinPoint point) {
// 获取到当前执行的方法名
String methodName = point.getSignature().getName(); boolean isSlave = false; if (slaveMethodPattern.isEmpty()) {
// 当前Spring容器中没有配置事务策略,采用方法名匹配方式
isSlave = isSlave(methodName);
} else {
// 使用策略规则匹配
for (String mappedName : slaveMethodPattern) {
if (isMatch(methodName, mappedName)) {
isSlave = true;
break;
}
}
}
//logger.info("执行方法是:"+methodName+";数据库:"+(isSlave?"从":"主"));
if (isSlave) {
// 标记为读库
DynamicDataSourceHolder.markSlave();
} else {
// 标记为写库
DynamicDataSourceHolder.markMaster();
}
} /**
* 判断是否为读库
*/
private Boolean isSlave(String methodName) {
// 方法名以query、find、get开头的方法名走从库
return StringUtils.startsWithAny(methodName, getSlaveMethodStart());
} /**
* 通配符匹配
*/
protected boolean isMatch(String methodName, String mappedName) {
return PatternMatchUtils.simpleMatch(mappedName, methodName);
} /**
* 用户指定slave的方法名前缀
*/
public void setSlaveMethodStart(String[] slaveMethodStart) {
this.slaveMethodStart = slaveMethodStart;
} public String[] getSlaveMethodStart() {
if(this.slaveMethodStart == null){
// 没有指定,使用默认
return defaultSlaveMethodStart;
}
return slaveMethodStart;
}
}

5.3。声明式事务管理方式三:基于注解方式

Step1:修改Spring配置文件,开启注解事务。

注意:<tx:annotation-driven>默认查找名称为transactionManager的事务管理器Bean

Step2:需要加事务的类上添加@Transactional

总结:

Spring003--Spring事务管理(mooc)的更多相关文章

  1. 【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】

    一.JDBC编程特点 静态代码+动态变量=JDBC编程. 静态代码:比如所有的数据库连接池 都实现了DataSource接口,都实现了Connection接口. 动态变量:用户名.密码.连接的数据库. ...

  2. spring事务管理器设计思想(二)

    上文见<spring事务管理器设计思想(一)> 对于第二个问题,涉及到事务的传播级别,定义如下: PROPAGATION_REQUIRED-- 如果当前没有事务,就新建一个事务.这是最常见 ...

  3. spring事务管理器设计思想(一)

    在最近做的一个项目里面,涉及到多数据源的操作,比较特殊的是,这多个数据库的表结构完全相同,由于我们使用的ibatis框架作为持久化层,为了防止每一个数据源都配置一套规则,所以重新实现了数据源,根据线程 ...

  4. 事务管理(下) 配置spring事务管理的几种方式(声明式事务)

    配置spring事务管理的几种方式(声明式事务) 概要: Spring对编程式事务的支持与EJB有很大的区别.不像EJB和Java事务API(Java Transaction API, JTA)耦合在 ...

  5. Spring事务管理器的应对

    Spring抽象的DAO体系兼容多种数据访问技术,它们各有特色,各有千秋.像Hibernate是非常优秀的ORM实现方案,但对底层SQL的控制不太方便:而iBatis则通过模板化技术让你方便地控制SQ ...

  6. Spring事务管理(转)

    1 初步理解 理解事务之前,先讲一个你日常生活中最常干的事:取钱. 比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱:然后ATM出1000元钱.这两个步骤必须是 ...

  7. [Spring框架]Spring 事务管理基础入门总结.

    前言:在之前的博客中已经说过了数据库的事务, 不过那里面更多的是说明事务的一些锁机制, 今天来说一下Spring管理事务的一些基础知识. 之前的文章: [数据库事务与锁]详解一: 彻底理解数据库事务一 ...

  8. Spring 事务管理 01 ——

    目录: 参考: 1.Spring 事务管理高级应用难点剖析: 第 1 部分

  9. Spring 事务管理原理探究

    此处先粘贴出Spring事务需要的配置内容: 1.Spring事务管理器的配置文件: 2.一个普通的JPA框架(此处是mybatis)的配置文件: <bean id="sqlSessi ...

  10. Spring 事务管理高级应用难点剖析--转

    第 1 部分 http://www.ibm.com/search/csass/search/?q=%E4%BA%8B%E5%8A%A1&sn=dw&lang=zh&cc=CN& ...

随机推荐

  1. lspci - 列出所有PCI设备

    总览 SYNOPSIS lspci [options] 描述 DESCRIPTION lspci 是一个用来显示系统中所有PCI总线设备或连接到该总线上的所有设备的工具. 为了能使用这个命令所有功能, ...

  2. PAT Advanced 1036 Boys vs Girls (25 分)

    This time you are asked to tell the difference between the lowest grade of all the male students and ...

  3. C++ STL(标准模板库)

    一.STL简介 STL(Standard Template Library,标准模板库)是惠普实验室开发的,在被引入C++之前该技术就已经存在了很长的一段时间. STL的代码从广义上讲分为三类:alg ...

  4. Composer\Downloader\TransportException ... Failed to enable crypto,failed to open stream: operation failed

    failed to open stream: operation failed 错误详细信息: [Composer\Downloader\TransportException] The "h ...

  5. 【ARC101F】Robots and Exits 树状数组优化DP

    ARC101F Robots and Exits 树状数组 有 $ n $ 个机器人和 $ m $ 个出口.这 $ n $ 个机器人的初始位置是 $ a_1,a_2.....a_n $ ,这 $ m ...

  6. CSS——相对定位、绝对定位、固定定位

    相对定位: position:relative 当元素被设置相对定位或是绝对定位后,将自动产生层叠,他们的层叠级别自然的高于文本流,除非设置其z-index值为负值. 并且我们发现当相对定位元素进行位 ...

  7. vue安装iview和配置

    在命令行工具上输入:npm install iview --save 等待安装完成. 在项目的src/main.js中添加三行代码引入iview import iView from 'iview' i ...

  8. POJ 1011 Sticks(搜索 && 剪枝 && 经典)

    题意 : 有n根木棍(n<=64),它们由一些相同长度的木棍切割而来,给定这n根木棍的长度,求使得原来长度可能的最小值. 分析 : 很经典的深搜题目,我们发现答案只可能是所有木棍长度总和的因数, ...

  9. 【bzoj1324】Exca王者之剑(8-9 方格取数问题)

    *题目描述: 在一个有m*n (m,n<=100)个方格的棋盘中,每个方格中有一个正整数.现要从方格中取数,使任意2 个数所在方格没有公共边,且取出的数的总和最大.试设计一个满足要求的取数算法, ...

  10. 2019前端面试题汇总(vue)

    毕业之后就在一直合肥小公司工作,没有老司机.没有技术氛围,在技术的道路上我只能独自摸索.老板也只会画饼充饥,前途一片迷茫看不到任何希望.于是乎,我果断辞职,在新年开工之际来到杭州,这里的互联网公司应该 ...