浅谈Spring之声明式事务

今天来和大家分享一下spring 的事务管理机制。也是很重要的一个概念。简单的说,spring的事务可以分为声明式事务和编程式事务。

首先先来说明一下事务的概念:
事务(Transcation),一般指我们要做的事情。是恢复和并发控制的基本单位。具备四个属性:

  • 原子性(atomicity):一个事务中包括的操作要么都做,要不都不做。
  • 一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
  • 隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  • 持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
    也是成为事务的ACID属性。
二者的区别

编程式事务是需要我们手动去实现的。而声明式事务是基于springAOP来实现的。其本质就是对方法前后实现拦截来实现的。用官方的话说就是,编程式事务的耦合度太高,入侵性比较强的。而声明式恰好弥补了前者的缺点。当然,spring在支持编程式事务时,现在也提出了TransactionTemplate模班。也是spring比较推荐的。

举个简单的例子:
转账:这是在合适不过的例子了。小博要给小硕转2000元。那这个问题应该怎么解决。
首先这应该是一个方法级来实现的。看下面

/****
*  转账方法
*  sourceName:转出账户
*  targerName :转入账户
* money:钱
****/
public boolean transferOfAccount(String sourceName,String targeName,Float money){
		//根据转出账户名查转出账户是否存在
		Account source = accountDao.findAccountByName(sourceName);
		//根据转入账户查询转入账户是否存在
        Account targer = accountDao.findAccountByName(targeName);
        //转出账户减钱
        source.setMoney(source.getMoney()-money);
        //转入账户加钱
        targer.setMoney(targer.getMoney()+money);
        //更新转出账户
        accountDao.updateAccount(source);
      	//更新转入账户
        accountDao.updateAccount(targer);
}

我们普通的方法应该就是这么实现的。但是有一个问题,在上面这个方法里面的任何一个环节出现问题,那么应该是转账失败的。而我们传统的这种写法是没有事务支持的。导致这几个步骤是独立的。每一个步骤是一个独立的事务,执行完即提交。所以,如果在生活中直接用这样的方法来实现转账,那么,天下大乱。
那怎么办?
好,我们先来看一下spring的事务机制实现:
POM文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.tff.demo</groupId>
    <artifactId>spring_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
     
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <!--  解析切点表达式的  -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.6</version>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>

    </dependencies>

</project>

dao层(利用springJdbc来完成对数据库的操作)

package com.springtx.demo.dao;

import com.springtx.demo.domain.Account;

import java.util.List;
import java.util.Map;

public interface AccountDao {
    /**
     * 根据 id 查询账户信息
     *
     * @param id
     * @return
     */
    Account findAccountById(Integer id);

    /**
     * 根据名称查询账户信息
     *
     * @return }
     */
    Account findAccountByName(String name);

    /**
     * 更新账户信息
     *
     * @param account
     */
    void updateAccount(Account account);

    //查询所有的信息
    List<Account> findAccountAll();
}

package com.springtx.demo.dao.impl;

import com.springtx.demo.dao.AccountDao;
import com.springtx.demo.dao.rowmapper.AccountRowMapper;
import com.springtx.demo.domain.Account;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {

    public Account findAccountById(Integer id) {
    	//方法里面第二参数,是我们封装的一个类,这个类实现了RowMapper<T>
        List<Account> list = getJdbcTemplate().query("select * from account1 where id = ?", new AccountRowMapper(), id);
        return list.isEmpty() ? null : list.get(0);
    }

    public Account findAccountByName(String name) {
        List<Account> list = getJdbcTemplate().query("select * from account1 where name = ?", new AccountRowMapper(), name);
        if (list.isEmpty()) {
            return null;
        }
        if (list.size() > 1) {
            throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
        }
        System.out.println("转账方法已经执行完毕=====================================");
        return list.get(0);
    }

    public void updateAccount(Account account) {
        getJdbcTemplate().update("update account1 set name = ?,money = ? where id = ?", account.getName(), account.getMoney(), account.getId());
    }

    public List<Account> findAccountAll() {
        List<Account> list = getJdbcTemplate().query("select * from account1 ", new RowMapper<Account>() {

            public Account mapRow(ResultSet rs, int i) throws SQLException {
                Account account = new Account();
                account.setId(rs.getInt("id"));
                account.setName(rs.getString("name"));
                account.setMoney(rs.getFloat("money"));
                return account;
            }
        });
        return list;
    }
    
}

