spring learning 12- declarative transaction management

Spring: declarative transaction

Review transactions

  • Transaction very important in the project development process, involving the issue of consistency of data, can not be sloppy!
  • Transaction management is enterprise-class application development techniques necessary, to ensure the integrity and consistency of the data.

The transaction is a series of operations as an independent unit of work, these actions are either completed or all does not work.

Four ACID properties of the transaction

  • Atomicity (Atomicity)
    transactions are atomic operations, consisting of a series of operations, the operation to ensure atomicity of transactions either completed or totally ineffective
  • Consistency (consistency)
    Once all actions to complete the transaction, the transaction must be submitted. Data and resources in a consistent state that satisfies the business rules in
  • Isolation (Isolation)
    may be a plurality of transactions simultaneously process the same data, so that each transaction with other transactions should be isolated to prevent data corruption
  • Persistence (durability)
    Once the transaction is completed, no matter what system error occurs, the result will not be affected. Typically, the outcome of the transaction is written to persistent storage

Transactions in spring

Spring transaction management to realize there are many details, if you have a general understanding of the entire interface framework will be very beneficial to our understanding of the transaction, the following policy to understand the specific transaction by Spring implement explain Spring's transaction interface.
Contact Spring transaction management interface involved are as follows:

Transaction Manager

Spring does not directly manage affairs, but offers a variety of transaction manager, transaction management responsibilities they will delegate transactions to JTA or Hibernate and other persistence mechanisms provided by the relevant platform framework to achieve.
Spring transaction manager interface is org.springframework.transaction.PlatformTransactionManager, through this interface, Spring provides a corresponding transaction manager for the platform, such as JDBC, Hibernate and so on, but the specific implementation of each platform is its own thing. The contents of this interface is as follows:

Public interface PlatformTransactionManager()...{  
    // 由TransactionDefinition得到TransactionStatus对象
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 
    // 提交
    Void commit(TransactionStatus status) throws TransactionException;  
    // 回滚
    Void rollback(TransactionStatus status) throws TransactionException;  
    } 

From here seen concrete specific transaction management mechanism Spring is transparent, it does not care to those who need to care about is the correspondence of each platform, so the Spring transaction management strengths is to provide a consistent programming across different transaction API models, such as JTA, JDBC, Hibernate, JPA. Such as JDBC transaction management mechanisms to achieve this:

JDBC transactions

If the application directly using JDBC to persist, DataSourceTransactionManager will handle the transaction boundaries for you. To use DataSourceTransactionManager, you need to use the following XML definition of its fit into the context of the application:

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

Define the basic transaction attributes

The above mentioned transaction manager interface via PlatformTransactionManager getTransaction (TransactionDefinition definition) to obtain transaction method, this method is inside the parameters of TransactionDefinition class that is defined transaction some basic properties.
So what is a transaction attributes? Transaction attributes can be configured to understand some basic affairs, describes how the policy is applied to the transaction method. Transaction property contains five aspects, namely:

  • Propagation behavior
  • Isolation Levels
  • Whether read-only
  • Transaction Timeout
  • Rollback rules

The TransactionDefinition Interface reads as follows:

public interface TransactionDefinition {
    int getPropagationBehavior(); // 返回事务的传播行为
    int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
    int getTimeout();  // 返回事务必须在多少秒内完成
    boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
} 
Propagation behavior

The first aspect of the transaction is the spread of behavior (propagation behavior). When the transaction method is invoked by another transaction method, you must specify how the transaction should spread. Eg: methods may continue to operate the existing transaction, or it may open a new transaction and run their own affairs. Spring defines seven communication behavior:

