博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。...
阅读量:5012 次
发布时间:2019-06-12

本文共 20310 字,大约阅读时间需要 67 分钟。

一、基础工作

首先修改我们上一次做的 ,如下所示:

    将xml声明式事务删除

 

 

java代码:
Java代码  
  1. <aop:config expose-proxy="true">   
  2.         <!-- 只对业务逻辑层实施事务 -->   
  3.         <aop:pointcut id="txPointcut" expression="execution(* cn.javass..service..*.*(..))" />   
  4.         <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>   
  5.     </aop:config>  

 

    并添加注解式事务支持:

 

 

java代码:
Java代码  
  1. <tx:annotation-driven transaction-manager="txManager"/>  

 

    在我们的BaseService接口上添加 @Transactional 使该方法开启事务

 

 

java代码:
Java代码  
  1. package cn.javass.common.service;   
  2. public interface IBaseService<M extends java.io.Serializable, PK extends java.io.Serializable> {   
  3. @Transactional   //开启默认事务   
  4.     public int countAll();   
  5. }    
package cn.javass.common.service;public interface IBaseService
{@Transactional //开启默认事务 public int countAll();}

 

在我们的log4j.properties中添加如下配置,表示输出spring的所有debug信息

 

java代码:
Java代码  
  1. log4j.logger.org.springframework=INFO,CONSOLE  
log4j.logger.org.springframework=INFO,CONSOLE

 

在我们的resources.properties里将hibernate.show_sql=true 改为true,为了看到hibernate的sql。

 

 

单元测试类:

 

java代码:
Java代码  
  1. package cn.javass.ssonline.spider.service.impl;   
  2.   
  3. import org.junit.Test;   
  4. import org.junit.runner.RunWith;   
  5. import org.springframework.beans.factory.annotation.Autowired;   
  6. import org.springframework.test.context.ContextConfiguration;   
  7. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;   
  8. import org.springframework.test.context.transaction.TransactionConfiguration;   
  9.   
  10. import cn.javass.demo.service.UserService;   
  11. @RunWith(SpringJUnit4ClassRunner.class)   
  12. @ContextConfiguration(locations = {
    "classpath:spring-config.xml"})   
  13. public class UserServiceTest2 {   
  14.        
  15.     @Autowired  
  16.     private UserService userService;   
  17.     @Test  
  18.     public void testCreate() {   
  19.        userService.countAll();   
  20.     }   
  21. }  
package cn.javass.ssonline.spider.service.impl;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import org.springframework.test.context.transaction.TransactionConfiguration;import cn.javass.demo.service.UserService;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = {"classpath:spring-config.xml"})public class UserServiceTest2 {        @Autowired    private UserService userService;    @Test    public void testCreate() {       userService.countAll();    }}

 

基础工作做好,接下来我们详细看看 Spring基于 JDK动态代理 和 CGLIB类级别代理到底有什么区别。

 

 

二、基于JDK动态代理:

 

java代码:
Java代码  
  1. <tx:annotation-driven transaction-manager="txManager"/>  

   该配置方式默认就是JDK动态代理方式

 

运行单元测试,核心日志如下:                                     

 

