概括来讲,事务是一个由有限操作集合组成的逻辑单元。事务操作包含两个目的,数据
一致以及操作隔离。数据一致是指事务提交时保证事务内的所有操作都成功完成,并且
更改永久生效;事务回滚时,保证能够恢复到事务执行之前的状态。操作隔离则是指多
个同时执行的事务之间应该相互独立,互不影响。
 
事务是一个比较广泛的概念,事务管理资源除了我们熟知的数据库外,还可以包含消息
队列、文件系统等。当然,一般来说,我们说的事务单指“数据库事务”。接下来我们会
以MySQL数据库、Spring声明式事务为主要研究对象,但是很多事务概念、接口抽象和实
现方式同时适用于其他情况。
 
想要阅读更多技术干货、行业洞察,欢迎关注[网易云信博客](https://yunxin.163.com
/dev-blog/?from=juejin&utm_source=juejin&utm_medium=article&)。
 
了解[网易云信](https://yunxin.163.com/?from=juejin&utm_source=juejin&utm_medi
um=article&),来自网易核心架构的通信与视频云服务。
 
## 事务属性和行为
 
### ACID属性
 
提到事务,不可避免需要涉及到事务的ACID属性:
 
+ 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么
全部被执行,要么都不执行。
+ 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致
状态。一致状态的含义是数据库中的数据应满足完整性约束。
 
 
 
 

概括来讲,事务是一个由有限操作集合组成的逻辑单元。事务操作包含两个目的,数据一致以及操作隔离。数据一致是指事务提交时保证事务内的所有操作都成功完成,并且更改永久生效;事务回滚时,保证能够恢复到事务执行之前的状态。操作隔离则是指多个同时执行的事务之间应该相互独立,互不影响。

事务是一个比较广泛的概念,事务管理资源除了我们熟知的数据库外,还可以包含消息队列、文件系统等。当然,一般来说,我们说的事务单指“数据库事务”。接下来我们会以MySQL数据库、Spring声明式事务为主要研究对象,但是很多事务概念、接口抽象和实现方式同时适用于其他情况。

想要阅读更多技术干货、行业洞察,欢迎关注网易云信博客

了解网易云信,来自网易核心架构的通信与视频云服务。

事务属性和行为

ACID属性

提到事务,不可避免需要涉及到事务的ACID属性:

  • 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  • 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  • 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。

我们将严格遵循ACID属性的事务称为刚性事务。与之相对,期望最终一致性,在事务执行的中间状态允许暂时不遵循ACID属性的事务称为柔性事务,可参考传统事务与柔性事务,柔性事务的使用涉及到分布式事务方案,可以后续扩展,这里我们先将注意集中在事务实现原理上。

隔离级别

根据SQL92标准,MySQL的InnoDB引擎提供四种隔离级别(即ACID中的I):读未提交(READ UNCOMMITTED)、读已提交(READ COMMITTED)、可重复读(REPEATABLE READ)和串行化(SERIALIZABLE),InnoDB默认的隔离级别是REPEATABLE READ,其可避免脏读不可重复读,但不能避免幻读,需要指出的是,InnoDB引擎的多版本并发控制机制(MVCC)并没有完全避免幻读,关于该问题以及隔离级别说明,可参考MySQL的InnoDB的幻读问题

传播机制

Spring针对方法嵌套调用时事务的创建行为定义了七种事务传播机制,分别是PROPAGATION_REQUIRED、PROPAGATION_SUPPORT、PROPAGATION_MANDATORY、PROPAGATION_REQUIRES_NEW、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER以及PROPAGATION_NESTED,基本上从字面意思就能知道每种传播机制各自的行为表现,Spring默认的事务传播机制是PROPAGATION_REQUIRED,即如果当前存在事务,则使用当前事务,否则创建新的事务。详情可参考Spring事务传播行为

事务行为

事务的行为包括事务开启、事务提交和事务回滚。InnoDB所有的用户SQL执行都在事务控制之内,在默认情况下,autocommit设置为true,单条SQL执行成功后,MySQL会自动提交事务,或者如果SQL执行出错,则根据异常类型执行事务提交或者回滚。可以使用START TRANSACTION(SQL标准)或者BEGIN开启事务,使用COMMITROLLBACK提交和回滚事务;也可以通过设置autocommit属性来控制事务行为,当设置autocommit为false时,其后执行的多条SQL语句将在一个事务内,直到执行COMMIT或者ROLLBACK事务才会提交或者回滚。