封装的参数类(也可以写成内部类的形式)

package com.springtx.demo.dao.rowmapper;

import com.springtx.demo.domain.Account;
import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

public class AccountRowMapper implements RowMapper<Account> {

    /**
     *  封装账户的返回数据,也可以写成内部类的形式
     * @param rs
     * @return
     * @throws SQLException
     */
    public Account mapRow(ResultSet rs, int rownum) throws SQLException {
        Account account = new Account();
        account.setId(rs.getInt("id"));
        account.setName(rs.getString("name"));
        account.setMoney(rs.getFloat("money"));
        return account;
    }
}

service层

package com.springtx.demo.service;

import com.springtx.demo.domain.Account;

import java.util.List;

public interface AccountService {
    /**
     * 根据 id 查询账户信息
     * @param id
     * @return
     */
    Account findAccountById(Integer id);//查
    /**
     * 转账
     * @param sourceName  转出账户名称
     * @param targeName 转入账户名称
     * @param money 转账金额
     */
    void transfer(String sourceName,String targeName,Float money);//增删改
    //查询所有的信息
    List<Account> findAccountAll();
}
package com.springtx.demo.service.impl;

import com.springtx.demo.dao.AccountDao;
import com.springtx.demo.domain.Account;
import com.springtx.demo.service.AccountService;
import java.util.List;

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;

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

    public Account findAccountById(Integer id) {

        return accountDao.findAccountById(id);
    }

    public void transfer(String sourceName, String targeName, Float money) {
        Account source = accountDao.findAccountByName(sourceName);
        Account targer = accountDao.findAccountByName(targeName);
        source.setMoney(source.getMoney()-money);
        targer.setMoney(targer.getMoney()+money);
        accountDao.updateAccount(source);
        //int i = 5 / 0;
        accountDao.updateAccount(targer);
    }

    public List<Account> findAccountAll() {
        return accountDao.findAccountAll();
    }
}

实体类:

package com.springtx.demo.domain;

