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:
parameter | effect |
---|---|
REQUIERD | If there is no current transaction, create a new transaction; If a transaction already exists, join it. This is the most common choice |
SUPPORTS | The current transaction is supported. If there is no current transaction, it will be executed in a non transaction method |
MANDATORY | Use the current transaction. If there is no current transaction, throw an exception |
REQUIERD_NEW | Create a new transaction. If there is a current transaction, suspend the current transaction |
NOT_SOPPORTED | The operation is performed in a non transactional manner. If there is a current transaction, the current transaction is suspended |
NEVER | Execute the operation in a non transactional manner, and throw an exception if the current transaction exists |
NESTED | Execute 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!!!