AOP增强

Spring使用AOP(面向切面编程)来实现声明式事务,后续在讲Spring事务具体实现的时候会详细说明,关于AOP的概念可参考Spring AOP概念理解(通俗易懂),这里不再细说。说下动态代理和AOP增强。

动态代理是Spring实现AOP的默认方式,分为两种:JDK动态代理和CGLIB动态代理。JDK动态代理面向接口,通过反射生成目标代理接口的匿名实现类;CGLIB动态代理则通过继承,使用字节码增强技术(或者objenesis类库)为目标代理类生成代理子类。Spring默认对接口实现使用JDK动态代理,对具体类使用CGLIB,同时也支持配置全局使用CGLIB来生成代理对象。

我们在切面配置中会使用到@Aspect注解,这里用到了Aspectj的切面表达式。Aspectj是java语言实现的一个AOP框架,使用静态代理模式,拥有完善的AOP功能,与Spring AOP互为补充。Spring采用了Aspectj强大的切面表达式定义方式,但是默认情况下仍然使用动态代理方式,并未使用Aspectj的编译器和织入器,当然也支持配置使用Aspectj静态代理替代动态代理方式。Aspectj功能更强大,比方说它支持对字段、POJO类进行增强,与之相对,Spring只支持对Bean方法级别进行增强。

Spring对方法的增强有五种方式:

  • 前置增强(org.springframework.aop.BeforeAdvice):在目标方法执行之前进行增强;
  • 后置增强(org.springframework.aop.AfterReturningAdvice):在目标方法执行之后进行增强;
  • 环绕增强(org.aopalliance.intercept.MethodInterceptor):在目标方法执行前后都执行增强;
  • 异常抛出增强(org.springframework.aop.ThrowsAdvice):在目标方法抛出异常后执行增强;
  • 引介增强(org.springframework.aop.IntroductionInterceptor):为目标类添加新的方法和属性。

声明式事务的实现就是通过环绕增强的方式,在目标方法执行之前开启事务,在目标方法执行之后提交或者回滚事务,事务拦截器的继承关系图可以体现这一点:

Spring事务抽象

统一一致的事务抽象是Spring框架的一大优势,无论是全局事务还是本地事务,JTA、JDBC、Hibernate还是JPA,Spring都使用统一的编程模型,使得应用程序可以很容易地在全局事务与本地事务,或者不同的事务框架之间进行切换。下图是Spring事务抽象的核心类图:

接口PlatformTransactionManager定义了事务操作的行为,其依赖TransactionDefinitionTransactionStatus接口,其实大部分的事务属性和行为我们以MySQL数据库为例已经有过了解,这里再对应介绍下。

  • PlatformTransactionManager:事务管理器

    • getTransaction方法:事务获取操作,根据事务属性定义,获取当前事务或者创建新事物;
    • commit方法:事务提交操作,注意这里所说的提交并非直接提交事务,而是根据当前事务状态执行提交或者回滚操作;
    • rollback方法:事务回滚操作,同样,也并非一定直接回滚事务,也有可能只是标记事务为只读,等待其他调用方执行回滚。
  • TransactionDefinition:事务属性定义
    • getPropagationBehavior方法:返回事务的传播属性,默认是PROPAGATION_REQUIRED
    • getIsolationLevel方法:返回事务隔离级别,事务隔离级别只有在创建新事务时才有效,也就是说只对应传播属性PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEW
    • getTimeout方法:返回事务超时时间,以秒为单位,同样只有在创建新事务时才有效;
    • isReadOnly方法:是否优化为只读事务,支持这项属性的事务管理器会将事务标记为只读,只读事务不允许有写操作,不支持只读属性的事务管理器需要忽略这项设置,这一点跟其他事务属性定义不同,针对其他不支持的属性设置,事务管理器应该抛出异常。
    • getName方法:返回事务名称,声明式事务中默认值为“类的完全限定名.方法名”。
  • TransactionStatus:当前事务状态
    • isNewTransaction方法:当前方法是否创建了新事务(区别于使用现有事务以及没有事务);
    • hasSavepoint方法:在嵌套事务场景中,判断当前事务是否包含保存点;
    • setRollbackOnlyisRollbackOnly方法:只读属性设置(主要用于标记事务,等待回滚)和查询;
    • flush方法:刷新底层会话中的修改到数据库,一般用于刷新如Hibernate/JPA的会话,是否生效由具体事务资源实现决定;
    • isCompleted方法:判断当前事务是否已完成(已提交或者已回滚)。

