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