环境

数据库: oracle 11g

JAR:

    • org.springframework:spring-jdbc:4.3.8.RELEASE
    • org.mybatis:mybatis:3.4.2

概念

REQUIRED(默认): 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务。

REQUIRED_NEW: 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动,如果存在当前事务,在该方法执行期间,当前事务会被挂起。

早前对NEW的理解只是停留在: 当前方法会新开启一个事务,新事务的提交/回滚不会影响之前事务的提交/回滚。

但"当前事务会被挂起",那么NEW事务结束后,被挂起的当前事务应该是会恢复,但会恢复哪些东西了?

(摘自: 在Spring中使用PROPAGATION_REQUIRES_NEW带来的缓存问题?)

源码分析:

在TransactionTemplate的<T> T execute(TransactionCallback<T> action)中会通过TransactionManager的getTransaction方法来取得TransactionStatus

1、取得当前线程所关联的SessionHolder

2、若存在SessionHolder并且开启了事务(this.sessionHolder != null && this.sessionHolder.getTransaction() != null),而且当前的的传播行为为PROPAGATION_REQUIRES_NEW

3、挂起当前线程绑定的事务及其事务同步器,取消当前线程和当前session和connection的绑定,并保存所有的挂起信息以供恢复

4、创建新的session,并开启新的事务

5、执行TransactionTemplate的TransactionCallback回调

6、在新事务提交后,会恢复上个事务的所有信息和执行

测试问题描述

1、 method_A(T1事务)中查询了某条记录A(A.name = 1);然后调用method_B(T2事务,并且是REQUIRES_NEW  )

2、在 method_B中修改了记录A并提交保存(A.name = 2);(method_B方法结束,T2事务提交)

3、然后回到 method_A  中再次查询这条记录A,发现得到的A.name = 1;但是此时数据库中的A.name = 2 。

用hibernate/mybatis得到的都是以上结果, 即在T1中查到的A.name = 1.

但是,我用spring的JdbcTemplate测试却发现: 本来在(3)得到A.name=1,但实际A.name =2。

为什么mybatis&hibernate得到的结果与JdbcTemplate不一样了?事务不是统一交给spring控制的吗?

(见问题: Spring事务REQUIRES_NEW的问题?)

个人理解

最初, 我以为在(3)一定会得到是A.name = 2。但实际在hibernate中得到的是A.name =1。(当时遇到问题用的是hibernate。)

然后我就重新去理解了REQUIRES_NEW, 发现应该是要得到A.name = 1, 即mybatis&hibernate得到的就是正确的。

但无意用JdbcTemplate测试却发现和mybatis&hibernate得到的不一样, 所以就迷茫了。完全不知道怎么理解。

现在在想一想, 到底什么是"事务控制"? 其实现在我也不知道怎么理解, 渣狗一只, 汪汪~~

可以参考:

spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!

spring事务源码解析 (这个可以细看)

但为什么觉得mybatis&hibernate的结果更正确呢?

1、更能体现出事务的一致性。

因为对t1事务来说,并没有改变过A.name。所以,从始至终都应该得到A.name =1。

一、spring boot公共部分

@SpringBootApplication
public class TransactionApplication {
public final static String ID = "1"; public static void main(String[] args) {
SpringApplication app = new SpringApplication(TransactionApplication.class);
app.run(args);
}
}

TransactionApplication.java

#### 28.1.2. 连接到一个生产环境数据库
## 注:其他的连接池可以手动配置。如果你定义自己的DataSource bean,自动配置不会发生。
## 详细属性配置参考:https://segmentfault.com/a/1190000004316491
#### ## Spring Boot能够从大多数数据库的url上推断出driver-class-name,那么你就不需要再指定它了。
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
## 默认会从url推断driver class
spring.datasource.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
spring.datasource.username=vergilyn
spring.datasource.password=409839163 ## JDNI
# spring.datasource.jndi-name=java:jboss/datasources/customers

application.properties

