spring Framework Learning - Personal Case ioc (xml), adding transfer methods, and demonstrating transactions

Keywords: PHP Java Apache MySQL Spring

1. Based on: https://www.cnblogs.com/ccoonngg/p/11223340.html

2. Add transfer method to AccountService interface

    /**
     * Transfer accounts
     * @param sourceId  Transfer account
     * @param targetId  Transfer to account
     * @param money     Transfer amount
     */
    void transfer(int sourceId,int targetId,float money);

3. Add implementation methods to AccountServiceImpl class

    @Override
    public void transfer(int sourceId, int targetId, float money) {
        //1.Find Transfer Account
        Account sourceAccount = accountDao.findAccountById(sourceId);
        //2.Find Transferred Account
        Account targetAccount = accountDao.findAccountById(targetId);
        //3.Transfer account reduction
        sourceAccount.setMoney(sourceAccount.getMoney() - money);
        //4.Transfer amount to account increase or decrease
        targetAccount.setMoney(targetAccount.getMoney() + money);
        //5.Update outgoing accounts
        accountDao.updateAccount(sourceAccount);
        //6.Update Transferred Account
        accountDao.updateAccount(targetAccount);
    }

4. Add unit tests to test classes

    @Test
    public void transfer(){
       as.transfer(1,2,100);
    }

5. Database content before and after unit testing

6. However, if you insert an error statement before step 6 in AccountServiceImpl, such as

        //5.Update outgoing accounts
        accountDao.updateAccount(sourceAccount);
        int i = 1/0;
        //6.Update Transferred Account
        accountDao.updateAccount(targetAccount);

7. Then do another unit test and the results are as follows

Because before updating the transfer account, there was a program error and it stopped running, but the previous programs have been executed

That is, there is no transaction support

Reason

Depending on the current configuration, the configuration object QueryRunner for Dbutils creates a new QueryRunner each time

And pull out a connection object from the data source each time you perform an operation

<!--To configure QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--Injection Data Source-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>

<!-- Configure Data Source -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--Required information for connecting to a database-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/cong"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>

You can see from the following diagram that it interacted with the database several times

8. Improvement, the improved code has become completely disguised

So simply consider it a new project

1. Create a maven project and import related dependencies

<?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.cong</groupId>
    <artifactId>spring_accountTransfer_transaction</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>
        <!-- dbutils Dependency on -->
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <!-- Connection pool dependency -->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
</project>

2. Create the com.cong.utils.ConnectionUtils class in the java directory

package com.cong.utils;

import javax.sql.DataSource;
import java.sql.Connection;

/**
 * Connected tool class that gets a connection from a data source and implements a binding to the thread
 *
 */
public class ConnectionUtils {
    //Need one Connection Type ThreadLocal,Instantiate directly
    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    //this DataSource cannot new,Can only wait spring Inject for us, so we need seter Method
    private DataSource dataSource;
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    //Get the connection for the current thread
    public Connection getThreadConnection(){
        try{
            //1.First from ThreadLocal Get on
            Connection conn = tl.get();
            //2.Determine if there is a connection on the current thread
            if (conn == null) {
                //3.Get a connection from the data source and save it in ThreadLocal in
                conn = dataSource.getConnection();
                tl.set(conn);
            }
            //4.Return connection on current thread
            return conn;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * Unbind connections and threads
     */
    public void removeConnection() {
        tl.remove();
    }
}

3. Create the TransactionManager class under the utils package

package com.cong.utils;

/**
 * Tool class related to transaction management that includes, opening transactions, committing transactions, rolling back transactions, and releasing connections
 * How does this class work?Transaction-related, first connected, and current thread's connection
 * So you need to use the ConnectionUtils class
 * And provides a setter method to wait for spring injection
 */
public class TransactionManager {

    private ConnectionUtils connectionUtils;
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    /**
     * Open Transaction
     */
    public  void beginTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * Submit Transaction
     */
    public  void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * Rollback transaction
     */
    public  void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        }catch (Exception e){
            e.printStackTrace();
        }
    }