部分Spring包含的对PlatformTransactionManager的实现类如下图所示:

AbstractPlatformTransactionManager抽象类实现了Spring事务的标准流程,其子类DataSourceTransactionManager是我们使用较多的JDBC单数据源事务管理器,而JtaTransactionManager是JTA(Java Transaction API)规范的实现类,另外两个则分别是JavaEE容器WebLogicWebSphere的JTA事务管理器的具体实现。

Spring事务切面

之前提到,Spring采用AOP来实现声明式事务,那么事务的AOP切面是如何织入的呢?这一点涉及到AOP动态代理对象的生成过程。

代理对象生成的核心类是AbstractAutoProxyCreator,实现了BeanPostProcessor接口,会在Bean初始化完成之后,通过postProcessAfterInitialization方法生成代理对象,关于BeanPostProcessor在Bean生命周期中的作用,可参考一些常用的Spring扩展接口

看一下AbstractAutoProxyCreator类的核心代码,主要关注三个方法:postProcessAfterInitialization、wrapIfNecessary和createProxy,为了突出核心流程,以注释代替了部分代码的具体实现,后续的源码分析也采用相同的处理。

// AbstractAutoProxyCreator.class
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
// 创建代理对象
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
} protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 参数检查,跳过已经执行过代理对象生成,或者已知的不需要生成代理对象的Bean
... // Create proxy if we have advice.
// 查询当前Bean所有的AOP增强配置,最终是通过AOPUtils工具类实现
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 执行AOP织入,创建代理对象
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
} protected Object createProxy(Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) { if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
} // 实例化代理工厂类
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this); // 当全局使用动态代理时,设置是否需要对目标Bean强制使用CGLIB动态代理
... // 构建AOP增强顾问,包含框架公共增强和应用程序自定义增强
// 设置proxyFactory属性,如增强、目标类、是否允许变更等
... // 创建代理对象
return proxyFactory.getProxy(getProxyClassLoader());
}

最后是通过调用ProxyFactory#getProxy(java.lang.ClassLoader)方法来创建代理对象:

// ProxyFactory.class
public Object getProxy(ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
} // ProxyFactory父类ProxyCreatorSupport.class
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
return getAopProxyFactory().createAopProxy(this);
} public ProxyCreatorSupport() {
this.aopProxyFactory = new DefaultAopProxyFactory();
}

ProxyFactory的父类构造器实例化了DefaultAopProxyFactory类,从其源代码我们可以看到Spring动态代理方式选择策略的实现:如果目标类optimize,proxyTargetClass属性设置为true或者未指定需要代理的接口,则使用CGLIB生成代理对象,否则使用JDK动态代理。

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 如果optimize,proxyTargetClass属性设置为true或者未指定代理接口,则使用CGLIB生成代理对象
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
// 参数检查,targetClass为空抛出异常
...
// 目标类本身是接口或者代理对象,仍然使用JDK动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// Objenesis是一个可以不通过构造器创建子类的java工具类库
// 作为Spring 4.0后CGLIB的默认实现
return new ObjenesisCglibAopProxy(config);
}
else {
// 否则使用JDK动态代理
return new JdkDynamicAopProxy(config);
}
}
...
}

Spring事务拦截

我们已经了解了AOP切面织入生成代理对象的过程,当Bean方法通过代理对象调用时,会触发对应的AOP增强拦截器,前面提到声明式事务是一种环绕增强,对应接口为MethodInterceptor,事务增强对该接口的实现为TransactionInterceptor,类图如下:

事务拦截器TransactionInterceptorinvoke方法中,通过调用父类TransactionAspectSupportinvokeWithinTransaction方法进行事务处理,该方法支持声明式事务和编程式事务。

// TransactionInterceptor.class
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
// 获取targetClass
... // Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
@Override
public Object proceedWithInvocation() throws Throwable {
// 实际执行目标方法
return invocation.proceed();
}
});
} // TransactionInterceptor父类TransactionAspectSupport.class
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
throws Throwable { // If the transaction attribute is null, the method is non-transactional.
// 查询目标方法事务属性、确定事务管理器、构造连接点标识(用于确认事务名称)
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// 事务获取
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// 通过回调执行目标方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 目标方法执行抛出异常,根据异常类型执行事务提交或者回滚操作
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 清理当前线程事务信息
cleanupTransactionInfo(txInfo);
}
// 目标方法执行成功,提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
} else {
// 带回调的事务执行处理,一般用于编程式事务
...
}
}