public class Parent implements Serializable{
private int parentId;
private String parentName;
public Parent() {
} public Parent(int parentId, String parentName) {
this.parentId = parentId;
this.parentName = parentName;
} public int getParentId() {
return parentId;
} public void setParentId(int parentId) {
this.parentId = parentId;
} public String getParentName() {
return parentName;
} public void setParentName(String parentName) {
this.parentName = parentName;
} @Override
public String toString() {
return "Parent{" +"parentId=" + parentId +", parentName='" + parentName + '\'' +'}';
}
}

Parent.java

数据库:

二、JdbcTemplate测试

@Service
public class JdbcMainService {
@Autowired
private JdbcParentDao parentDao;
@Autowired
private JdbcNewService newService; @Transactional(propagation = Propagation.REQUIRED)
public void updateCur() {
Parent init = parentDao.getEntity(TransactionApplication.ID);
System.out.println("init select: " + init); Parent curu = this.updateCur(init);
System.out.println("curu update: " + curu); Parent curs = parentDao.getEntity(TransactionApplication.ID);
System.out.println("curs select: " + curs); Parent news = newService.getEntity(TransactionApplication.ID);
System.out.println("news select: " + news);
} @Transactional(propagation = Propagation.REQUIRED)
public void updateNew() {
Parent init = parentDao.getEntity(TransactionApplication.ID);
System.out.println("init select: " + init); Parent newu = newService.updateNew(init);
System.out.println("newu update: " + newu); Parent curs = parentDao.getEntity(TransactionApplication.ID);
System.out.println("curs select: " + curs); Parent news = newService.getEntity(TransactionApplication.ID);
System.out.println("news select: " + news);
} @Transactional(propagation = Propagation.REQUIRED)
public Parent updateCur(Parent parent) {
Parent p2 = new Parent();
p2.setParentId(parent.getParentId());
p2.setParentName(parent.getParentName() + "@cur");
parentDao.update(p2);
return p2;
}
}

JdbcMainService.java

@Service
public class JdbcNewService {
@Autowired
private JdbcParentDao jdbcParentDao; @Transactional(propagation = Propagation.REQUIRES_NEW)
public Parent updateNew(Parent parent){
Parent p2 = new Parent();
p2.setParentId(parent.getParentId());
p2.setParentName(parent.getParentName() + "@new");
jdbcParentDao.update(p2); return p2;
} @Transactional(propagation = Propagation.REQUIRES_NEW)
public Parent getEntity(String id) {
return jdbcParentDao.getEntity(id);
}
}

JdbcNewService.java

@Repository
public class JdbcParentDao {
@Autowired
private JdbcTemplate jdbcTemplate; @Autowired
private NamedParameterJdbcTemplate namedJdbc; public Parent getEntity(String id){
String sql = "select * from parent where parent_id = ?"; return this.jdbcTemplate.queryForObject(sql,new Object[]{id},new BeanPropertyRowMapper<Parent>(Parent.class));
} public void update(Parent parent){
String sql = "update parent set parent_name = ? where parent_id = ?";
this.jdbcTemplate.update(sql,new Object[]{parent.getParentName(),parent.getParentId()});
}
}

JdbcParentDao.java

2.1 说明

JdbcMainService: 其中的方法全是REQUIRED,且此class是主要的测试class。

JdbcNewService: 其中的方法全是REQUIRES_NEW。

JdbcParentDao: 共用的dao层方法。

2.2 测试JdbcMainService.updateCur()
    @Transactional(propagation = Propagation.REQUIRED)
public void updateCur() {
Parent init = parentDao.getEntity(TransactionApplication.ID);
System.out.println("init select: " + init); // init select: Parent{parentId=1, parentName='周父'} Parent curu = this.updateCur(init);
System.out.println("curu update: " + curu); // curu update: Parent{parentId=1, parentName='周父@cur'} Parent curs = parentDao.getEntity(TransactionApplication.ID);
System.out.println("curs select: " + curs); // curs select: Parent{parentId=1, parentName='周父@cur'}
// 此时数据库的实际值是: parentName='周父', 因为cur方法还未结束, 即cur事务还未提交。 Parent news = newService.getEntity(TransactionApplication.ID);
System.out.println("news select: " + news); // news select: Parent{parentId=1, parentName='周父'}
}

