Spring事务详解与使用

Spring事务核心对象

J2EE开发使用分层设计的思想进行,对于简单的业务层转调数据层的单一操作,事务开启在业务层或者数据层并无太大差别,当业务中包含多个数据层的调用时,需要在业务层开启事务,对数据层中多个操作进行组合并归属于同一个事务进行处理

Spring为业务层提供了整套的事务解决方案:

  • PlatformTransactionManager
  • TransactionDefinition
  • TransactionStatus

PlatformTransactionManager

PlatformTransactionManager:平台事务管理器实现类,是一个接口,需要使用它的实现类

  • DataSourceTransactionManager 适用于Spring JDBC或MyBatis

  • HibernateTransactionManager 适用于Hibernate3.0及以上版本

  • JpaTransactionManager 适用于JPA

  • JdoTransactionManager 适用于JDO

  • JtaTransactionManager 适用于JTA

  • JPA(Java Persistence API)Java EE 标准之一,为POJO提供持久化标准规范,并规范了持久化开发的统一API,符合JPA规范的开发可以在不同的JPA框架下运行

  • JDO(Java Data Object )是Java对象持久化规范,用于存取某种数据库中的对象,并提供标准化API。与JDBC相比,JDBC仅针对关系数据库进行操作,JDO可以扩展到关系数据库、文件、XML、对象数据库(ODBMS)等,可移植性更强

  • JTA(Java Transaction API)Java EE 标准之一,允许应用程序执行分布式事务处理。与JDBC相比,JDBC事务则被限定在一个单一的数据库连接,而一个JTA事务可以有多个参与者,比如JDBC连接、JDO 都可以参与到一个JTA事务中

此接口定义了事务的基本操作

  • 获取事务 :
TransactionStatus getTransaction(TransactionDefinition definition)
  • 提交事务 :
void commit(TransactionStatus status) 
  • 回滚事务 :
void rollback(TransactionStatus status)

TransactionDefinition

此接口定义了事务的基本信息

  • 获取事务定义名称
String getName()
  • 获取事务的读写属性
boolean isReadOnly()
  • 获取事务隔离级别
int getIsolationLevel()
  • 获事务超时时间
int getTimeout()
  • 获取事务传播行为特征
int getPropagationBehavior()

TransactionStatus

此接口定义了事务在执行过程中某个时间点上的状态信息及对应的状态操作

获取事务是否处于新开启事务状态

boolean isNewTransaction()

获取事务是否处于已完成状态

boolean isCompleted()

获取事务是否处于回滚状态

boolean isRollbackOnly()

刷新事务状态

void flush()

获取事务是否具有回滚存储点

boolean hasSavepoint()

设置事务处于回滚状态

void setRollbackOnly()

事务控制方式

  • 编程式

  • 声明式(XML)

  • 声明式(注解)

案例:

模拟银行转账业务说明,银行转账操作中,涉及从A账户到B账户的资金转移操作。数据层仅提供单条数据的基础操作,未设计多账户间的业务操作。

案例环境(基于Spring、Mybatis整合):

pom.xml

   <dependencies>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>5.1.9.RELEASEversion>
        dependency>

        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-jdbcartifactId>
            <version>5.1.9.RELEASEversion>
        dependency>

        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatisartifactId>
            <version>3.5.3version>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>5.1.47version>
        dependency>

        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.1.16version>
        dependency>

        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatis-springartifactId>
            <version>1.3.0version>
        dependency>

        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
            <version>1.9.4version>
        dependency>
        <dependency>
            <groupId>org.junit.jupitergroupId>
            <artifactId>junit-jupiterartifactId>
            <version>RELEASEversion>
            <scope>testscope>
        dependency>
    dependencies>

applicationContext.xml


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:property-placeholder location="classpath:*.properties"/>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    bean>

    <bean id="accountService" class="com.itzhuzhu.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="dataSource" ref="dataSource"/>
    bean>

    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="typeAliasesPackage" value="com.itzhuzhu.domain"/>
    bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.itzhuzhu.dao"/>
    bean>

beans>

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db1
jdbc.username=root
jdbc.password=不告诉你

AccountDao.xml


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itzhuzhu.dao.AccountDao">

    <update id="inMoney">
        update account set money = money + #{money} where name = #{name}
    update>

    <update id="outMoney">
        update account set money = money - #{money} where name = #{name}
    update>

mapper>

dao包下AccountDao

public interface AccountDao {
    void inMoney(@Param("name") String name, @Param("money") Double money);

    void outMoney(@Param("name") String name, @Param("money") Double money);
}

domain包下Account

public class Account implements Serializable {
    private Integer id;
    private String name;
    private Double money;
}

service包下AccountService