import java.io.Serializable;

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

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Float getMoney() {
        return money;
    }

    public void setMoney(Float money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

日志类(只是为了给大家说明一下springAOP面向切面的编程)
package com.springtx.demo.log;

import java.util.Date;

public class Log {

public void logBefore(){
    System.out.println("之前日志 === Log日志打印"+new Date());
}
public void logAfterReturning(){
    System.out.println("之后日志 === Log日志打印"+new Date());
}
public void logThrow(){
    System.out.println("异常日志 === Log日志打印"+new Date());
}
public void logAfter(){
    System.out.println("最终 === Log日志打印"+new Date());
}
public void loground(){
    System.out.println("环绕日志 === Log日志打印"+new Date());
}

}
spring配置文件

<?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: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/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置service层 -->
    <bean id="accountService" class="com.springtx.demo.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!--  配置dao层  -->
    <bean id="accountDao" class="com.springtx.demo.dao.impl.AccountDaoImpl">
        <!--   注入dataSource  -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--   配置数据源  -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://10.10.6.45:3396/TEST"></property>
        <property name="username" value="root"></property>
        <property name="password" value="Az.123456"></property>
    </bean>

    <!--   配置一个事务管理器 (springJDBC提供的事务机制,一会在后面详细讲解)  -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--  注入dataSource   -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <bean id="log" class="com.springtx.demo.log.Log"></bean>

    <!--   事务的配置(曾强的配置)  -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!--   配置事务的属性 相当于编程时中显示的调用事务  -->
        <tx:attributes>
            <!-- 指定方法名称:是业务核心方法
            read-only:是否是只读事务。默认 false,不只读。
            isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
            propagation:指定事务的传播行为。
            timeout:指定超时时间。默认值为:-1。永不超时。
            rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。
            没有默认值,任何异常都回滚。
            no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回
            滚。没有默认值,任何异常都回滚。
            -->
            <tx:method name="*" read-only="false" propagation="REQUIRED"/>
            <tx:method name="find*" read-only="true" propagation="SUPPORTS"></tx:method>
        </tx:attributes>

    </tx:advice>
    <!--  配置aop -->
    <aop:config>
        <!--  配置切入点表达式 -->
        <aop:pointcut id="pt1" expression="execution(* com.springtx.demo.service.impl.*.*(..))"></aop:pointcut>
		<!--   实现springJdBC的事务机制    -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>

		<!--   为了演示spring的面向切面编程  -->
        <aop:aspect id="logP" ref="log">
            <aop:before method="logBefore" pointcut-ref="pt1"></aop:before>
            <aop:after-throwing method="logThrow" pointcut-ref="pt1"></aop:after-throwing>
            <aop:after-returning method="logAfterReturning" pointcut-ref="pt1"></aop:after-returning>
            <aop:after method="logAfter" pointcut-ref="pt1"></aop:after>
        </aop:aspect>

    </aop:config>

</beans>

运行结果:
在这里插入图片描述我们看到,存续已经正常运行出来了。
可能就会有人问,这不是一样的吗?看代码,好像是一样的。但是,这已经是实现了事务机制的。那个转账方法“transOfAccount”里面的那几个步骤会当成一个事务,还记得我们上面讲的事务的特性吗?其中第一条,原子性。也就是,这个转账方法,要么全部执行完毕,要不就不执行。多用在恢复和事务并发的场合。就已经很能说能问题了。如果,我们在转账过程中,遇到异常或者网络问题转账失败时,事务会进行恢复。要满足一致性的特点。

注解实现方式

很多地方都一样,只需要改动很小的一部分
第一步:配置事务管理器并注入数据源

<!-- 配置事务管理器 -->
<bean  id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>

第二步:在业务层使用@Transactional注解

@Service("accountService")
@Transactional(readOnly=true,propagation=Propagation.SUPPORTS)
public class AccountServiceImpl implements IAccountService {
	@Autowired
	private IAccountDao accountDao;
	@Override
	public Account findAccountById(Integer id) {
		return accountDao.findAccountById(id);
	}
	@Override
	@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
	public void transfer(String sourceName, String targeName, Float money) {
		//1.根据名称查询两个账户
		Account source = accountDao.findAccountByName(sourceName);
		Account target = accountDao.findAccountByName(targeName);
		//2.修改两个账户的金额
		source.setMoney(source.getMoney()-money);//转出账户减钱
		target.setMoney(target.getMoney()+money);//转入账户加钱
		//3.更新两个账户
		accountDao.updateAccount(source);
		//int i=1/0;
		accountDao.updateAccount(target);
	}
}

该注解的属性和 xml 中的属性含义一致。该注解可以出现在接口上,类上和方法上。
出现接口上,表示该接口的所有实现类都有事务支持。
出现在类上,表示类中所有方法有事务支持
出现在方法上,表示方法有事务支持。
以上三个位置的优先级:方法>类>接口
第三步:在配置文件中开启Spring对注解事务的支持

<!-- 开启 spring 对注解事务的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

备注:如果不适用XML来实现第三步,要使用注解的话如下:

@Configuration
@EnableTransactionManagement
public class SpringTxConfiguration {
//里面配置数据源,配置 JdbcTemplate,配置事务管理器。在之前的步骤已经写过了。
}

接下来我们看一张图:
在这里插入图片描述
这是spring对事务管理及涉及的接口:
而我们上面的demo正是用的springJDBC提供的事务支持。所以,我们在配置文件中肯定要注入DataSourceTransactionManager这个类。

spring事务管理器

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。此接口的内容如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.transaction;

public interface PlatformTransactionManager {
	// 由TransactionDefinition得到TransactionStatus对象
    TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;
	//提交事务
    void commit(TransactionStatus var1) throws TransactionException;
	//回滚事务
    void rollback(TransactionStatus var1) throws TransactionException;
}

从这里可知具体的具体的事务管理机制对Spring来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。下面分别介绍各个平台框架实现事务管理的机制。

  1. JDBC事务
    如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:
  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
  1. Hibernate
    如果应用程序的持久化是通过Hibernate实现的,那么你需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的声明:
 <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
  1. Java持久化API(JPA)
    Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。如果你计划使用JPA的话,那你需要使用Spring的JpaTransactionManager来处理事务。你需要在Spring中这样配置JpaTransactionManager:
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合作来构建事务。
4. Java 原生的API
如果你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),你就需要使用JtaTransactionManager:

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManagerName" value="java:/TransactionManager" />
    </bean>

JtaTransactionManager将事务管理的责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象,其中事务成功完成通过UserTransaction.commit()方法提交,事务失败通过UserTransaction.rollback()方法回滚。

对spring声明式事务的讲解就到这里了。欢迎各路大神批评指正!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值