Propagation behavior meaning
propagation_requierd It indicates that the current method must run within a transaction. If no transaction creates a new transaction, if there is a transaction, added to this transaction, which is the most common choice.
propagation_supports Support the current transaction, if there is no current transaction, execute non-transactional method.
propagation_mandatory Indicates that the method must be run within a transaction, if the transaction does not currently exist, it will throw an exception
propagation_required_new It indicates that the current method must run in its own transaction. A new transaction will be started. If there is a current transaction, during the execution of the method, the current transaction will be suspended. If you are using JTATransactionManager, then you need to access TransactionManager
propagation_not_supported It indicates that the method should not be run in a transaction. If there is a current transaction, the duration of the method, the current transaction will be suspended. If you are using JTATransactionManager, then you need to access TransactionManager
propagation_never It indicates that the current method should not be run in a transaction context. If there is a transaction is currently running, an exception is thrown
propagation_nested He said that if there is already a current transaction, which will run in a nested transaction. Independently nested transaction within the current transaction commit or rollback alone. If the current transaction does not exist, then it behaves as PROPAGATION_REQUIRED. Note that each vendor support for the spread of this behavior is somewhat different. You can refer to the Resource Manager documentation to confirm whether they support nested transactions
Isolation sector

The second dimension is the transaction isolation level (isolation level). Isolation levels define the extent of a transaction may be affected by other concurrent transactions.

Dirty reads, non-repeatable read, phantom read the concept note:

  • 脏读:指当一个事务正字访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。
  • 不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。
  • 幻象读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)
隔离级别 含义
isolation_default 使用后端数据库默认的隔离级别
isolation_read_uncommitted 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
isolation_read_committed 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
isolation_repeatable_read 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
Iisolation_serializable 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的
是否只读

事务的第三个特性是它是否为只读事务。如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。

事务超时

为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。

回滚规则

事务五边形的最后一个方面是一组规则,这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)
但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。

事务状态

上面讲到的调用PlatformTransactionManager接口的getTransaction()的方法得到的是TransactionStatus接口的一个实现,这个接口的内容如下:

public interface TransactionStatus{
    boolean isNewTransaction(); // 是否是新的事物
    boolean hasSavepoint(); // 是否有恢复点
    void setRollbackOnly();  // 设置为只回滚
    boolean isRollbackOnly(); // 是否为只回滚
    boolean isCompleted; // 是否已完成
} 

可以发现这个接口描述的是一些处理事务提供简单的控制事务执行和查询事务状态的方法,在回滚或提交的时候需要应用对应的事务状态。

Spring中的事务实现的几种方式

Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。

编程式事务

  • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
  • 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

声明式事务

  • 一般情况下比编程式事务好用。
  • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
  • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。

声明式事务有三种方式

1、基于 TransactionProxyFactoryBean的声明式事务管理

2、 基于 @Transactional 的声明式事务管理

3、基于Aspectj AOP配置事务

这里用的就是这个

转账的事务管理

(基于spring整合mybatis)

场景:

账户之间A向B转账,转账的整个过程就可以看作是一个事务,这个事务又可以分为两个动作

1、A减少对应的金额

2、B增加对应的金额

如果动作1成功了,动作2失败了,那么事务就是没有提交成功,则需要回滚。

只有动作1和动作2都成功了,这个事务才算是真正完成。

mysql创建表