    /**
     * Release Connection
     */
    public  void release(){
        try {
            connectionUtils.getThreadConnection().close();//Return to Connection Pool
            connectionUtils.removeConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

4. Create a com.cong.pojo.Account class in the java directory

package com.cong.pojo;

public class Account {
    private int id;
    private String name;
    private float money;

    public int getId() {
        return id;
    }

    public void setId(int 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 +
                '}';
    }
}

5. Create a com.cong.dao package in the java directory, and create Account persistence layer-related interfaces and classes

package com.cong.dao;
import com.cong.pojo.Account;
import java.util.List;
public interface AccountDao {
    List<Account> findAllAccount();//find all
    Account findAccountById(int id);//find one
    void saveAccount(Account account);//save
    void updateAccount(Account account);//update
    void deleteAccount(int id);//delete
}



package com.cong.dao;
import com.cong.pojo.Account;
import com.cong.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.util.List;
/**
 * You can find it in the bean.xml
 * <bean id="accountDao" class="com.cong.dao.AccountDaoImpl">
 <property name="runner" ref="runner"></property>
 * </bean>
 * <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
 *      <constructor-arg name="ds" ref="dataSource"></constructor-arg>
 * </bean>
 * accountDao A QueryRunner object is injected and QueryRunner automatically retrieves a connection from the connection pool
 *But we don't want it to be automatically fetched from the connection pool right now, so we need a ConnectionUtils object in AccountDaoImpl to get the connection
 * Then you need to add a join parameter to runner's query method
 */
public class AccountDaoImpl implements AccountDao {
    private QueryRunner runner;
    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }
    private ConnectionUtils connectionUtils;
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    @Override
    public List<Account> findAllAccount() {
        try {
            return runner.query(connectionUtils.getThreadConnection(),"select * from account", new BeanListHandler<Account>(Account.class));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Account findAccountById(int id) {
        try{
            return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ? ",new BeanHandler<Account>(Account.class),id);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void saveAccount(Account account) {
        try {
            runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money) values(?,?)",account.getName(),account.getMoney());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void updateAccount(Account account) {
        try {
            runner.update(connectionUtils.getThreadConnection(),"update account set name = ?,money = ? where id =?",account.getName(),account.getMoney(),account.getId());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void deleteAccount(int id) {
        try {
            runner.update(connectionUtils.getThreadConnection(),"delete from account where id = ?",id);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

6. Create a com.cong.service in the java directory, and create service-tier-related interfaces and classes

package com.cong.service;

import com.cong.pojo.Account;

import java.util.List;

public interface AccountService {
    List<Account> findAllAccount();//find all
    Account findAccountById(int id);//find one
    void saveAccount(Account account);//save
    void updateAccount(Account account);//update
    void deleteAccount(int id);//delete
    /**
     * Transfer accounts
     * @param sourceId  Transfer account
     * @param targetId  Transfer to account
     * @param money     Transfer amount
     */
    void transfer(int sourceId, int targetId, float money);
}



package com.cong.service;

import com.cong.dao.AccountDao;
import com.cong.pojo.Account;
import com.cong.utils.TransactionManager;

import java.util.List;

/**
 * Business layer implementation class for accounts
 * Transaction control should be at the business level
 */
public class AccountServiceImpl implements AccountService {
    //Business tiers call persistence tiers, so you need objects with a persistence tier
    private AccountDao accountDao;
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
    //Adding transaction support requires TransactionManager Object,This object cannot be created by itself, it can only have spring Injection, so there are setter Method
    private TransactionManager tm;
    public void setTm(TransactionManager tm) {
        this.tm = tm;
    }

    /**
     * Transaction control can be added to all of the following operations
     * Open transactions, perform operations, commit transactions (return results), roll back operations, close transactions
     */
    @Override
    public List<Account> findAllAccount() {
        try {
            //1.Open Transaction
            tm.beginTransaction();
            //2.Perform operation
            List<Account> accounts = accountDao.findAllAccount();
            //3.Submit Transaction
            tm.commit();
            //4.Return results
            return accounts;
        }catch (Exception e){
            //5.rolling back action
            tm.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.Close Transaction
            tm.release();
        }
    }

    @Override
    public Account findAccountById(int id) {
        try {
            //1.Open Transaction
            tm.beginTransaction();
            //2.Perform operation
            Account account = accountDao.findAccountById(id);
            //3.Submit Transaction
            tm.commit();
            //4.Return results
            return account;
        }catch (Exception e){
            //5.rolling back action
            tm.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.Close Transaction
            tm.release();
        }
    }

    @Override
    public void saveAccount(Account account) {
        try {
            //1.Open Transaction
            tm.beginTransaction();
            //2.Perform operation
            accountDao.saveAccount(account);
            //3.Submit Transaction
            tm.commit();
        }catch (Exception e){
            //5.rolling back action
            tm.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.Close Transaction
            tm.release();
        }

    }

    @Override
    public void updateAccount(Account account) {
        try {
            //1.Open Transaction
            tm.beginTransaction();
            //2.Perform operation
            accountDao.updateAccount(account);
            //3.Submit Transaction
            tm.commit();
        }catch (Exception e){
            //5.rolling back action
            tm.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.Close Transaction
            tm.release();
        }

    }

    @Override
    public void deleteAccount(int id) {
        try {
            //1.Open Transaction
            tm.beginTransaction();
            //2.Perform operation
            accountDao.deleteAccount(id);
            //3.Submit Transaction
            tm.commit();
        }catch (Exception e){
            //5.rolling back action
            tm.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.Close Transaction
            tm.release();
        }
    }

    @Override
    public void transfer(int sourceId, int targetId, float money) {
        try {
            //1.Open Transaction
            tm.beginTransaction();
            //2.Perform operation
            //2.1.Find Transfer Account
            Account sourceAccount = accountDao.findAccountById(sourceId);
            //2.2.Find Transferred Account
            Account targetAccount = accountDao.findAccountById(targetId);
            //2.3.Transfer account reduction
            sourceAccount.setMoney(sourceAccount.getMoney() - money);
            //2.4.Transfer amount to account increase or decrease
            targetAccount.setMoney(targetAccount.getMoney() + money);
            //2.5.Update outgoing accounts
            accountDao.updateAccount(sourceAccount);
            //2.6.Update Transferred Account
            accountDao.updateAccount(targetAccount);
            //3.Submit Transaction
            tm.commit();
        }catch (Exception e){
            //5.rolling back action
            tm.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.Close Transaction
            tm.release();
        }
    }
}

7. Create bean.xml under resources

<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- To configure Service -->
    <bean id="accountService" class="com.cong.service.AccountServiceImpl">
        <!-- injection dao -->
        <property name="accountDao" ref="accountDao"></property>
        <property name="tm" ref="tm"></property>
    </bean>

    <!--To configure Dao object-->
    <bean id="accountDao" class="com.cong.dao.AccountDaoImpl">
        <!-- injection QueryRunner -->
        <property name="runner" ref="runner"></property>
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

    <!--To configure QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--<constructor-arg name="ds" ref="dataSource"></constructor-arg>-->
    </bean>

    <!-- Configure Data Source -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--Required information for connecting to a database-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/cong"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
    <bean id="connectionUtils" class="com.cong.utils.ConnectionUtils">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <bean id="tm" class="com.cong.utils.TransactionManager">
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
</beans>

8. Create test classes under test, java

import com.cong.pojo.Account;
import com.cong.service.AccountService;
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;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
    @Autowired
    AccountService as;
    @Test
    public void transfer(){
        as.transfer(1,2,100);
    }
   @Test
    public void testFindAll(){
       List<Account> list = as.findAllAccount();
       for (Account account : list) {
           System.out.println(account.toString());
       }
   }
    @Test
    public void testFindOne(){
        Account account = as.findAccountById(1);
        System.out.println(account.toString());
    }
    @Test
    public void testSave(){
       Account account = new Account();
       account.setName("cong");
       account.setMoney(5);
       as.saveAccount(account);
    }
    @Test
    public void testUpdate(){
        Account account = new Account();
        account.setName("rainbow");
        account.setMoney(50000);
        account.setId(3);
        as.updateAccount(account);
    }
    @Test
    public void testDelete(){
        as.deleteAccount(4);
    }
}

9. Unit test transfer, front and back results

10. Similarly, add an error statement during the transfer process

            //2.5.Update outgoing accounts
            int i = 1/0;
            accountDao.updateAccount(sourceAccount);
            //2.6.Update Transferred Account
            accountDao.updateAccount(targetAccount);

11. Running result, program error, content in database unchanged

 

11. Although transaction management has been added to the above cases, there are some drawbacks.

* Profiles are cumbersome, and many dependency injections are messy

*service uses Transaction Manager, dao connectionUtils

* Transaction Managers also use connectionUtils, which have some interdependencies

*More importantly, the code in AccountServiceImpl has a lot of duplication

So you need to use aop

Posted by lucidpc on Mon, 22 Jul 2019 10:44:27 -0700