Spring series, spring transaction management mechanism

Posted by Dingbats on Sat, 15 Jan 2022 23:03:40 +0100

For the integrated configuration of soring and mybatis, refer to the previous chapter Spring series (VII). Integration of spring and MyBatis framework

8 Spring transaction management mechanism

In spring, transactions are submitted automatically, but when we operate data, there are always some business processes that need transaction control.

In the project, the business layer (Service layer) is not only the place to process business, but also the place to write and manage database transactions. To test the transactions, first create the business layer, and intentionally add a line of exception code at the withdrawal place after the business layer writes the code to add user withdrawal, deposit and transfer operations (e.g. int i = 1/0;) Or add a constraint that the minimum account balance is not less than 1 yuan in the database to simulate the unexpected situation in reality. Finally, code the test method, so that the program will have an exception when it executes the error code. Without transaction management, even if there is a withdrawal (transfer out) exception, the depositor still successfully deposits (transfers) the transfer amount, In this way, the bank will lose money; If the transaction management mechanism is added and the configuration of transaction management is correct, the transaction will be rolled back as long as either party of the transfer parties has an exception during the execution of the business, so as to ensure the success or failure of the transaction at the same time!

analysis:

  • You can use MyBatis to control transactions
  • Transactions should be controlled at the business logic level
  • Hard coding method, the code is cumbersome, and destroys stratification, and the code is not easy to maintain

Tips:

  • It can be implemented by AOP
  • Spring provides declarative transaction support

8.1 core issues of declarative affairs

What methods and transaction strategies are adopted;

Configuration steps:

  • Import tx and aop namespaces;
  • Define transaction management beans and inject data source beans into them;
  • Configure transaction enhancement through < TX: advice >, bind transaction manager and define transaction rules for different methods;
  • Configure aspects to combine transaction enhancement with method pointcuts.

8.2 transaction communication mechanism

propagation has the following properties:

parametereffect
REQUIERDIf there is no current transaction, create a new transaction; If a transaction already exists, join it. This is the most common choice
SUPPORTSThe current transaction is supported. If there is no current transaction, it will be executed in a non transaction method
MANDATORYUse the current transaction. If there is no current transaction, throw an exception
REQUIERD_NEWCreate a new transaction. If there is a current transaction, suspend the current transaction
NOT_SOPPORTEDThe operation is performed in a non transactional manner. If there is a current transaction, the current transaction is suspended
NEVERExecute the operation in a non transactional manner, and throw an exception if the current transaction exists
NESTEDExecute within a nested transaction; If there are currently no transactions, a transaction similar to REQUIRED is executed

8.3 create database account table

-- Create table
create table ACCOUNT
(
  accountid    VARCHAR2(20),
  accountname  VARCHAR2(20),
  accountmoney NUMBER(11)
)

-- Add constraint (account amount cannot be less than 1 yuan)
alter table ACCOUNT add constraint CK_ACCOUNT_MONEY check (accountmoney>=1);

--Add two pieces of data
insert into account values('1001','Zhang San',100);
insert into account values('1002','Li Si',1);

8.4 create entity class Account

package com.pojo;
import java.io.Serializable;

/**
 * @author Yisujun (CSDN: qq_52596258)
 * @date 2021-07-20 10:18:37
 */
public class Account implements Serializable {
    private String accountid;
    private String accountname;
    private float accountmoney;

    public String getAccountid() {
        return accountid;
    }

    public void setAccountid(String accountid) {
        this.accountid = accountid;
    }

    public String getAccountname() {
        return accountname;
    }

    public void setAccountname(String accountname) {
        this.accountname = accountname;
    }

    public float getAccountmoney() {
        return accountmoney;
    }

    public void setAccountmoney(float accountmoney) {
        this.accountmoney = accountmoney;
    }
}

8.5 create interface class AccountDaoMapper

package com.dao;

import org.apache.ibatis.annotations.Param;

import javax.ws.rs.PATCH;

/**
 * @author Yisujun (CSDN: qq_52596258)
 * @date 2021-07-20 10:20:04
 */