分析:  以上结果都在预料之内。因为update是在cur事务中, 所以cur事务中再次select可以得到cur事务的更新但未提交的数据, 即"curs select"得到的是CUR事务update后的值, 而非当前数据库中的值。

这也证明了CUR事务、NEW事务是两个完全独立的事务。

回去再看NEW描述中的"当前事务会被挂起", 是否是在NEW事务执行完返回CUR事务, 会恢复CUR事务的所有信息和执行。

2.2 测试JdbcMainService.updateNew()

    @Transactional(propagation = Propagation.REQUIRED)
public void updateNew() {
Parent init = parentDao.getEntity(TransactionApplication.ID);
System.out.println("init select: " + init); // init select: Parent{parentId=1, parentName='周父'} Parent newu = newService.updateNew(init);
System.out.println("newu update: " + newu); // newu update: Parent{parentId=1, parentName='周父@new'}
// 用REQUIRES_NEW更新, 此时数据库已是最新值 parentName='周父@new' Parent curs = parentDao.getEntity(TransactionApplication.ID);
System.out.println("curs select: " + curs); // curs select: Parent{parentId=1, parentName='周父@new'} Parent news = newService.getEntity(TransactionApplication.ID);
System.out.println("news select: " + news); // news select: Parent{parentId=1, parentName='周父@new'}
}

问题: 为什么"curs select"得到的会是parentName='周父@new'?
  针对之前对REQUIRED与REQUIRES_NEW的理解, 返回到CUR事务中查询到的结果应该是与"init select"一样才对。但现在明显得到的是数据库最新值。

三、mybatis测试

@Service
public class MybatisMainService {
@Autowired
private MybatisParentMapper parentMapper;
@Autowired
private MybatisNewService newService; @Transactional(propagation = Propagation.REQUIRED)
public void updateCur(){
Parent init = parentMapper.getEntity(TransactionApplication.ID);
System.out.println("init select: " + init); Parent curu = this.updateCur(init);
System.out.println("curu update: " + curu); Parent curs = parentMapper.getEntity(TransactionApplication.ID);
System.out.println("curs select: " + curs); Parent news = newService.getEntity(TransactionApplication.ID);
System.out.println("news select: " + news); Parent nocs = parentMapper.getNoCacheEntity(TransactionApplication.ID);
System.out.println("nocs select: " + nocs);
} @Transactional(propagation = Propagation.REQUIRED)
public void updateNew(){
Parent init = parentMapper.getEntity(TransactionApplication.ID);
System.out.println("init select: " + init); Parent newu = newService.updateNew(init);
System.out.println("newu update: " + newu); Parent curs = parentMapper.getEntity(TransactionApplication.ID);
System.out.println("curs select: " + curs); Parent news = newService.getEntity(TransactionApplication.ID);
System.out.println("news select: " + news); Parent nocs = parentMapper.getNoCacheEntity(TransactionApplication.ID);
System.out.println("nocs select: " + nocs);
} @Transactional(propagation = Propagation.REQUIRED)
public Parent updateCur(Parent parent){
Parent p2 = new Parent();
p2.setParentId(parent.getParentId());
p2.setParentName(parent.getParentName() + "@cur");
parentMapper.update(p2); return p2;
}
}

MybatisMainService.java

@Service
public class MybatisNewService {
@Autowired
private MybatisParentMapper parentMapper; @Transactional(propagation = Propagation.REQUIRES_NEW)
public Parent updateNew(Parent parent){
Parent p2 = new Parent();
p2.setParentId(parent.getParentId());
p2.setParentName(parent.getParentName() + "@new");
parentMapper.update(p2); return p2;
} @Transactional(propagation = Propagation.REQUIRES_NEW)
public Parent getEntity(String id) {
return parentMapper.getEntity(id);
}
}