在讲Spring事务抽象时,有提到事务抽象的核心接口为PlatformTransactionManager,它负责管理事务行为,包括事务的获取、提交和回滚。在invokeWithinTransaction方法中,我们可以看到createTransactionIfNecessarycommitTransactionAfterReturningcompleteTransactionAfterThrowing都是针对该接口编程,并不依赖于特定事务管理器,这里是对Spring事务抽象的实现。

//TransactionAspectSupport.class
protected TransactionInfo createTransactionIfNecessary(
PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
...
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
// 获取事务
status = tm.getTransaction(txAttr);
...
} protected void commitTransactionAfterReturning(TransactionInfo txInfo) {
if (txInfo != null && txInfo.hasTransaction()) {
...
// 提交事务
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
} protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.hasTransaction()) {
...
if (txInfo.transactionAttribute.rollbackOn(ex)) {
try {
// 异常类型为回滚异常,执行事务回滚
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
...
} else {
try {
// 异常类型为非回滚异常,仍然执行事务提交
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
...
} protected final class TransactionInfo {
private final PlatformTransactionManager transactionManager;
...

另外,在获取事务时,AbstractPlatformTransactionManager#doBegin方法负责开启新事务,在DataSourceTransactionManager有如下代码:

@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
// 获取数据库连接con
...
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
...
}

这里才真正开启了数据库事务。

Spring事务同步

提到事务传播机制时,我们经常提到一个条件“如果当前已有事务”,那么Spring是如何知道当前是否已经开启了事务呢?在AbstractPlatformTransactionManager中是这样做的:

// AbstractPlatformTransactionManager.class
@Override
public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
Object transaction = doGetTransaction();
// 参数为null时构造默认值
...
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(definition, transaction, debugEnabled);
}
... // 获取当前事务对象
protected abstract Object doGetTransaction() throws TransactionException; // 判断当前事务对象是否包含活跃事务
protected boolean isExistingTransaction(Object transaction) throws TransactionException {
return false;
}

注意getTransaction方法是final的,无法被子类覆盖,保证了获取事务流程的一致和稳定。抽象方法doGetTransaction获取当前事务对象,方法isExistingTransaction判断当前事务对象是否存在活跃事务,具体逻辑由特定事务管理器实现,看下我们使用最多的DataSourceTransactionManager对应的实现:

// DataSourceTransactionManager.class
@Override
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);
txObject.setConnectionHolder(conHolder, false);
return txObject;
} @Override
protected boolean isExistingTransaction(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
}

可以看到,获取当前事务对象时,使用了TransactionSynchronizationManager#getResource方法,类图如下:

TransactionSynchronizationManager通过ThreadLocal对象在当前线程记录了resourcessynchronizations属性。resources是一个HashMap,用于记录当前参与事务的事务资源,方便进行事务同步,在DataSourceTransactionManager的例子中就是以dataSource作为key,保存了数据库连接,这样在同一个线程中,不同的方法调用就可以通过dataSource获取相同的数据库连接,从而保证所有操作在一个事务中进行。synchronizations属性是一个TransactionSynchronization对象的集合,AbstractPlatformTransactionManager类中定义了事务操作各个阶段的调用流程,以事务提交为例:

// AbstractPlatformTransactionManager.class
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
prepareForCommit(status);
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
....
else if (status.isNewTransaction()) {
// 记录日志
...
doCommit(status);
}
...
// 事务调用异常处理
...
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
}

我们可以看到,有很多trigger前缀的方法,这些方法用于在事务操作的各个阶段触发回调,从而可以精确控制在事务执行的不同阶段所要执行的操作,这些回调实际上都通过TransactionSynchronizationUtils来实现,它会遍历TransactionSynchronizationManager#synchronizations集合中的TransactionSynchronization对象,然后分别触发集合中各元素对应方法的调用。例如:

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// do something after commit
}
});

这段代码就在当前线程的事务synchronizations属性中,添加了一个自定义同步类,如果当前存在事务,那么在事务管理器执行事务提交之后,就会触发afterCommit方法,可以通过这种方式在事务执行的不同阶段自定义一些操作。

到这里,我们已经对Spring事务的实现原理和处理流程有了一定的了解。

想要阅读更多技术干货、行业洞察,欢迎关注网易云信博客

了解网易云信,来自网易核心架构的通信与视频云服务。

 

