java framework SSM learning -- Spring's transaction control

Posted by supernova on Sun, 21 Jun 2020 10:18:32 +0200

When it comes to Spring AOP, we are going to enhance transaction control by ourselves, and the Spring framework is ready for us to use these functions. Of course, don't think that SrpingAOP can only enhance the transaction function. AOP can be used to enhance any method you want to enhance. So how do we use it?

XML configuration declarative transaction

Configuration steps of declarative transaction control based on XML in spring
        1. Configure transaction manager
        
        2. Notification of configuration transaction
            At this point, we need to import constraints of transaction tx namespace and constraints, and also need aop's
            Configure transaction notifications with < TX: advice > tags
                Properties:
                    id: a unique identifier for transaction notification
                    Transaction manager: provides a transaction manager reference for transaction notifications
                    
        3. Configure common pointcut expressions in aop
        
        4. Establish the relationship between transaction notification and pointcut expression
        	Use< aop:advisor >Label
        		Property: pointcut Ref: pointcut expression for reference configuration
        			  Advice Ref: used to refer to transaction notifications
        			  
        5. Configure transaction properties
            Is inside the notification < TX: advice > tag of the transaction
                Isolation: used to specify the isolation level of transactions. The DEFAULT level is DEFAULT, which means to use the DEFAULT isolation level of the database
                Propagation: used to specify the propagation behavior of a transaction. The default value is REQUIRED, which means there must be transaction, adding, deleting and changing options. Support can be selected for query method
                Read only: used to specify whether the transaction is read-only. Only the query method can be set to true. The default value is false, indicating read-write
                Timeout: used to specify the timeout of the transaction. The default value is - 1, which means never timeout. If a value is specified, in seconds
                Rollback for: used to specify an exception. When the exception is generated, the transaction is rolled back. When other exceptions are generated, the transaction is not rolled back. There is no default value. Indicates that any exception is rolled back
                No rollback for: used to specify an exception. When the exception is generated, the transaction will not be rolled back. When other exceptions are generated, the transaction will be rolled back. There is no default value. Indicates that any exception is rolled back

XML configuration transaction case (case using JdbcTemplate)

IAccountDao interface class

package com.dao;

import com.domain.Account;

//Account persistence interface
public interface IAccountDao {
    Account findAccountById(int id);

    Account findAccountByName(String name);

    void updateAccount(Account account);
}

IAccountDao implementation class

package com.dao.impl;

import com.dao.IAccountDao;
import com.domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

//Account persistence layer implementation class
public class AccountDaoImpl implements IAccountDao {

    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public Account findAccountById(int id) {
        List<Account> accountList =  jdbcTemplate.query("select * from account where id = ?" , new BeanPropertyRowMapper<Account>(Account.class) , id);
        return accountList.isEmpty()? null : accountList.get(0);
    }

    public Account findAccountByName(String name) {
        List<Account> accountList = jdbcTemplate.query("select * from account where name = ? " , new BeanPropertyRowMapper<Account>(Account.class) , name);
        if(accountList.isEmpty()){
            return null;
        }
        if(accountList.size() > 1){
            throw new RuntimeException("Result set is not unique");
        }
        return accountList.get(0);
    }

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

IAccountService interface class

package comservice;

import com.domain.Account;

public interface IAccountService {
    Account findAccountById(Integer id);

    void transfer(String sourceName , String targetName , Float money);
}

IAccountService implementation class

package com.service.impl;

import com.dao.IAccountDao;
import com.domain.Account;
import com.service.IAccountService;

import java.util.List;

//Business layer implementation class of account
//Transaction control is at the business level
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;

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

    public Account findAccountById(Integer id) {
        Account accounts = accountDao.findAccountById(id);
        return accounts;
    }

    public void updateAccount(Account account) {
        accountDao.updateAccount(account);
    }

    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("Transfer in progress");
            //1. Query transfer out account by name
            Account source = accountDao.findAccountByName(sourceName);
            //2. Query transfer in account by name
            Account target = accountDao.findAccountByName(targetName);
            //3. Decrease in transfer out account
            source.setMoney(source.getMoney() - money);
            //4. Add money to transfer in account
            target.setMoney(target.getMoney() + money);
            //5. Update transfer out account
            accountDao.updateAccount(source);
            //int i =1/0;
            //6. Update transfer in account
            accountDao.updateAccount(target);
    }
}

bean.xml Constraints for adding transaction constraints

<?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
        https://www.springframework.org/schema/beans/spring-beans.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">

Configure transaction manager

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

Notification of configuration transaction (detailed explanation in the introduction)

<tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes> <!--Configure notifications for transactions-->
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
        </tx:attributes>
    </tx:advice>

Configure AOP to establish the corresponding relationship between pointcut expression and transaction notification at the same time (detailed explanation in the beginning)

<aop:config>
        <! -- configure pointcut expressions -- >
        <aop:pointcut id="pointCut" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <! -- establish correspondence between pointcut expression and transaction notification -- >
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut"></aop:advisor>
    </aop:config>

Test class

package com.test;

import com.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

//Use Junit unit to test our configuration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    private IAccountService as;

    @Test
    public void testTransfer(){
        as.transfer("aaa" , "bbb" , 200f);
    }
}