MybatisNewService.java

@Mapper
public interface MybatisParentMapper { @Select("select * from parent where PARENT_ID = #{id}")
@Results({
@Result(id = true, column = "PARENT_ID", property = "parentId"),
@Result(column = "PARENT_NAME", property = "parentName")
})
Parent getEntity(@Param("id") String id); @Select("select * from parent where PARENT_ID = #{id}")
@Results({
@Result(id = true, column = "PARENT_ID", property = "parentId"),
@Result(column = "PARENT_NAME", property = "parentName")
})
@Options(flushCache = Options.FlushCachePolicy.TRUE,useCache = false)
Parent getNoCacheEntity(@Param("id") String id); @Update("update parent set PARENT_NAME=#{parentName} where PARENT_ID = #{parentId}")
@Results({
@Result(id = true, column = "PARENT_ID", property = "parentId"),
@Result(column = "PARENT_NAME", property = "parentName")
})
void update(Parent parent);
}

MybatisParentMapper.java

3.1 说明

MybatisMainService: 其中的方法全是REQUIRED,且此class是主要的测试class。

MybatisNewService: 其中的方法全是REQUIRES_NEW。

MybatisParentMapper: mybatis纯注解的共用dao。

3.2 测试MybatisMainService.updateCur()
@Transactional(propagation = Propagation.REQUIRED)
public void updateCur(){
Parent init = parentMapper.getEntity(TransactionApplication.ID);
System.out.println("init select: " + init); // init select: Parent{parentId=1, parentName='周父'} Parent curu = this.updateCur(init);
System.out.println("curu update: " + curu); // curu update: Parent{parentId=1, parentName='周父@cur'} Parent curs = parentMapper.getEntity(TransactionApplication.ID);
System.out.println("curs select: " + curs); // curs select: Parent{parentId=1, parentName='周父@cur'} Parent news = newService.getEntity(TransactionApplication.ID);
System.out.println("news select: " + news); // news select: Parent{parentId=1, parentName='周父'} Parent nocs = parentMapper.getNoCacheEntity(TransactionApplication.ID);
System.out.println("nocs select: " + nocs); // nocs select: Parent{parentId=1, parentName='周父@cur'}
}

1) "curs select"确实是得到当前事务的结果(即使是未提交的)。

2) "news select"得到的是现在数据库中的结果。

3) "nocs select"指定了@Options(flushCache = Options.FlushCachePolicy.TRUE,useCache = false),为什么得到的会是parentName='周父@cur',而不是parentName='周父'。

虽然现在数据库中的值是parentName='周父',但是当前事务下当前数据库中的值是parentName='周父@cur'(未提交)。

所以,当前事务中即使是useCache=flase,重新从数据库去查询,当前事务下的最新值就是parentName='周父'。

3.3 测试MybatisMainService.updateNew()

@Transactional(propagation = Propagation.REQUIRED)
public void updateNew(){
Parent init = parentMapper.getEntity(TransactionApplication.ID);
System.out.println("init select: " + init); // init select: Parent{parentId=1, parentName='周父'} Parent newu = newService.updateNew(init);
System.out.println("newu update: " + newu); // newu update: Parent{parentId=1, parentName='周父@new'} Parent curs = parentMapper.getEntity(TransactionApplication.ID);
System.out.println("curs select: " + curs); // curs select: Parent{parentId=1, parentName='周父'} Parent news = newService.getEntity(TransactionApplication.ID);
System.out.println("news select: " + news); // news select: Parent{parentId=1, parentName='周父@new'} Parent nocs = parentMapper.getNoCacheEntity(TransactionApplication.ID);
System.out.println("nocs select: " + nocs); // nocs select: Parent{parentId=1, parentName='周父@new'} Parent curs2 = parentMapper.getEntity(TransactionApplication.ID);
System.out.println("curs2 select: " + curs2); // curs2 select: Parent{parentId=1, parentName='周父@new'}
}

