概述
除了基于XML的事务配置,Spring还提供了基于注解的事务配置,即通过@Transactional对需要事务增强的Bean接口、实现类或者方法进行标注:在容器中配置基于注解的事务增强驱动,即可以启用基于注解的声明式事务。
使用@Transactional注解
我们来对Spring JDBC-使用XML配置声明式事务中的例子使用@Transactional对基于aop/tx命名空间的事务配置进行改造,我们来感受下二者在使用方式上的差异。
@Service @Transactional // 对业务类进行事务增强的标注 public class TeacherService { private TeacherDao teacherDao; public void addTeacher(Teacher teacher) { teacherDao.addTeacher(teacher); } public void updateTeacher(Teacher teacher) { teacherDao.updateTeacher(teacher); } public void getTeacherById(int teacherId) { teacherDao.getTeacher(teacherId); } public void addStudentForTeacher(Teacher teacher, Student student) { teacher.setStudent(student); teacherDao.addStudent(student); } }
因为注解本身具有一组普适性的默认事务属性,所以往往只需要在需要事务管理的业务类中添加一个@Transactional注解,就完成了业务类事务属性的配置.
当然,注解只是提供元数据,它本身并不能完成事务切面织入的功能,因此,还需要在Spring中配置文件中通过一行小小的配置“通知”Spring容器对标注@Transactional注解的Bean进行加工处理,如下
<!--基于数据源的事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"/> <!--对标注了@Transactional注解Bean进行加工处理,以织入事务管理切面--> <tx:annotation-driven transaction-manager="transactionManager"/>
在默认情况下,<tx:annotation-driven>
会自动使用名为transactionManager事务管理器, 所以,如果我们的事务管理器的id为transactionManager,如上所示,则可以进一步简化为
<tx:annotation-driven/>
<tx:annotation-driven>
其他属性
- proxy-target-class: 如果为true ,Spring将通过创建子类来代理业务类,若果为false,则使用基于接口的代理。 如果使用子类代理,,需要在类路径中添加CGlib.jar类库
- order:如果业务类除了事务切面外,还要织入其他的切面,则通过该属性可以控制事务切面在目标连接点的织入顺序。
- mode: 模式 ,默认为proxy ,可以选择aspectj
关于@Transaction的属性
基于@Transactional注解的配置和基于XML的配置方式一样,它拥有一组普适性很强的默认事务属性,往往可以直接使用这些默认的属性
- 事务传播行为: PROPAGATION_REQUIRED
- 事务隔离级别:ISOLATION_DEFAULT
- 读写事务属性:读/写事务
- 超时时间:依赖底层的事务属性的默认值
- 回滚设置:任何运行期异常引发回滚,任何检查型异常不会已发回滚。
因为这些默认设置在大多数情况下是都是适用的,所以一般不需要手工设置事务注解的属性(如下面的表格),当然Spring允许通过手工设定属性值覆盖默认值。
在何处标注@Transactional注解
@Transactional注解可以被应用于接口定义和接口方法、类定义和类的Public方法上。
但是Spring建议在业务的实现类上使用@Transactional注解,当然也可以在业务接口上使用@Transactional注解,但是这样会遗留下一些容易被忽视的隐患, 因为注解不能被继承,所以在业务接口中标注的@Transactional注解不会被业务实现类继承。 如果通过以下配置启用了代理类
<tx:annotation-driven transaction-manager="transactionManager" proxy-target="true"/>
那么业务类不会添加事务增强,照样工作在非事务环境下。 举个例子,如果使用子类代理,假设用户为 XXX接口标注了@Transaction注解,那么其实现类XXXImpl依旧不会启用事务机制。
因此,Spring建议在具体业务类上使用@Transactional注解,这样不管tx:annotation-driven将proxy-target设置为true还是false,业务类都会启用事务机制
@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果我们在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
在方法处使用注解
方法处的注解会覆盖类定义的注解,如果有些方法需要使用特殊的事务属性,则可以在类注解的基础上提供方法注解,比如
@Repository @Transactional // (1)类级注解,适用于类中所有的public方法 public class TeacherDaoImpl extends BaseDao implements TeacherDao { private Logger logger = Logger.getLogger(TeacherDaoImpl.class); private static final String addTeacherSQL = "insert into teacher(id,name,age,sex) values(teacher_id_seq.nextval,?,?,?)"; private static final String queryTeacherByIdSQL = "select name ,age ,sex from teacher where id = ?"; @Transactional(readOnly=true) // (2)提供额外的注解信息,它将覆盖(1)处的类级注解 @Override public Teacher getTeacher(int teacherId) { logger.info("TeacherID:" + teacherId); final Teacher teacher = new Teacher(); jdbcTemplate.query(queryTeacherByIdSQL, new Object[] { teacherId }, new RowCallbackHandler() { @Override public void processRow(ResultSet rs) throws SQLException { teacher.setAge(rs.getInt("age")); teacher.setName(rs.getString("name")); teacher.setSex(rs.getString("sex")); } }); return teacher; } }
(2)处的方法注解提供了readOnly属性,它将覆盖类级注解中默认的readOnly=false设置
使用不同的事务管理器
一般情况下,一个应用仅需要使用一个事务管理器, 如果希望在不同的地方使用不同的事务管理器,则可以通过如下方式实现
配置文件:
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 --> <context:component-scan base-package="com.xgj.dao.transaction.multiTxManager" /> <!-- 使用context命名空间,引入数据库的properties文件 --> <context:property-placeholder location="classpath:spring/jdbc.properties" /> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}" /> <!-- 配置Jdbc模板 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource" /> <!--基于数据源的事务管理器,通过属性引用数据源 可以使用dataSource-ref引用不同的数据源,我们这里只展示同一个--> <bean id="forumTxManger" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"> <!-- 为事务管理器指定一个名字 --> <qualifier value="forum"/> </bean> <bean id="topicTxManger" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"> <!-- 为事务管理器指定一个名字 --> <qualifier value="topic"/> </bean> <!-- 通知Spring处理注解Bean --> <tx:annotation-driven/> </beans>
使用
package com.xgj.dao.transaction.multiTxManager; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class MulitTxServiceWitSpecificName { // (1)使用名为forum的事务管理器 @Transactional("forum") public void addForum() { } // 使用名为topic的事务管理器 @Transactional("topic") public void addTopic() { } }
在(1)处我们为事务管理器指定了一个数据源,每个事务管理器都可以绑定一个独立的数据源。
在spring配置文件中
<!-- 为事务管理器指定一个名字 --> <qualifier value="forum"/>
指定了一个可以被@Transactional注解引用的事务管理器的标识。
我们发现在代码中使用 @Transactional(“forum”) 来引用特定的事务管理器,如果很多地方都需要使用,则显得很麻烦,我们可以通过自定义注解进行标识
package com.xgj.dao.transaction.multiTxManager; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.transaction.annotation.Transactional; @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) // 绑定到forum的事务管理器中 @Transactional("forum") public @interface ForumTransactional { }
引用的话,调整下代码,如下
package com.xgj.dao.transaction.multiTxManager; import org.springframework.stereotype.Service; @Service public class MulitTxServiceWithSelfDefineAnno { // 使用名为forum的事务管理器 @ForumTransactional public void addForum() { } // 使用名为topic的事务管理器 @TopicTransactional public void addTopic() { } }
示例
代码已托管到Github—> https://github.com/yangshangwei/SpringMaster