When there is no abnormality:

After operation:


When an exception occurs:
Turn off the int i =1/0 comment in the transfer method.
After operation:


It can be found that transaction control has been enhanced into the pointcut method. At this point, declarative transactions are configured based on XML.

Annotation configuration declarative transaction

Configuration steps of declarative transaction control with annotations in spring
        1. Configure transaction manager
        2. Enable spring annotation transaction support
        3. Use @ Transactional annotation where transaction support is needed

Note: in this process, pure annotation is used

newly build jdbcConfig.properties Property profile

The information stored in it is needed to create the data source.

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=

New IAccountDao

package com.dao;

import com.domain.Account;

//Account persistence interface
public interface IAccountDao {
    Account findAccountById(int id);

    Account findAccountByName(String name);

    void updateAccount(Account account);
}

New IAccountDao implementation class

package com.dao.impl;

import com.dao.IAccountDao;
import com.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

//Account persistence layer implementation class
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public Account findAccountById(int id) {
        List<Account> accountList =  jdbcTemplate.query("select * from account where id = ?" , new BeanPropertyRowMapper<Account>(Account.class) , id);
        return accountList.isEmpty()? null : accountList.get(0);
    }

    public Account findAccountByName(String name) {
        List<Account> accountList = jdbcTemplate.query("select * from account where name = ? " , new BeanPropertyRowMapper<Account>(Account.class) , name);
        if(accountList.isEmpty()){
            return null;
        }
        if(accountList.size() > 1){
            throw new RuntimeException("Result set is not unique");
        }
        return accountList.get(0);
    }

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

New IAccountService interface class

package com.service;

import com.domain.Account;

public interface IAccountService {
    Account findAccountById(Integer id);

    void transfer(String sourceName , String targetName , Float money);
}

Create a new IAccountService implementation class

package com.service.impl;

import com.dao.IAccountDao;
import com.domain.Account;
import com.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

//Business layer implementation class of account
//Transaction control is at the business level
@Service
//This label indicates that transaction support is required. Only classes with this annotation can start transactions
@Transactional
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDao accountDao;

    public Account findAccountById(Integer id) {
        Account accounts = accountDao.findAccountById(id);
        return accounts;
    }

    public void updateAccount(Account account) {
        accountDao.updateAccount(account);
    }

    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("Transfer in progress");
            //1. Query transfer out account by name
            Account source = accountDao.findAccountByName(sourceName);
            //2. Query transfer in account by name
            Account target = accountDao.findAccountByName(targetName);
            //3. Decrease in transfer out account
            source.setMoney(source.getMoney() - money);
            //4. Add money to transfer in account
            target.setMoney(target.getMoney() + money);
            //5. Update transfer out account
            accountDao.updateAccount(source);
            //int i =1/0;
            //6. Update transfer in account
            accountDao.updateAccount(target);
    }
}

New JdbcConfig configuration class

package config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

//Configuration classes related to connecting database
@Configuration//Configure class labels
@ComponentScan//Packages to be scanned when adding containers
public class JdbcConfig {
	//The Value tag assigns a Value to the variable, and the EL expression in it uses ${} to reference the Value of the external file, which represents the Value in the reference property file.
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

	//The Bean tag stores the returned value object in a container
    @Bean(name="jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

	//The Bean tag stores the returned value object in a container
    @Bean(name="dataSource")
    public DataSource createDataSource(){
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName(driver);
        driverManagerDataSource.setUrl(url);
        driverManagerDataSource.setUsername(username);
        driverManagerDataSource.setPassword(password);
        return driverManagerDataSource;
    }
}

New transaction configuration class TransactionManagerConfig

package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

//Configure class labels
@Configuration
public class TransactionManagerConfig {

	//The Bean tag stores the returned value object in a container
    @Bean(name="transactionManager")
    public PlatformTransactionManager createTransactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

Create a new SpringConfiguration configuration class (equivalent to bean.xml )

package config;

import org.springframework.context.annotation.*;
import org.springframework.transaction.annotation.EnableTransactionManagement;

//Configuration class of spring 
@Configuration//Represents a configuration class
@ComponentScan("com")//Packages to scan when creating a new container
@Import({JdbcConfig.class , TransactionManagerConfig.class})//This configuration class is the main configuration class. If you need to reference other configuration classes, you need to Import them with the Import tag. Property is the bytecode of the configuration class.
@PropertySource("jdbcConfig.properties")//This label is the import property profile
@EnableTransactionManagement//Turn on transaction control
@EnableAspectJAutoProxy//Enable AOP as annotation mode
public class SpringConfiguration {

}

New test class

package com.test;

import com.service.IAccountService;
import config.SpringConfiguration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


//Use Junit unit to test our configuration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {

    @Autowired
    private IAccountService as;

    @Test
    public void testTransfer(){
        as.transfer("aaa" , "bbb" , 500f);
    }
}

test result

Database raw data:

When no exception occurs:

Transfer function is normal

When an exception occurs (open the annotation int i = 1/0 in the transfer method):

As you can see from the results, we succeeded in using declarative transactions in a purely annotated way.

Topics: JDBC Spring xml Junit