1) "curs select"得到当前事务的结果,所以parentName='周父'。

2) "news select"&"nocs select"为什么得到parentName='周父@new'?

按3.2的测试结论,如果nocs得到的是当前事务下的最新结果,那么当前事务的最新结果是什么? 从结果看,是parentName='周父@new'。

个人又觉得为了事务的一致性,觉得应该得到parentName='周父'。即应该和"curs select"得到一样的结果。

这种对事务的理解是对的吗(不管是交由spring控制的,还是oracle等数据库实际的事务控制)?

先说个人在此对"nocs select"得到结果的解释: 当前事务中的最新值就是parentName='周父@new',即new事务提交后的结果已经对cur可见。

现在用PL/SQL做这么2个测试:

这是否可以证明:

只要更新被提交后,更新后的值对别的事务即是可见的。所以,别的事务得到的结果就是数据库当前的值=别的事务更新后的值。

未提交的,对别的事务是不可见的,即别的事务得到的还是数据库目前的值。

3) "curs select"为什么得到parentName='周父'?

按2)中的说法,new更新已经提交了那么得到的不应该是parentName='周父@new'吗? (回过去看JdbcTemplate此处得到的是什么, parentName='周父@new')

现在是不是觉得JdbcTemplate才是最纯粹的原始事务呢,那为什么mybatis&hibernate中会得到此结果呢?

这主要是mybatis&hibernate中都存在缓存(主要是一级缓存造成的),所以JdbcTemplate与mybatis&hibernate的差异是因为缓存造成的。(JdbcTemplate无缓存一说)

4) "curs2 select"为什么得到parentName='周父@new'?

因为在"curs2 select"执行过"nocs select","nocs select"清空了缓存,并把查询到的结果重新放入缓存。这之后"curs2 select"又是从缓存中取的结果。所以得到的是parentName='周父@new'。

四、总结JdbcTemplate、mybatis的查询

// 以下是 JdbcTemplate    @Transactional(propagation = Propagation.REQUIRED)
public void getCur(){
Parent s1 = parentDao.getEntity(TransactionApplication.ID);
System.out.println("s1 select: " + s1); // s1 select: Parent{parentId=1, parentName='周父'}
// 利用断点: 手动修改数据库的值 '周父' -> '周父s2'
Parent s2 = parentDao.getEntity(TransactionApplication.ID);
System.out.println("s2 select: " + s2); // s2 select: Parent{parentId=1, parentName='周父s2'} // 利用断点: 手动修改数据库的值 '周父' -> '周父s3'
Parent s3 = parentDao.getEntity(TransactionApplication.ID);
System.out.println("s3 select: " + s3); // s3 select: Parent{parentId=1, parentName='周父s3'}
}

// 以下是 mybatis        @Transactional(propagation = Propagation.REQUIRED)
public void getCur(){
Parent s1 = parentMapper.getEntity(TransactionApplication.ID);
System.out.println("s1 select: " + s1); // s1 select: Parent{parentId=1, parentName='周父'} // 利用断点: 手动修改数据库的值 '周父' -> '周父s2'
Parent s2 = parentMapper.getEntity(TransactionApplication.ID);
System.out.println("s2 select: " + s2); // s2 select: Parent{parentId=1, parentName='周父'} // 利用断点: 手动修改数据库的值 '周父' -> '周父s3'
Parent s3 = parentMapper.getEntity(TransactionApplication.ID);
System.out.println("s3 select: " + s3); // s3 select: Parent{parentId=1, parentName='周父'}
}
// 以下是mybatis noCache混用测试
@Transactional(propagation = Propagation.REQUIRED)
public void getNoCache(){
Parent s1 = parentMapper.getEntity(TransactionApplication.ID);
System.out.println("s1 select: " + s1); // s1 select: Parent{parentId=1, parentName='周父'} // 利用断点: 手动修改数据库的值 '周父' -> '周父s2'
Parent s2 = parentMapper.getEntity(TransactionApplication.ID);
System.out.println("s2 select: " + s2); // s2 select: Parent{parentId=1, parentName='周父'} 从缓存取值,所以s2=s1
Parent s3 = parentMapper.getNoCacheEntity(TransactionApplication.ID);
System.out.println("s3 select: " + s3); // s3 select: Parent{parentId=1, parentName='周父s2'} 清空缓存重新查询
Parent s4 = parentMapper.getEntity(TransactionApplication.ID);
System.out.println("s4 select: " + s4); // s4 select: Parent{parentId=1, parentName='周父s2'} 从缓存取值,所以s4=s3 // 利用断点: 手动修改数据库的值 '周父' -> '周父s3'
Parent s5 = parentMapper.getNoCacheEntity(TransactionApplication.ID);
System.out.println("s5 select: " + s5); // s5 select: Parent{parentId=1, parentName='周父s3'}
}