java代码:
Java代码  
  1. 2012-03-07 09:58:44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager - Creating new transaction with name [cn.javass.common.service.impl.BaseService.countAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''      //开启事务   
  2. 2012-03-07 09:58:44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager - Opened new Session    
  3.   
  4. 2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager - Bound value [org.springframework.orm.hibernate4.SessionHolder@1184a4ffor key [org.hibernate.internal.SessionFactoryImpl@107b56e] to thread [main] //绑定session到ThreadLocal   
  5. 2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager - Initializing transaction synchronization   
  6. 2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.interceptor.TransactionInterceptor - Getting transaction for [cn.javass.common.service.impl.BaseService.countAll]   
  7. 2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager - Retrieved value [org.springframework.orm.hibernate4.SessionHolder@1184a4ffor key [org.hibernate.internal.SessionFactoryImpl@107b56e] bound to thread [main]   
  8. 2012-03-07 09:58:44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager - Found thread-bound Session    
  9.   
  10. 2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager - Retrieved value [org.springframework.orm.hibernate4.SessionHolder@1184a4ffor key [org.hibernate.internal.SessionFactoryImpl@107b56e] bound to thread [main]   
  11. Hibernate:    
  12.     select   
  13.         count(*) as col_0_0_    
  14.     from   
  15.         tbl_user usermodel0_   
  16.   
  17. 2012-03-07 09:58:44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager - Committing Hibernate transaction on Session    //提交事务   
  18.   
  19. 2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager - Removed value [org.springframework.orm.hibernate4.SessionHolder@1184a4ffor key [org.hibernate.internal.SessionFactoryImpl@107b56e] from thread [main] //解除绑定session到ThreadLocal  
2012-03-07 09:58:44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager - Creating new transaction with name [cn.javass.common.service.impl.BaseService.countAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''      //开启事务2012-03-07 09:58:44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager - Opened new Session 2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager - Bound value [org.springframework.orm.hibernate4.SessionHolder@1184a4f] for key [org.hibernate.internal.SessionFactoryImpl@107b56e] to thread [main] //绑定session到ThreadLocal2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager - Initializing transaction synchronization2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.interceptor.TransactionInterceptor - Getting transaction for [cn.javass.common.service.impl.BaseService.countAll]2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager - Retrieved value [org.springframework.orm.hibernate4.SessionHolder@1184a4f] for key [org.hibernate.internal.SessionFactoryImpl@107b56e] bound to thread [main]2012-03-07 09:58:44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager - Found thread-bound Session 2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager - Retrieved value [org.springframework.orm.hibernate4.SessionHolder@1184a4f] for key [org.hibernate.internal.SessionFactoryImpl@107b56e] bound to thread [main]Hibernate:     select        count(*) as col_0_0_     from        tbl_user usermodel0_2012-03-07 09:58:44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager - Committing Hibernate transaction on Session    //提交事务2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager - Removed value [org.springframework.orm.hibernate4.SessionHolder@1184a4f] for key [org.hibernate.internal.SessionFactoryImpl@107b56e] from thread [main] //解除绑定session到ThreadLocal

 

到此我们可以看到事务起作用了,也就是说即使把@Transactional放到接口上 基于JDK动态代理也是可以工作的。

 

 

三、基于CGLIB类代理:

 

java代码:
Java代码  
  1. <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>  

   该配置方式是基于CGLIB类代理

 

启动测试会报错,No Session found for current thread,说明事务没有起作用

 

java代码:
Java代码  
  1. org.hibernate.HibernateException: No Session found for current thread   
  2.     at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:97)   
  3.     at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1024)   
  4.     at cn.javass.common.dao.hibernate4.BaseHibernateDao.getSession(BaseHibernateDao.java:63)   
  5.     at cn.javass.common.dao.hibernate4.BaseHibernateDao.aggregate(BaseHibernateDao.java:238)   
  6.     at cn.javass.common.dao.hibernate4.BaseHibernateDao.countAll(BaseHibernateDao.java:114)   
  7.     at cn.javass.common.service.impl.BaseService.countAll(BaseService.java:60)   
  8.     at cn.javass.common.service.impl.BaseService$$FastClassByCGLIB$$5b04dd69.invoke(<generated>)   
  9.     at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:149)   
  10.     at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:618)   
  11.     at cn.javass.demo.service.impl.UserServiceImpl$$EnhancerByCGLIB$$7d46c567.countAll(<generated>)   
  12.     at cn.javass.ssonline.spider.service.impl.UserServiceTest2.testCreate(UserServiceTest2.java:20)  
org.hibernate.HibernateException: No Session found for current thread    at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:97)    at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1024)    at cn.javass.common.dao.hibernate4.BaseHibernateDao.getSession(BaseHibernateDao.java:63)    at cn.javass.common.dao.hibernate4.BaseHibernateDao.aggregate(BaseHibernateDao.java:238)    at cn.javass.common.dao.hibernate4.BaseHibernateDao.countAll(BaseHibernateDao.java:114)    at cn.javass.common.service.impl.BaseService.countAll(BaseService.java:60)    at cn.javass.common.service.impl.BaseService$$FastClassByCGLIB$$5b04dd69.invoke(
) at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:149) at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:618) at cn.javass.demo.service.impl.UserServiceImpl$$EnhancerByCGLIB$$7d46c567.countAll(
) at cn.javass.ssonline.spider.service.impl.UserServiceTest2.testCreate(UserServiceTest2.java:20)

 

 

如果将注解放在具体类上或具体类的实现方法上才会起作用。

 