CREATE TABLE `account` (
  `id` int(20) auto_increment PRIMARY KEY COMMENT '用户id',
  `name` varchar(30) DEFAULT NULL COMMENT '姓名',
  `pwd` varchar(30) DEFAULT NULL COMMENT '密码',
    `money` int DEFAULT NULL COMMENT '金额'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Account账户类

import lombok.Data;//lombok插件

@Data//getter,setter,toString...
public class Account {
    private int id;
    private String name;
    private String pwd;
    private int money;
}

代理接口

AccountMapper.java

public interface AccountMapper {
    Account getAccount(int id);//查找账户
    int updateAccount(Account account);//更新账户
}

代理接口映射文件

AccountMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cong.dao.AccountMapper">
    <select id="getAccount" parameterType="int" resultType="account">
        select * from mybatis.account where id = #{id};
    </select>
    <update id="updateAccount" parameterType="account">
        update mybatis.account set name = #{name},pwd = #{pwd},money = #{money}
        where id = #{id};
    </update>
</mapper>

代理接口实现类

AccountMapperIplm.java

public class AccountMapperImpl extends SqlSessionDaoSupport implements AccountMapper {
    @Override
    public Account getAccount(int id) {
        return getSqlSessionTemplate().getMapper(AccountMapper.class).getAccount(id);
    }

    @Override
    public int updateAccount(Account account) {
        return getSqlSessionTemplate().getMapper(AccountMapper.class).updateAccount(account);
    }
}

业务层接口

public interface AccountService {
    void transaction(int fromId,int targetId,int money);//转账
}

业务接口实现类

import com.cong.dao.AccountMapper;
import com.cong.pojo.Account;

public class AccountServiceImpl implements AccountService {
    private AccountMapper accountMapper;

    public void setAccountMapper(AccountMapper accountMapper) {
        this.accountMapper = accountMapper;
    }

    @Override
    public void transaction(int fromId, int targetId, int money) {
        //1根据id查询转出账户
        Account source = accountMapper.getAccount(fromId);
        //2根据名称查询转入账户
        Account target = accountMapper.getAccount(targetId);
        //3转出账户减钱
        source.setMoney(source.getMoney() - money);
        //4转入账户加钱
        target.setMoney(target.getMoney() + money);
        //5更新转出账户
        accountMapper.updateAccount(source);
        int i = 1/0;//模拟出错
        //6更新转入账户
        accountMapper.updateAccount(target);
    }
}

mybatis配置文件

mybatis-config.xml,留下别名,以容易看出是个整合的项目

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <package name="com.cong.pojo"/>
    </typeAliases>
</configuration>

mybatis整合spring配置文件

spring-dao.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--datasource-->
    <!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的-->
    <bean id="dataSources" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    <!--sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSources"/>
        <!--关联Mybatis-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/cong/dao/*Mapper.xml"/>
        <!--<property name="typeAliases" value="com.cong.pojo.Account"/>-->
    </bean>
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--利用构造器注入,没有setter-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
</beans>

spring配置文件

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--导入整合的配置文件-->
    <import resource="classpath:spring-dao.xml"/>
    <bean id="accountMapper" class="com.cong.dao.AccountMapperImpl">
        <property name="sqlSessionTemplate" ref="sqlSession"/>
    </bean>
    <bean id="accountService" class="com.cong.service.AccountServiceImpl">
        <property name="accountMapper" ref="accountMapper"/>
    </bean>
</beans>

测试

@Test
public void test2() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    AccountService accountService = (AccountService) context.getBean("accountService");
    accountService.transaction(1,2,90);//id为1的账户向id为2的账户转账90元
}

数据库中的内容

id name pws money
1 cong ajd123. 100
2 rainbow 1889869... 5000

此时,没有事务的支持,转账的过程中会出错

帐号1的金额减少了90,而账户2的金额并没有增加90,执行之后的结果会变成这样

id name pws money
1 cong ajd123. 10
2 rainbow 1889869... 5000

基于Aspectj AOP配置事务

修改applicationContext.xml,添加事务的支持

<?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:tx="http://www.springframework.org/schema/tx"
       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/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--导入整合的配置文件-->
    <import resource="classpath:spring-dao.xml"/>
    <bean id="accountMapper" class="com.cong.dao.AccountMapperImpl">
        <property name="sqlSessionTemplate" ref="sqlSession"/>
    </bean>
    <bean id="accountService" class="com.cong.service.AccountServiceImpl">
        <property name="accountMapper" ref="accountMapper"/>
    </bean>
    <!--事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSources"/>
    </bean>
    <!--配置事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            
            <tx:method name="transaction" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    <!--配置aop织入事务-->
    <aop:config>
        <aop:pointcut id="txPointcut" expression="execution(* com.cong.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
</beans>

其中id="transactionManager" 就是创建一个事务管理器,因为

  • 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
  • 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。

配置好事务管理器后我们需要去配置事务的通知

然后再通过aop进行横切,就可以进行事务的管理了。

将数据库中的值恢复到初始状态再测试一下,数据库中的值不变,证明事务起到了作用。

int i = 1/0;//模拟出错注释掉,再次测试,结果如下

id name pws money
1 cong ajd123. 10
2 rainbow 1889869... 5090

至此,转账的事务管理完成

思考问题?

为什么需要配置事务?

  • 如果不配置,就需要我们手动提交控制事务;
  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!

Guess you like

Origin www.cnblogs.com/ccoonngg/p/12026776.html