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. 谈谈mybatis中的#与$的区别

    #相当于对数据 加上 双引号,$相当于直接显示数据 . #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号.如:", 如果传入的值是id,则解析成的sql为order by &q ...

  2. Maven 添加其他Maven组件配置问题

    在父工程的pom文件里,添加如下配置 <project> <!--其它配置--> <modules> <module>A项目文件夹</module ...

  3. 202-基于TI DSP TMS320C6678、Xilinx K7 FPGA XC7K325T的高速数据处理核心板

    该DSP+FPGA高速信号采集处理板由我公司自主研发,包含一片TI DSP TMS320C6678和一片Xilinx FPGA K7 XC72K325T-1ffg900.包含1个千兆网口,1个FMC ...

  4. PHP file函数

    一.判断函数 is_file($filename) //判断是否文件 is_link($filename) //判断是否为链接符号 is_dir($filename) //判断是否为路径 is_rea ...

  5. 外网无法ping自己的linux服务器

    Linux默认是允许Ping响应的,系统是否允许Ping由2个因素决定的:A.内核参数,B.防火墙,需要2个因素同时允许才能允许Ping,2个因素有任意一个禁Ping就无法Ping. 具体的配置方法如 ...

  6. Spring Boot 2.x整合mybatis及druid数据源及逆向工程

    1逆向工程 1)db.properties #============================# #===== Database sttings =====# #=============== ...

  7. handy源码阅读(五):PollerBase类

    使用poll内核函数等待事件发生: struct PollerBase: private noncopyable { int64_t id_; int lastActive_; PollerBase( ...

  8. POST接口测试的请求方式

    一.基础知识 1.HTTP的八种请求方法:GET, POST ,HEAD,OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法. GET请求:请求指定的页面信息,并返回实体 ...

  9. linux运维、架构之路-MySQL日志(三)

    一.MySQL日志 1.错误日志 ①配置方法 [mysqld] log-error=/data/mysql/mysql.log ②查看配置方式 mysql> show variables lik ...

  10. java如何实现多继承

    在java中,原则上是不允许多继承的,也就是类与类之间只可以单继承.那么,有没有办法,可以在不使用接口的情况下实现多继承呢?  答案是可以.使用内部类就可以多继承,严格来说,还不是实现多继承,但是这种 ...