Spring交易可见性

在初始化应用程序上下文时,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. 二:1用于findByIsbn,另外1用于deductStock。
  2. 一:在同一笔交易中同时找到findByIsbn和deductStock。
  3. 完全没有交易!

答案是(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本身的错误。 高度赞赏,如果有人可以回答这个问题。

参考: Spring交易可见性来自prasanthnath博客上的JCG合作伙伴 Prasanth Gullapalli。

翻译自: https://www.javacodegeeks.com/2013/11/spring-transactions-visibility.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值