在初始化应用程序上下文时,Spring遇到带有@Transactional标记的类时会创建代理。 @Transactional可以应用于类级别或方法级别。 在类级别应用它意味着该类中定义的所有公共方法都是事务性的。 Spring创建的代理类型,即Jdk代理或CGLIB代理,取决于将方法标记为事务性的类。 如果该类实现了至少一个接口,则Spring将创建一个Jdk动态代理。 该代理实现与原始类相同的接口,并使用事务维护逻辑拦截接口方法。 它将调用委托给其中组成的原始对象。 假设该类未实现任何接口,Spring将创建一个CGLIB代理。 该代理扩展了原始类并覆盖了公共方法。 我们将尽快对此进行仔细研究。 假设我们有一个这样定义的类:
public interface BookDao{
void buyBook(String isbn) throws BookNotFoundException;
Book findByIsbn(String isbn);
int deductStock(Book book);
}
public class JdbcBookDao implements BookDao{
void buyBook(String isbn) throws BookNotFoundException{
Book book = findByIsbn(isbn);
if(book == null){
throw new BookNotFoundException();
}
deductStock(book);
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
Book findByIsbn(String isbn){
Book book = getJdbcTemplate().queryForObject("SELECT * FROM BOOK WHERE ISBN=?",
ParameterizedBeanPropertyRowMapper.newInstance(Book.class), isbn);
return book;
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
int deductStock(Book book){
String sql = "UPDATE BOOK_STOCK SET STOCK=STOCK-1 WHERE BOOK_ID=?";
return getJdbcTemplate().update(sql, stockIncrement, book.getId());
}
}
现在,Spring是否会通过从main方法调用bookDao的findByIsbn自动创建交易? 否。我们必须在xml配置中声明这一点:
<tx:annotation-driven>
因此,如果它不创建事务,是否会引发错误? 答案还是不是。Spring非事务地执行此语句。
按照我们的预期,一旦声明了‹tx:annotation-driven›,Spring就会在该类实现接口时为JdbcBookDao创建一个Jdk动态代理。 现在说,我们调用JdbcBookDao的buyBook方法,Spring创建了多少个事务?
- 二:1用于findByIsbn,另外1用于deductStock。
- 一:在同一笔交易中同时找到findByIsbn和deductStock。
- 完全没有交易!
答案是(3)。 交易的默认交易模式是“代理”。 这意味着Spring仅考虑通过代理进行的方法调用进行自动事务管理。 现在,如果您仔细观察,buyBook方法不会标记为事务性的。 因此,当创建事务代理时,此方法不会被事务管理逻辑拦截,因为它没有标记为@Transactional。 简而言之,buyBook不会在代理中被覆盖。 因此,该方法直接在原始对象上调用。 因此,其他两个方法也将在原始对象上调用。 请记住,只有PROXY包含事务管理代码。 因此,当其他方法也被原始对象调用时,Spring根本不会创建事务。 现在,如果我们将buyBook标记为@Transactional,是否可以解决问题? Spring是否会为每个findByIsbn和deductStock方法创建两个单独的事务?
不会。在调用buyBook()时,Spring仅创建一个事务。 由于不会在原始对象本身而非代理上调用各个方法,因此不会进一步创建任何新事务。 那么如何解决这个问题呢?
我们可以要求Spring创建一个CGLIB proxy()吗? 现在,由于代理是具有重写的公共事务方法的子类,因此它为每个方法调用创建一个新事务? 再次没有。 CGLIB代理不会直接在超类上调用该方法。 这是一个大概的实现方法。
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyInterceptor implements MethodInterceptor {
private Object realObject;
public MyInterceptor(Object original) {
this.realObject = original;
}
// This method will be called every time the object proxy calls any of its methods
public Object intercept(Object o, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
/**
* Transaction Management Code
*/
// Invoke the method on the real object with the given params
Object res = method.invoke(realObject, args);
/**
* Transaction Management Code
*/
return res;
}
}
import net.sf.cglib.proxy.Enhancer;
public class ProxyCreator(){
public static T createProxy(T original){
// Enhancer is CGLIB class which builds a dynamic proxy with new capabilities
Enhancer e = new Enhancer();
e.setSuperclass(original.getClass());
// We have to declare the interceptor whose 'intercept' will be called when methods are called on proxy.
e.setCallback(new MyInterceptor(original));
T proxy = (T) e.create();
return proxy;
}
}
因此,如您在此处看到的,代理扩展了原始类并由其对象组成。 因此,当我们调用buyBook时,代理会创建一个事务并将该调用委托给原始对象。 从原始对象的buyBook中调用findByIsbn和deductStock不会,因此不会创建新交易。
一个快速的周转解决方案是,因为JdbcBookDao是一个单例,请从应用程序上下文中获取此对象。 现在,与其直接在对象上调用方法,不如使用引用来调用它(以确保调用代理),这就是方法的外观。
public class JdbcBookDao implements BookDao, ApplicationContextAware{
private ApplicationContext context;
private BookDao bookDao;
public void setApplicationContext(ApplicationContext context){
this.context = context;
}
public BookDao getBookDao(){
bookDao = (BookDao)context.getBean("jdbcBookDao");
}
void buyBook(String isbn) throws BookNotFoundException{
Book book = getBookDao().findByIsbn(isbn);
if(book == null){
throw new BookNotFoundException();
}
getBookDao().deductStock(book);
}
.....
}
刚刚实施了一个粗略的版本来使它正常工作。 我们绝对可以改进其设计方式。 与其直接将应用程序上下文注入DAO中,不如说我们可以使用一种辅助类来实现。 或完成此任务的另一种选择是使用程序化事务。
最后要注意的一点是,Spring仅在将公共方法标记为事务性时才管理事务。 对于私有,受保护和程序包私有的方法,Spring不提供事务管理支持。 对于动态代理,当它们实现接口时,所有事务处理方法都是公共的。 因此,无需担心非公开方法。 对于CGLIB代理,在创建子类时仅覆盖公共方法。 因此,即使在这里,也不会考虑非公开方法。
让我以一个问题结束这次讨论。 当我尝试使用‹tx:annotation-driven proxy-target-class =” true” /›代理目标类时,它实际上不起作用,即未创建CGLIB代理 。 为此,我必须进行一些小改动。 正如Spring文档明确指出的那样,如果在‹tx:annotation驱动的›,‹aop:config›或‹aop:aspectj-autoproxy›中的任何一个上启用了代理目标类,Spring将在容器上启用CGLIB代理创建。 因此,我刚刚创建了一个空的‹aop:config proxy-target-class =“ true” /›。 不用担心,它开始起作用! 不知道这是否是Spring本身的错误。 高度赞赏,如果有人可以回答这个问题。
翻译自: https://www.javacodegeeks.com/2013/11/spring-transactions-visibility.html