现在在回来看这两种select得到的两种结果。(个人从以上测试得到结论,并不一定正确,希望高人指出正确的理解)

1) mybatis的观念: (默认情况下)从始至终当前事务都不应该看到别的事务的结果,即使是别的事务已经提交。

2) jdbcTemplate(或oracle)的观念: 只要事务被提交,另一个事务中就可以看到它提交的结果。

所以,现在看来也不能说哪种更好,只是个人偏向于mybatis。最主要的知道了有这差异(感觉主要是对mybatis&hibernate的缓存最用不够理解,只是知道存在缓存这一说)

五、题外话

在测试JdbcTemplate的时候,可能有一种猜测是: 并没有在一个事务中,或者并没有在同一个连接中,所以JdbcTemplate得到的查询结果永远是最新的。

这个可以很容易证明是同一个连接。见源码: org.springframework.jdbc.datasource.DataSourceUtils

package org.springframework.jdbc.datasource;

public abstract class DataSourceUtils {
public static final int CONNECTION_SYNCHRONIZATION_ORDER = 1000;
private static final Log logger = LogFactory.getLog(DataSourceUtils.class); public DataSourceUtils() {
} public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
return doGetConnection(dataSource);
} catch (SQLException var2) {
throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", var2);
}
} public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
if(conHolder == null || !conHolder.hasConnection() && !conHolder.isSynchronizedWithTransaction()) {
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = dataSource.getConnection();
if(TransactionSynchronizationManager.isSynchronizationActive()) {
logger.debug("Registering transaction synchronization for JDBC Connection");
ConnectionHolder holderToUse = conHolder;
if(conHolder == null) {
holderToUse = new ConnectionHolder(con);
} else {
conHolder.setConnection(con);
} holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(new DataSourceUtils.ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if(holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
System.out.println("datasource cur: " + con);
return con;
} else {
conHolder.requested();
if(!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(dataSource.getConnection());
}
System.out.println("datasource new: " + conHolder.getConnection());
return conHolder.getConnection();
}
}
}

【Spring】事务(transactional) - REQUIRES_NEW在JdbcTemplate、Mybatis中的不同表现的更多相关文章

  1. 分析spring事务@Transactional注解在同一个类中的方法之间调用不生效的原因及解决方案

    问题: 在Spring管理的项目中,方法A使用了Transactional注解,试图实现事务性.但当同一个class中的方法B调用方法A时,会发现方法A中的异常不再导致回滚,也即事务失效了. 当这个方 ...

  2. Spring事务Transactional和动态代理(二)-cglib动态代理

    系列文章索引: Spring事务Transactional和动态代理(一)-JDK代理实现 Spring事务Transactional和动态代理(二)-cglib动态代理 Spring事务Transa ...

  3. Spring事务Transactional和动态代理(一)-JDK代理实现

    系列文章索引: Spring事务Transactional和动态代理(一)-JDK代理实现 Spring事务Transactional和动态代理(二)-cglib动态代理 Spring事务Transa ...

  4. Spring事务Transactional和动态代理(三)-事务失效的场景

    系列文章索引: Spring事务Transactional和动态代理(一)-JDK代理实现 Spring事务Transactional和动态代理(二)-cglib动态代理 Spring事务Transa ...

  5. spring事务源码分析结合mybatis源码(三)

    下面将结合mybatis源码来分析下,这种持久化框架是如何对connection使用,来达到spring事务的控制. 想要在把mybatis跟spring整合都需要这样一个jar包:mybatis-s ...

  6. Spring事务@Transactional标签深入学习

    事务管理是应用系统开发中必不可少的一部分.Spring为事务管理提供了丰富的功能支持.Spring事务管理分为编码式和声明式 两种方式.编码式事务指的是通过编码方式实现事务;声明式事务基于AOP,将具 ...

  7. spring事务源码分析结合mybatis源码(一)

    最近想提升,苦逼程序猿,想了想还是拿最熟悉,之前也一直想看但没看的spring源码来看吧,正好最近在弄事务这部分的东西,就看了下,同时写下随笔记录下,以备后查. spring tx源码分析 这里只分析 ...

  8. spring事务源码分析结合mybatis源码(二)

    让我们继续上篇,分析下如果有第二个调用进入的过程. 代码部分主要是下面这个: if (isExistingTransaction(transaction)) { return handleExisti ...

  9. spring 事务-使用@Transactional 注解(事务隔离级别)

    转: spring 事务-使用@Transactional 注解(事务隔离级别) 2016年08月11日 21:49:20 华华鱼 阅读数 15490 标签: spring事务Transactiona ...

随机推荐

  1. mvc jQuery 点击按钮实现导出Excel功能 参数长短不限

    var exportSubmit=function(url, obj){ var form = $("<form>"); //定义一个form表单 form.attr( ...

  2. Maven: 每次更新Maven Project ,JAVA 版本都变为1.5

    由于Maven默认编译环境是JAVA 1.5 ,所以我们需要在pom.xml指定编译插件版本号,这样就可以保证更新Maven project版本不变. <!-- java编译插件 --> ...

  3. TypeScript躬行记(6)——高级类型

    本节将对TypeScript中类型的高级特性做详细讲解,包括交叉类型.类型别名.类型保护等. 一.交叉类型 交叉类型(Intersection Type)是将多个类型通过“&”符号合并成一个新 ...

  4. ARTS Week 11

    Jan 6, 2020 ~ Jan 12, 2020 Algorithm Problem 108 Convert Sorted Array to Binary Search Tree (将有序数组转化 ...

  5. java3选择结构一

    1 public class jh_01_为什么需要if选择结构 { 2 /* 3 * 让它有条件性的去执行某些内容. 4 * System.out.println(2); 5 * 把你要控制的内容放 ...

  6. 基于MR实现ngram语言模型

    在大数据的今天,世界上任何一台单机都无法处理大数据,无论cpu的计算能力或者内存的容量.必须采用分布式来实现多台单机的资源整合,来进行任务的处理,包括离线的批处理和在线的实时处理. 鉴于上次开会讲了语 ...

  7. numpy 介绍与使用

    一.介绍 中文文档:https://www.numpy.org.cn/ NumPy是Python语言的一个扩展包.支持多维数组与矩阵运算,此外也针对数组运算提供大量的数学函数库.NumPy提供了与Ma ...

  8. [Python]获取字典所有值

    方法一:Key Value 直接获取 databases = {1: 'Student', 2: 'School'} for k,v in databases.items(): print(k,v) ...

  9. 51nod 1002 数塔取值问题 dp

    动态规划 1002 数塔取数问题 1.0 秒 131,072.0 KB 5 分 1级题   一个高度为N的由正整数组成的三角形,从上走到下,求经过的数字和的最大值. 每次只能走到下一层相邻的数上,例如 ...

  10. 阿里云ECS服务器,mysql无法外网访问

    可参考https://www.jianshu.com/p/7a41734b502e 问题原因 未授权远程IP地址登录.root用户默认只能在localhost也就是本机登录 解决方案 在服务器上登录数 ...