Spring事务原理一探的更多相关文章

  1. Spring事务原理分析-部分二

    Spring事务原理分析-部分二 说明:这是我在蚂蚁课堂学习了余老师Spring手写框架的课程的一些笔记,部分代码代码会用到余老师的课件代码.这不是广告,是我听了之后觉得很好. 课堂链接:Spring ...

  2. Spring事务原理分析-部分一

    Spring事务原理分析-部分一 什么事务 事务:逻辑上的一组操作,组成这组操作的各个单元,要么全都成功,要么全都失败. 事务基本特性 ⑴ 原子性(Atomicity) 原子性是指事务包含的所有操作要 ...

  3. 深入理解 Spring 事务原理

    本文由码农网 – 吴极心原创,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划! 一.事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供 ...

  4. 深入理解 Spring 事务原理【转】

    本文转自码农网 – 吴极心原创  连接地址:http://www.codeceo.com/article/spring-transactions.html 一.事务的基本原理 Spring事务的本质其 ...

  5. 事务之四:Spring事务--原理

    一.Spring事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的.对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行: ...

  6. 理解 Spring 事务原理

    转载:https://www.jianshu.com/p/4312162b1458 一.事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事 ...

  7. 一文带你深入浅出Spring 事务原理

    Spring事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的.对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行: 获 ...

  8. Spring事务原理分析--手写Spring事务

    一.基本概念和原理 1.Spring事务 基于AOP环绕通知和异常通知的 2.Spring事务分为编程式事务.声明事务.编程事务包括注解方式和扫包方式(xml) Spring事务底层使用编程事务(自己 ...

  9. 【Spring】看了这篇Spring事务原理,我才知道我对Spring事务的误解有多深!

    写在前面 有很多小伙伴们留言说,冰河,你能不能写一篇关于Spring事务的文章呢?我:可以啊,安排上了!那还等什么呢?走起啊!! 事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有 ...

随机推荐

  1. 一些常用的WebServices

    天气预报Web服务,数据来源于中国气象局 Endpoint  Disco  WSDL IP地址来源搜索 WEB 服务(是目前最完整的IP地址数据) Endpoint  Disco  WSDL 随机英文 ...

  2. php+mysql+nginx于linux部署对环境

    始终linux在补锅匠,在尚未完成linux根据 - 型nginxserver环境进行部署,这些天来,无论它是什么部署,遇到的问题非常多,今天,我的环境中部署文档发行,够一起讨论一下,希望大家採用后遇 ...

  3. Android 测试 Appium、Robotium、monkey而其他的框架或工具控制

    1. Appium測试 (功能測试,用户接受度測试,黑盒測试) - Rating: 8 Website: http://appium.io/ Appium測试相当于黑盒測试. 仅仅是測试UI逻辑正确性 ...

  4. angular.js分页代码的实例

    对于大多数web应用来说显示项目列表是一种很常见的任务.通常情况下,我们的数据会比较多,无法很好地显示在单个页面中.在这种情况下,我们需要把数据以页的方式来展示,同时带有转到上一页和下一页的功能.现在 ...

  5. asp .net mvc 获得用户IP

    string strHostName = System.Net.Dns.GetHostName(); //clientIPAddress是一个数组,可能有多个数据 var clientIPAddres ...

  6. 微软Insider Dev Tour 活动

    总述 Insider Dev Tour 将在6月全球29个城市中展开.这次活动是通过与微软全球MVPs和RD合作进行的,并涵盖了微软最新的开者方面的最新消息. 日期提示 2018年5月7日 : 暂无其 ...

  7. 移花接木:借助 IViewLocationExpander 更换 ASP.NET Core View Component 视图路径

    端午节在家将一个 asp.net 项目向 asp.net core 迁移时遇到了一个问题,用 view component 取代 Html.RenderAction 之后,运行时 view compo ...

  8. DB First .edmx

    DB First查看Entity相互关系.edmx 图表     .edmx源代码——xml文件右键,打开方式     xml内容     详细查看DB:.edmx—Model Browser(模型浏 ...

  9. WPF 用代码增加路由事件的方法

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threa ...

  10. WPF ScrollViewer(滚动条) 自定义样式表制作 (改良+美化)

    原文:WPF ScrollViewer(滚动条) 自定义样式表制作 (改良+美化) 注释直接写在代码里了   不太理解意思的 可以先去看看我上一篇  WPF ScrollViewer(滚动条)  自定 ...