java代码:
Java代码  
  1. package cn.javass.common.service.impl;   
  2. public abstract class BaseService<M extends java.io.Serializable, PK extends java.io.Serializable> implements IBaseService<M, PK> {   
  3.     
  4.    @Transactional()   //放在抽象类上   
  5.     @Override  
  6.     public int countAll() {   
  7.         return baseDao.countAll();   
  8.     }   
  9. }  
package cn.javass.common.service.impl;public abstract class BaseService
implements IBaseService
{ @Transactional() //放在抽象类上 @Override public int countAll() { return baseDao.countAll(); }}

 

运行测试类,将发现成功了,因为我们的UserService继承该方法,但如果UserService覆盖该方法,如下所示,也将无法织入事务(报错):

 

java代码:
Java代码  
  1. package cn.javass.demo.service.impl;   
  2. public class UserServiceImpl extends BaseService<UserModel, Integer> implements UserService {   
  3.     //没有@Transactional   
  4.     @Override    
  5.     public int countAll() {   
  6.         return baseDao.countAll();   
  7.     }   
  8. }  
package cn.javass.demo.service.impl;public class UserServiceImpl extends BaseService
implements UserService { //没有@Transactional @Override public int countAll() { return baseDao.countAll(); }}

 

 

四、基于aspectj的

 

java代码:
Java代码  
  1. <tx:annotation-driven transaction-manager="txManager" mode="aspectj" proxy-target-class="true"/>  

在此就不演示了,我们主要分析基于JDK动态代理和CGLIB类代理两种的区别。

 

五、结论:

基于JDK动态代理 ,可以将@Transactional放置在接口和具体类上。

基于CGLIB类代理,只能将@Transactional放置在具体类上。

 

因此 在实际开发时全部将@Transactional放到具体类上,而不是接口上。

 

六、分析

1、  JDK动态代理

1.1、Spring使用JdkDynamicAopProxy实现代理:

 

java代码:
Java代码  
  1. package org.springframework.aop.framework;   
  2. final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {   
  3.     //注意此处的method 一定是接口上的method(因此放置在接口上的@Transactional是可以发现的)   
  4.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {   
  5.     }   
  6. }  
package org.springframework.aop.framework;final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {    //注意此处的method 一定是接口上的method(因此放置在接口上的@Transactional是可以发现的)	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    }}

注意此处的method 一定是接口上的method(因此放置在接口上的@Transactional是可以发现的)

 

1.2、如果<tx:annotation-driven 中 proxy-target-class="true" ,Spring将使用CGLIB动态代理,而内部通过Cglib2AopProxy实现代理,而内部通过DynamicAdvisedInterceptor进行拦截:

 

java代码:
Java代码  
  1. package org.springframework.aop.framework;   
  2. final class Cglib2AopProxy implements AopProxy, Serializable {   
  3.     private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {   
  4.             //注意此处的method 一定是具体类上的method(因此只用放置在具体类上的@Transactional是可以发现的)   
  5.         public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {   
  6.             }   
  7.        }   
  8. }  
package org.springframework.aop.framework;final class Cglib2AopProxy implements AopProxy, Serializable {	private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {            //注意此处的method 一定是具体类上的method(因此只用放置在具体类上的@Transactional是可以发现的)	    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {            }       }}

 

1.3、Spring使用AnnotationTransactionAttributeSource通过查找一个类或方法是否有@Transactional注解事务来返回TransactionAttribute(表示开启事务):

 

java代码:
Java代码  
  1. package org.springframework.transaction.annotation;   
  2. public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource implements Serializable {   
  3.          protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae) {   
  4.         for (TransactionAnnotationParser annotationParser : this.annotationParsers) {   
  5.             TransactionAttribute attr = annotationParser.parseTransactionAnnotation(ae);   
  6.             if (attr != null) {   
  7.                 return attr;   
  8.             }   
  9.         }   
  10.         return null;   
  11.     }   
  12. }  
package org.springframework.transaction.annotation;public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource implements Serializable {         protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae) {		for (TransactionAnnotationParser annotationParser : this.annotationParsers) {			TransactionAttribute attr = annotationParser.parseTransactionAnnotation(ae);			if (attr != null) {				return attr;			}		}		return null;	}}

而AnnotationTransactionAttributeSource又使用SpringTransactionAnnotationParser来解析是否有@Transactional注解:

 

 

java代码:
Java代码  
  1. package org.springframework.transaction.annotation;   
  2.   
  3. public class SpringTransactionAnnotationParser implements TransactionAnnotationParser, Serializable {   
  4.   
  5.     public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {   
  6.         Transactional ann = AnnotationUtils.getAnnotation(ae, Transactional.class);   
  7.         if (ann != null) {   
  8.             return parseTransactionAnnotation(ann);   
  9.         }   
  10.         else {   
  11.             return null;   
  12.         }   
  13.     }   
  14.   
  15.     public TransactionAttribute parseTransactionAnnotation(Transactional ann) {   
  16.            
  17.     }   
  18.   
  19. }  
package org.springframework.transaction.annotation;public class SpringTransactionAnnotationParser implements TransactionAnnotationParser, Serializable {	public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {		Transactional ann = AnnotationUtils.getAnnotation(ae, Transactional.class);		if (ann != null) {			return parseTransactionAnnotation(ann);		}		else {			return null;		}	}	public TransactionAttribute parseTransactionAnnotation(Transactional ann) {		    }}

 

    此处使用AnnotationUtils.getAnnotation(ae, Transactional.class); 这个方法只能发现当前方法/类上的注解,不能发现父类的注解。 Spring还提供了一个 AnnotationUtils.findAnnotation()方法 可以发现父类/父接口中的注解(但spring没有使用该接口)。

 

   如果Spring此处换成AnnotationUtils.findAnnotation(),将可以发现父类/父接口中的注解。

 

 

 

 

这里还一个问题,描述如下:

 

在接口中删除@Transactional   //开启默认事务

 

 

java代码:
Java代码  
  1. package cn.javass.common.service;   
  2. public interface IBaseService<M extends java.io.Serializable, PK extends java.io.Serializable> {   
  3.     public int countAll();   
  4. }    
package cn.javass.common.service;public interface IBaseService
{ public int countAll();}

 

在具体类中添加@Transactional

 

 

java代码:
Java代码  
  1. package cn.javass.common.service.impl;   
  2. public abstract class BaseService<M extends java.io.Serializable, PK extends java.io.Serializable> implements IBaseService<M, PK> {   
  3.   
  4.     @Transactional()   //开启默认事务   
  5.     @Override  
  6.     public int countAll() {   
  7.         return baseDao.countAll();   
  8.     }   
  9. }  
package cn.javass.common.service.impl;public abstract class BaseService
implements IBaseService
{ @Transactional() //开启默认事务 @Override public int countAll() { return baseDao.countAll(); }}

 

 

 

 

 

问题:

    我们之前说过,基于JDK动态代理时, method 一定是接口上的method(因此放置在接口上的@Transactional是可以发现的),但现在我们放在具体类上,那么Spring是如何发现的呢??

    还记得发现TransactionAttribute是通过AnnotationTransactionAttributeSource吗?具体看步骤1.3:

 

而AnnotationTransactionAttributeSource 继承AbstractFallbackTransactionAttributeSource

 

java代码:
Java代码  
  1. package org.springframework.transaction.interceptor;   
  2. public abstract class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource {   
  3.   
  4.     public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) {   
  5.         //第一次 会委托给computeTransactionAttribute   
  6. }   
  7.   
  8.     //计算TransactionAttribute的   
  9.     private TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {   
  10.            
  11.         //省略   
  12.   
  13.         // Ignore CGLIB subclasses - introspect the actual user class.   
  14.         Class<?> userClass = ClassUtils.getUserClass(targetClass);   
  15.         // The method may be on an interface, but we need attributes from the target class.   
  16.         // If the target class is null, the method will be unchanged.   
  17.         //①此处将查找当前类覆盖的方法   
  18.         Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);   
  19.         // If we are dealing with method with generic parameters, find the original method.   
  20.         specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);   
  21.   
  22.         // First try is the method in the target class.   
  23.         TransactionAttribute txAtt = findTransactionAttribute(specificMethod);   
  24.         if (txAtt != null) {   
  25.             return txAtt;   
  26.         }   
  27.   
  28.         //找类上边的注解   
  29.         // Second try is the transaction attribute on the target class.   
  30.         txAtt = findTransactionAttribute(specificMethod.getDeclaringClass());   
  31.         if (txAtt != null) {   
  32.             return txAtt;   
  33.         }   
  34.         //②如果子类覆盖的方法没有 再直接找当前传过来的   
  35.         if (specificMethod != method) {   
  36.             // Fallback is to look at the original method.   
  37.             txAtt = findTransactionAttribute(method);   
  38.             if (txAtt != null) {   
  39.                 return txAtt;   
  40.             }   
  41.             // Last fallback is the class of the original method.   
  42.             return findTransactionAttribute(method.getDeclaringClass());   
  43.         }   
  44.         return null;   
  45.     }   
  46. }  
package org.springframework.transaction.interceptor;public abstract class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource {	public TransactionAttribute getTransactionAttribute(Method method, Class
targetClass) { //第一次 会委托给computeTransactionAttribute} //计算TransactionAttribute的 private TransactionAttribute computeTransactionAttribute(Method method, Class
targetClass) { //省略 // Ignore CGLIB subclasses - introspect the actual user class. Class
userClass = ClassUtils.getUserClass(targetClass); // The method may be on an interface, but we need attributes from the target class. // If the target class is null, the method will be unchanged. //①此处将查找当前类覆盖的方法 Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass); // If we are dealing with method with generic parameters, find the original method. specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); // First try is the method in the target class. TransactionAttribute txAtt = findTransactionAttribute(specificMethod); if (txAtt != null) { return txAtt; } //找类上边的注解 // Second try is the transaction attribute on the target class. txAtt = findTransactionAttribute(specificMethod.getDeclaringClass()); if (txAtt != null) { return txAtt; } //②如果子类覆盖的方法没有 再直接找当前传过来的 if (specificMethod != method) { // Fallback is to look at the original method. txAtt = findTransactionAttribute(method); if (txAtt != null) { return txAtt; } // Last fallback is the class of the original method. return findTransactionAttribute(method.getDeclaringClass()); } return null; }}

 

 

       //①此处将查找子类覆盖的方法

       Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);

 

        // ClassUtils.getMostSpecificMethod

       public static Method getMostSpecificMethod(Method method, Class<?> targetClass) {

       Method specificMethod = null;

       if (method != null && isOverridable(method, targetClass) &&

              targetClass != null && !targetClass.equals(method.getDeclaringClass())) {

           try {

              specificMethod = ReflectionUtils.findMethod(targetClass, method.getName(), method.getParameterTypes());

           } catch (AccessControlException ex) {

              // security settings are disallowing reflective access; leave

              // 'specificMethod' null and fall back to 'method' below

           }

       }

       return (specificMethod != null ? specificMethod : method);

    }

 

    可以看出将找到当前类的那个方法。因此我们放置在BaseService countAll方法上的@Transactional起作用了。

 

 

      //②如果子类覆盖的方法没有 再直接找当前传过来的

       if (specificMethod != method) {

           // Fallback is to look at the original method.

           txAtt = findTransactionAttribute(method);

           if (txAtt != null) {

              return txAtt;

           }

           // Last fallback is the class of the original method.

           return findTransactionAttribute(method.getDeclaringClass());

       }

 

       查找子类失败时直接使用传过来的方法。

 

 

 

 

因此,建议大家使用基于Schema风格的事务(不用考虑这么多问题,也不用考虑是类还是方法)。而@Transactional建议放置到具体类上,不要放置到接口。

转载于:https://www.cnblogs.com/yl2755/archive/2012/07/09/2582303.html

你可能感兴趣的文章
实现QQ第三方登录、网站接入
查看>>
HTML CSS 层叠样式表 三
查看>>
Qt pro pri 文件学习1
查看>>
软件工程概论第六周学习进度条
查看>>
[思路]导入导出功能
查看>>
【iOS】UICollectionView自己定义Layout之蜂窝布局
查看>>
golang——(strings包)常用字符串操作函数
查看>>
发布aar到jcenter
查看>>
跨浏览器问题的五种解决方案
查看>>
XPath定位时,使用文本的方法小技巧。
查看>>
安装pandas报错(AttributeError: 'module' object has no attribute 'main')
查看>>
ch02 fundamental definition 01
查看>>
JSON解析
查看>>
Position is everything?(css定位学习的一些心得)(一)
查看>>
如何提高编程水平
查看>>
Jquery Uploadify3.21.与2.1版本 使用中存在的问题--记录三
查看>>
Linux查看进程的内存占用情况 分类: ubuntu ...
查看>>
[BZOJ 2818]Gcd
查看>>
FORM值传递与地址传递
查看>>
(译)yaml快速教程
查看>>