public interface AccountService {

    /**
     * 转账操作
     *
     * @param outName 出账用户名
     * @param inName  入账用户名
     * @param money   转账金额
     */
    public void transfer(String outName, String inName, Double money);
}

service.impl包下AccountServiceImpl

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void transfer(String outName, String inName, Double money) {

        // 开启事务
        PlatformTransactionManager pt = new DataSourceTransactionManager(dataSource);
        // 事务定义
        TransactionDefinition td = new DefaultTransactionDefinition();
        // 事务状态
        TransactionStatus ts = pt.getTransaction(td);

        accountDao.inMoney(outName, money);
        int i = 1 / 0;
        accountDao.outMoney(inName, money);

        // 提交事务
        pt.commit(ts);
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService accountService = (AccountService) ctx.getBean("accountService");
        accountService.transfer("张三","李四",100D);
    }
}
编程式事务:
public void transfer(String outName,String inName,Double money){
    //创建事务管理器
    DataSourceTransactionManager dstm = new DataSourceTransactionManager();
    //为事务管理器设置与数据层相同的数据源
    dstm.setDataSource(dataSource);
    //创建事务定义对象
    TransactionDefinition td = new DefaultTransactionDefinition();
    //创建事务状态对象,用于控制事务执行
    TransactionStatus ts = dstm.getTransaction(td);
    accountDao.inMoney(outName,money);
    int i = 1/0;    //模拟业务层事务过程中出现错误
    accountDao.outMoney(inName,money);
    //提交事务
    dstm.commit(ts);
}
使用AOP控制事务

将业务层的事务处理功能抽取出来制作成AOP通知,利用环绕通知运行期动态织入。
aop包下TxAdvice

public class TxAdvice {
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Object transactionManager(ProceedingJoinPoint pjp) throws Throwable {
        // 开启事务
        PlatformTransactionManager pt = new DataSourceTransactionManager(dataSource);
        // 事务定义
        TransactionDefinition td = new DefaultTransactionDefinition();
        // 事务状态
        TransactionStatus ts = pt.getTransaction(td);
        Object ret = pjp.proceed(pjp.getArgs());
        // 提交事务
        pt.commit(ts);
        return ret;
    }
}

AccountServiceImpl

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void transfer(String outName, String inName, Double money) {

        accountDao.inMoney(outName, money);
        int i = 1 / 0;
        accountDao.outMoney(inName, money);
    }
}

applicationContext.xml

    
    <bean id="txAdvice" class="com.itzhuzhu.aop.TxAdvice">
        <property name="dataSource" ref="dataSource"/>
    bean>

    <aop:config>
        <aop:pointcut id="pt" expression="execution(* *..transfer(..))"/>
        <aop:aspect ref="txAdvice">
            <aop:around method="transactionManager" pointcut-ref="pt"/>
        aop:aspect>
    aop:config>

声明式事务(XML)

声明式事务是由Spring操控事务,基于上面的案例改造

applicationContext.xml


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:property-placeholder location="classpath:*.properties"/>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    bean>

    <bean id="accountService" class="com.itzhuzhu.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        
    bean>

    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="typeAliasesPackage" value="com.itzhuzhu.domain"/>
    bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.itzhuzhu.dao"/>
    bean>


    

	
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    bean>

    
    <tx:advice id="txAdvice" transaction-manager="txManager">
        
        <tx:attributes>
            <tx:method name="*" read-only="false"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>

            <tx:method
                    name="transfer"
                    read-only="false"
                    timeout="-1"
                    isolation="DEFAULT"
                    no-rollback-for=""
                    rollback-for=""
                    propagation="REQUIRED"
            />
            
        tx:attributes>
    tx:advice>

    <aop:config>
        <aop:pointcut id="pt" expression="execution(* com.itzhuzhu.service.*Service.*(..))"/>
        <aop:pointcut id="pt2" expression="execution(* com.itzhuzhu.dao.*.b(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt2"/>
    aop:config>
beans>

使用tx命名空间配置事务专属通知类

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="*" read-only="false" />
        <tx:method name="get*" read-only="true" />
        <tx:method name="find*" read-only="true" />
    tx:attributes>
tx:advice>

使用aop:advisor在AOP配置中引用事务专属通知类

<aop:config>
    <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
aop:config>

aop:advice与aop:advisor区别:

  • aop:advice配置的通知类可以是普通java对象,不实现接口,也不使用继承关系

  • aop:advisor配置的通知类必须实现通知接口

    • MethodBeforeAdvice

    • AfterReturningAdvice

    • ThrowsAdvice

tx配置——tx:advice

  • 名称:tx:advice

  • 类型:标签

  • 归属:beans标签

  • 作用:专用于声明事务通知

  • 格式:

