- 系列
- 概述
- 使用Transactional注解
- txannotation-driven其他属性
- 关于Transaction的属性
- 在何处标注Transactional注解
- 在方法处使用注解
- 使用不同的事务管理器
- 示例
系列
Spring对事务管理的支持概述以及 编程式的事务管理
Spring JDBC-使用XML配置声明式事务
Spring JDBC-使用注解配置声明式事务
概述
除了基于XML的事务配置,Spring还提供了基于注解的事务配置,即通过@Transactional对需要事务增强的Bean接口、实现类或者方法进行标注:在容器中配置基于注解的事务增强驱动,即可以启用基于注解的声明式事务。
使用@Transactional注解
我们来对Spring JDBC-使用XML配置声明式事务中的例子使用@Transactional对基于aop/tx命名空间的事务配置进行改造,我们来感受下二者在使用方式上的差异。
代码语言:javascript复制@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进行加工处理,如下
代码语言:javascript复制<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<tx:annotation-driven transaction-manager="transactionManager"/>
在默认情况下,会自动使用名为transactionManager事务管理器, 所以,如果我们的事务管理器的id为transactionManager,如上所示,则可以进一步简化为
代码语言:javascript复制<tx:annotation-driven/>
其他属性
- proxy-target-class: 如果为true ,Spring将通过创建子类来代理业务类,若果为false,则使用基于接口的代理。 如果使用子类代理,,需要在类路径中添加CGlib.jar类库
- order:如果业务类除了事务切面外,还要织入其他的切面,则通过该属性可以控制事务切面在目标连接点的织入顺序。
- mode: 模式 ,默认为proxy ,可以选择aspectj
关于@Transaction的属性
基于@Transactional注解的配置和基于XML的配置方式一样,它拥有一组普适性很强的默认事务属性,往往可以直接使用这些默认的属性
- 事务传播行为: PROPAGATION_REQUIRED
- 事务隔离级别:ISOLATION_DEFAULT
- 读写事务属性:读/写事务
- 超时时间:依赖底层的事务属性的默认值
- 回滚设置:任何运行期异常引发回滚,任何检查型异常不会已发回滚。
因为这些默认设置在大多数情况下是都是适用的,所以一般不需要手工设置事务注解的属性(如下面的表格),当然Spring允许通过手工设定属性值覆盖默认值。
属性名 | 说明 |
---|---|
propagation | 事务传播行为,通过org.springframework.transaction.annotation.Propagation枚举类,提供合法值,比如@Transactional(propagation=Propagation.REQUIRES_NEW) |
isolation | 事务的隔离级别,通过org.springframework.transaction.annotation.Isolation枚举类,提供合法值,比如@Transactional(isolation=Isolation.READ_COMMITED) |
readOnly | 事务读写属性,布尔型,比如 @Transactional(readOnly=true) |
timeout | 超时时间,int型,单位为秒,例如 @Transactional(timeout=10) |
rollbackFor | 一组异常类,遇到时进行回滚,类型为 Class[],比如@Transactional(rollbackFor={SQLException,class})。 多个异常之间使用逗号分隔 |
rollbackForClassName | 组异常类,遇到时进行回滚,类型为Strin[],默认值为{},比如@Transactional(rollbackForClassName={“Exception”}) |
noRollbackFor | 一组异常类,遇到时不回滚,类型为 Class[],默认值为{} |
noRolbackForClassName | 一组异常类,遇到时不回滚,类型为 String[],默认值为{} |
在何处标注@Transactional注解
@Transactional注解可以被应用于接口定义和接口方法、类定义和类的Public方法上。
但是Spring建议在业务的实现类上使用@Transactional注解,当然也可以在业务接口上使用@Transactional注解,但是这样会遗留下一些容易被忽视的隐患, 因为注解不能被继承,所以在业务接口中标注的@Transactional注解不会被业务实现类继承。 如果通过以下配置启用了代理类
代码语言:javascript复制<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 注解,这将被忽略,也不会抛出任何异常。
在方法处使用注解
方法处的注解会覆盖类定义的注解,如果有些方法需要使用特殊的事务属性,则可以在类注解的基础上提供方法注解,比如
代码语言:javascript复制@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设置
使用不同的事务管理器
一般情况下,一个应用仅需要使用一个事务管理器, 如果希望在不同的地方使用不同的事务管理器,则可以通过如下方式实现
配置文件:
代码语言:javascript复制<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">
<context:component-scan base-package="com.xgj.dao.transaction.multiTxManager" />
<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}" />
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource" />
<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>
<tx:annotation-driven/>
beans>
使用
代码语言:javascript复制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配置文件中
代码语言:javascript复制 <qualifier value="forum"/>
指定了一个可以被@Transactional注解引用的事务管理器的标识。
我们发现在代码中使用 @Transactional(“forum”) 来引用特定的事务管理器,如果很多地方都需要使用,则显得很麻烦,我们可以通过自定义注解进行标识
代码语言:javascript复制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 {
}
引用的话,调整下代码,如下
代码语言:javascript复制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