Spring JDBC-使用注解配置声明式事务

2021-08-17 10:10:05 浏览数 (1)

  • 系列
  • 概述
  • 使用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

0 人点赞