public interface AccountDaoMapper {

    /**
     * withdraw money
     * @param accountid Withdrawal account id
     * @param money  Withdrawal amount
     * @return
     */
    public int takeMoney(@Param("accountid") String accountid, @Param("money") float money);

    /**
     * deposit
     * @param accountid Deposit account id
     * @param money  Deposit amount
     * @return
     */
    public int saveMoney(@Param("accountid") String accountid,@Param("money")float money);

}

8.6 create interface mapping file accountdaomapper 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">

<!--namespace Is a namespace, which should be consistent with the path of the corresponding interface (if the interface is not created, MyBatis (automatically created when compiling internally)-->
<mapper namespace="com.dao.AccountDaoMapper">

    <!--withdraw money-->
    <update id="takeMoney">
        update account set accountmoney = accountmoney - #{money} where accountid = #{accountid}
    </update>

    <!--deposit-->
    <update id="saveMoney">
        update account set accountmoney = accountmoney + #{money} where accountid = #{accountid}
    </update>

</mapper>

8.7 create business logic layer interface AccountService

package com.service;

/**
 * @author Yisujun (CSDN: qq_52596258)
 * @date 2021-07-20 10:30:13
 */
public interface AccountService {

    /**
     * transfer accounts
     * @param accountid1 Withdrawal id
     * @param accountid2 Deposit id
     * @param money Transfer amount
     */
    public int transMoney(String accountid1,String accountid2,float money);
}

8.8 create business logic layer interface implementation class AccountServiceImpl

package com.service.impl;

import com.dao.AccountDaoMapper;
import com.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.ejb.TransactionManagement;


/**
 * @author Yisujun (CSDN: qq_52596258)
 * @date 2021-07-20 10:32:59
 */
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {

    @Autowired
    AccountDaoMapper accountDaoMapper;

    /**
     * withdraw money
     * @param accountid1 Withdrawal id
     * @param accountid2 Deposit id
     * @param money Transfer amount
     */
    @Override
    public int transMoney(String accountid1, String accountid2, float money) {

        //If you withdraw first
        int r1 = accountDaoMapper.takeMoney(accountid1,money);
        //Post deposit
        int r2 = accountDaoMapper.saveMoney(accountid2,money);

        return 1;
    }
}

8.9 create a test class TestTransactionManager (test transfer when transaction management is not enabled)

package com.test;

import com.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author Yisujun (CSDN: qq_52596258)
 * @date 2021-07-20 10:47:10
 */
public class TestTransactionManager {
    public static void main(String[] args) {

        ApplicationContext ap = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService accountService = (AccountService)ap.getBean("accountServiceImpl");
        String accountid1 = "1001";//Zhang San account id
        String accountid2 = "1002";//Li Si account id
        float money = 100;//Transfer amount
        int r = accountService.transMoney(accountid1,accountid2,money);
        System.out.println();
    }
}

Execution results:


The above transaction management is not enabled, and the transfer fails because it violates the constraint of the accountmoney key, which cannot be less than 1 yuan. Because Zhang San wants to transfer out 100 yuan, there is only 100 yuan in his account, so zero after transfer out violates the constraint, so the transfer fails!

8.10 when the deposit is executed before withdrawal, the transfer is still tested without opening the transaction management


Execution results:

If this happens, Zhang San will die of joy! The bank can die! Therefore, we should introduce transaction mechanism to ensure that transactions can succeed or fail at the same time!

8.11 introduce transaction mechanism and implement transaction mechanism based on Aop aspect (applicationConotext.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:context="http://www.springframework.org/schema/context"
       xmlns:bean="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">

    <!--Load connection Oracle Parameter profile for database-->
    <context:property-placeholder location="database.properties"/>

    <!--Configure database connection pool (using alibaba Provided druid Connection pool - derus)-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!--We do not apply here spring Internally provided jdbc Connect to the database, so jdbc.driverClassName No need to import-->
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="filters" value="${jdbc.filters}"/>
        <property name="maxActive" value="${jdbc.maxActive}"/>
        <property name="initialSize" value="${jdbc.initialSize}"/>
        <property name="maxWait" value="${jdbc.maxWait}"/>
        <property name="minIdle" value="${jdbc.minIdle}"/>
        <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
        <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
        <property name="validationQuery" value="${jdbc.validationQuery}"/>
        <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/>
        <property name="testOnBorrow" value="${jdbc.testOnBorrow}"/>
        <property name="testOnReturn" value="${jdbc.testOnReturn}"/>
        <property name="poolPreparedStatements" value="${jdbc.poolPreparedStatements}"/>
        <property name="maxPoolPreparedStatementPerConnectionSize"
                  value="${jdbc.maxPoolPreparedStatementPerConnectionSize}"/>
    </bean>

    <!--use mybatis To operate a database, you must first parse it xml Documents,
    Then according to sqlSessionFactoryBean Factory creation sqlSessionFactory,
    Finally by sqlSessionFactory establish sqlSession To operate the database-->

    <!--stay spring In, mybatis Gave full control over the operation of the database to spring Administration-->
    <!--to configure sqlSessionFactory factory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--Load data source-->
        <property name="dataSource" ref="dataSource"/>
        <!--read mybatis Configuration file (here for future use) mybatis Business expansion (nothing can be done in this file)-->
        <property name="configLocation" value="mybatis-conf.xml"/>
    </bean>

    <!--appoint Spring To which package MyBatis Map file of operation database-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.dao"/>
    </bean>

    <!--Enable annotation scanning - specify Spring Under which package do you scan business logic annotation component classes-->
    <context:component-scan base-package="com.service"/>


    <!--Configure transaction manager-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--Configure transaction propagation mechanism-->
     <tx:advice id="txAdvice">
         <tx:attributes>
             <!--propagation Transaction propagation mechanism-->
             <tx:method name="trans*" propagation="REQUIRED"/>
             <tx:method name="save*" propagation="REQUIRED"/>
             <tx:method name="update*" propagation="REQUIRED"/>
             <tx:method name="add*" propagation="REQUIRED"/>
             <tx:method name="modify*" propagation="REQUIRED"/>
             <tx:method name="del*" propagation="REQUIRED"/>
             <tx:method name="remove*" propagation="REQUIRED"/>
             <tx:method name="get*" propagation="SUPPORTS"/>
             <tx:method name="find*" propagation="SUPPORTS"/>
             <tx:method name="search*" propagation="SUPPORTS"/>
         </tx:attributes>
     </tx:advice>


     <!--Define section-->
     <aop:config>
         <!--Define pointcuts-->
         <aop:pointcut id="pointcutServiceAdvice" expression="execution(* com.service..*.*(..))"/>
         <!--Open notification-->
         <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcutServiceAdvice"/>
     </aop:config>

</beans>



  • Execute the test class again and view the console information:


    Let's look at the database table records again:

8.12 use annotation to realize transaction management mechanism




Business logic layer implementation class AccountServiceImpl

package com.service.impl;

import com.dao.AccountDaoMapper;
import com.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.ejb.TransactionManagement;


/**
 * @author Yisujun (CSDN: qq_52596258)
 * @date 2021-07-20 10:32:59
 */
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {

    @Autowired
    AccountDaoMapper accountDaoMapper;

    /**
     * withdraw money
     * @param accountid1 Withdrawal id
     * @param accountid2 Deposit id
     * @param money Transfer amount
     */
    @Override
    /*@Transactional(propagation = Propagation.REQUIRED)*/
    public int transMoney(String accountid1, String accountid2, float money) {

        //If you deposit first
        int r2 = accountDaoMapper.saveMoney(accountid2,money);
        //Post withdrawal
        int r1 = accountDaoMapper.takeMoney(accountid1,money);

        return 1;
    }
}


Test the TestTransactionManager class here:


The effect is the same as the transaction management mechanism configured in the xml file above. Annotations are the most widely used in future development. It's OK to understand so much temporarily!!!

Topics: Spring Transactional