参考自:http://www.ibm.com/developerworks/cn/java/j-lo-spring-ts1/index.html
IBM技术博客论坛
第一部分:
有可能数据库本身不支持事务,如使用MylSAM引擎的MySQL数据库,那么在Spring中用和没用事务管理器都是无效的.
但是对于Hibernate来说就不不同,因为Hibernate自己定义了事务管理,重点和Hibernate一级缓存有关.当我们调用CRUD方法的时候
Hibernate并不直接向数据库发送SQL语句,只有在提交事务或者是Flush一级缓存的时候才真正向数据库发送SQL
所以,即使底层数据库不支持事务,Hibernate 的事务管理也是有一定好处的,不会对数据操作的效率造成负面影响。所以,如果是使用 Hibernate 数据访问技术,没有理由不配置 HibernateTransactionManager 事务管理器。
对于简单的应用来说不必要使用应用分层,因为这样会导致生成不必要的包和文件
有关事务方法的嵌套:
实际上就是使用事务传播行为
所谓事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。Spring 支持 7 种事务传播行为:
- PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
- PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
- PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。
@Autowired private ScoreService scoreService; public void logon(String userName) { System.out.println("logon method..."); updateLastLogonTime(userName); //①使用Hibernate数据访问技术 scoreService.addScore(userName, 20); //②使用Spring JDBC数据访问技术 }
public void updateLastLogonTime(String userName) { System.out.println("updateLastLogonTime..."); User user = hibernateTemplate.get(User.class,userName); user.setLastLogonTime(System.currentTimeMillis()); hibernateTemplate.flush(); //③请看下文的分析 }
这个事务方法里面同时使用了2种数据处理技术
在③处,我们显式调用了 flush() 方法,将 Session 中的缓存同步到数据库中,这个操作将即时向数据库发送一条更新记录的 SQL 语句。之所以要在此显式执行 flush() 方法,原因是:默认情况下,Hibernate 要在事务提交时才将数据的更改同步到数据库中,而事务提交发生在 logon() 方法返回前。如果所有针对数据库的更改都使用 Hibernate,这种数据同步延迟的机制不会产生任何问题。但是,我们在 logon() 方法中同时采用了 Hibernate 和 Spring JDBC 混合数据访问技术。Spring JDBC 无法自动感知 Hibernate 一级缓存,所以如果不及时调用 flush() 方法将数据更改同步到数据库,则②处通过 Spring JDBC 进行数据更改的结果将被 Hibernate 一级缓存中的更改覆盖掉,因为,一级缓存在 logon() 方法返回前才同步到数据库!
其实就是说Hibernate的事务处理是在事务方法返回前一刻才实际提交事务,这样的话JDBC的事务修改等就可能被覆盖了,导致数据操作失败
结果:
Hibernate 和 JDBC 这两种数据访问技术在同一事务上下文中“共用”一个连接。在④处,提交 Hibernate 事务,接着在⑤处触发调用底层的 Connection 提交事务。
特殊方法成漏网之鱼:
对于基于接口动态代理的 AOP 事务增强来说,由于接口的方法是 public 的,这就要求实现类的实现方法必须是 public 的(不能是 protected,private 等),同时不能使用 static 的修饰符。所以,可以实施接口动态代理的方法只能是使用“public”或“public final”修饰符的方法,其它方法不可能被动态代理,相应的也就不能实施 AOP 增强,也不能进行 Spring 事务增强了。
基于 CGLib 字节码动态代理的方案是通过扩展被增强类,动态创建子类的方式进行 AOP 增强植入的。由于使用 final、static、private 修饰符的方法都不能被子类覆盖,相应的,这些方法将不能被实施 AOP 增强。所以,必须特别注意这些修饰符的使用,以免不小心成为事务管理的漏网之鱼。
@Service("userService") public class UserService { //① private方法因访问权限的限制,无法被子类覆盖 private void method1() { System.out.println("method1"); } //② final方法无法被子类覆盖 public final void method2() { System.out.println("method2"); } //③ static是类级别的方法,无法被子类覆盖 public static void method3() { System.out.println("method3"); } //④ public方法可以被子类覆盖,因此可以被动态字节码增强 public void method4() { System.out.println("method4"); } }在配置文件中配置好:
<aop:config proxy-target-class="true"> <!-- ①显式使用CGLib动态代理 --> <!-- ②希望对UserService所有方法实施事务增强 --> <aop:pointcut id="serviceJdbcMethod" expression="execution(* user.special.UserService.*(..))"/> <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="jdbcAdvice" order="0"/> </aop:config> <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice>在 ① 处,我们通过 proxy-target-class="true"显式使用 CGLib 动态代理技术,在 ② 处通过 AspjectJ 切点表达式表达 UserService 所有的方法,希望对 UserService 所有方法都实施 Spring AOP 事务增强。
表 2. 不能被 Spring AOP 事务增强的方法
动态代理策略 | 不能被事务增强的方法 |
---|---|
基于接口的动态代理 | 除 public 外的其它所有的方法,此外 public static 也不能被增强 |
基于 CGLib 的动态代理 | private、static、final 的方法 |
这些不能被 Spring 事务增强的方法和可被 Spring 事务增强的方法唯一的区别在 “是否可以主动启动一个新事务”:前者不能而后者可以。对于事务传播行为来说,二者是完全相同的,前者也和后者一样不会造成数据连接的泄漏问题。换句话说,如果这些“特殊方法”被无事务上下文的方法调用,则它们就工作在无事务上下文中;反之,如果被具有事务上下文的方法调用,则它们就工作在事务上下文中。
就是说不能自己创建事务,但是可以用别人已经创建好的事务,通过加入别人的事务来提交.
在实际开发中,最容易造成隐患的是基于 CGLib 的动态代理时的“public static”和“public final”这两种特殊方法。原因是它们本身是 public 的,所以可以直接被外部类(如 Web 层的 Controller 类)调用,只要调用者没有事务上下文,这些特殊方法也就以无事务的方式运作。
第三部分:
Spring JDBC数据连接泄露:
如果直接使用SpringJDBC中获取连接会出现连接泄露:
①直接从数据源获取连接,后续程序没有显式释放该连接
Connection conn = jdbcTemplate.getDataSource().getConnection();
这样的代码就会出现连接泄露,不会自己销毁.除非使用Spring已经封装好的框架等
也可以使用一个工具类DataSourceUtils获取数据连接,这个工具保证了会自己销毁连接.
但是使用DataSourceUtils也可能会出现连接泄露,因为如果当不存在事务的时候就会立刻返回,以至于没有成功的销毁连接
说明有事务上下文时,需要等到整个事务方法(即 logon())返回后,事务上下文绑定的连接才释放。但在没有事务上下文时,logon() 调用 JdbcTemplate 执行完数据操作后,马上就释放连接。
所以要修改这个漏洞,就要使用手动的释放连接
finally {
// ②显式使用DataSourceUtils释放连接
DataSourceUtils.releaseConnection(conn,jdbcTemplate.getDataSource());
}
另一种保证数据源不泄露的方式是使用TransactionAwareDataSourceProxy来代理数据源
在配置文件中配置
<!-- ①对数据源进行代理-->
<bean id="dataSourceProxy"
class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"
p:targetDataSource-ref="dataSource"/>
其他ORM框架一样提供了这种代理,具体可以查询Spring官网