<beans>
    <tx:advice id="txAdvice" transaction-manager="txManager">
    tx:advice>
beans>
  • 基本属性:

    • id :用于配置aop时指定通知器的id

    • transaction-manager :指定事务管理器bean

tx配置——tx:attributes

  • 名称:tx:attributes

  • 类型:标签

  • 归属:tx:advice标签

  • 作用:定义通知属性

  • 格式:

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
    tx:attributes>
tx:advice>
  • 基本属性:

tx配置——tx:method

  • 名称:tx:method

  • 类型:标签

  • 归属:tx:attribute标签

  • 作用:设置具体的事务属性

  • 格式:

<tx:attributes>
    <tx:method name="*" read-only="false" />
    <tx:method name="get*" read-only="true" />
tx:attributes>
  • 说明:

    通常事务属性会配置多个,包含1个读写的全事务属性,1个只读的查询类事务属性

tx:method属性:

事务传播行为

  • 事务管理员

  • 事务协调员
    Spring事务详解与使用_第1张图片

  • 事务传播行为描述的是事务协调员对事务管理员所携带事务的处理态度

事务传播应用:

  • 场景A:生成订单业务

    • 子业务S1:记录日志到数据库表X

    • 子业务S2:保存订单数据到数据库表Y

    • 子业务S3:……

    • 如果S2或S3或……事务提交失败,此时S1是否回滚?如何控制?

    • (S1需要新事务)

  • 场景B:生成订单业务

    • 背景1:订单号生成依赖数据库中一个专门用于控制订单号编号生成的表M获取

    • 背景2:每次获取完订单号,表M中记录的编号自增1

    • 子业务S1:从表M中获取订单编号

    • 子业务S2:保存订单数据,订单编号来自于表M

    • 子业务S3:……

    • 如果S2或S3或……事务提交失败,此时S1是否回滚?如何控制?

    • (S1需要新事务)

声明式事务(注解)

@Transactional

  • 名称:@Transactional

  • 类型:方法注解,类注解,接口注解

  • 位置:方法定义上方,类定义上方,接口定义上方(不要写在实现类,实现类会换,一般写接口上,接口方法写详细的,接口类可以做一个大的控制)

  • 作用:设置当前类/接口中所有方法或具体方法开启事务,并指定相关事务属性

  • 范例:

@Transactional
public interface AccountService {

    /**
     * 转账操作
     *
     * @param outName 出账用户名
     * @param inName  入账用户名
     * @param money   转账金额
     */
    @Transactional(
    	readOnly = false,
    	timeout = -1,
    	isolation = Isolation.DEFAULT,
    	rollbackFor = {java.lang.ArithmeticException.class, IOException.class},
    	noRollbackFor = {},
    	propagation = Propagation.REQUIRED)
    public void transfer(String outName, String inName, Double money);
}

tx:annotation-driven

  • 名称:tx:annotation-driven

  • 类型:标签

  • 归属:beans标签

  • 作用:开启事务注解驱动,并指定对应的事务管理器

  • 范例:

    <tx:annotation-driven transaction-manager="txManager"/>
    

声明式事务(纯注解驱动)

  • 名称:@EnableTransactionManagement

  • 类型:类注解

  • 位置:Spring注解配置类上方

  • 作用:开启注解驱动,等同XML格式中的注解驱动

  • 范例:

AccountDao

public interface AccountDao {

    @Update("update account set money = money + #{money} where name = #{name}")
    void inMoney(@Param("name") String name, @Param("money") Double money);

    @Update("update account set money = money - #{money} where name = #{name}")
    void outMoney(@Param("name") String name, @Param("money") Double money);

}

AccountServiceImpl

@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    public void transfer(String outName, String inName, Double money) {
        accountDao.inMoney(outName,money);
        int i = 1/0;
        accountDao.outMoney(inName,money);
    }
}

JDBCConfig

public class JDBCConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean("dataSource")
    public DataSource getDataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }

    @Bean
    public PlatformTransactionManager getTransactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

MyBatisConfig

public class MyBatisConfig {

    @Bean
    public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.itzhuzhu.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
    }

    @Bean
    public MapperScannerConfigurer getMapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.itzhuzhu.dao");
        return msc;
    }
}

SpringConfig

@Configuration
@ComponentScan("com.itzhuzhu")
@PropertySource("classpath:jdbc.properties")
@Import({JDBCConfig.class,MyBatisConfig.class,TransactionManagerConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
public class TransactionManagerConfig {
    @Bean
    public PlatformTransactionManager getTransactionManager(@Autowired DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

你可能感兴趣的:(Spring,spring,java-ee,java)