Spring08_純粋な注釈実戦_取引バージョンのサポート

13428 ワード

このチュートリアルのソースにアクセスしてください:tutorial_demo
前回のチュートリアルでは、Apache Commons DbUtilsと組み合わせて、単一テーブルのCRUD操作を実現しましたが、このチュートリアルの操作はトランザクションをサポートしていません.このチュートリアルでは、既存の知識に基づいて、トランザクションをサポートするバージョンに変更し、後続の学習の準備をしています.
一、振り替え操作問題の分析
次に、問題がある問題を分析するために、振り替え操作を実現します.
1.1、業務層インタフェースIAccountServiceに相応の方法を追加する
//        
void transfer(Integer srcId, Integer dstId, Float money);

1.2.ビジネス層実装クラスAccountServiceImplに新たに追加する方法
//    
@Override
public void transfer(Integer srcId, Integer dstId, Float money) {
    //  Id         
	Account src = accountDao.findById(srcId);
    Account dst = accountDao.findById(dstId);
        
	if(src == null) {
		throw new RuntimeException("       ");
	}
        
	if(dst == null) {
		throw new RuntimeException("       ");
	}
        
	if(src.getMoney() < money) {
		throw new RuntimeException("        ");
    }
     
    //     ,     
	src.setMoney(src.getMoney() - money);
	dst.setMoney(dst.getMoney() + money);
        
    //       
    accountDao.update(src);
    accountDao.update(dst);
}

1.3、試験クラスに試験方法を追加する
@Test
public void testTrans() {
	accountService.transfer(1, 2, 10F);
}

テストメソッドを実行し、テストに成功しました.問題ないようです.
1.4、サービス層の振り込み方法の修正
//    
@Override
public void transfer(Integer srcId, Integer dstId, Float money) {
    //  Id         
	Account src = accountDao.findById(srcId);
    Account dst = accountDao.findById(dstId);
        
	if(src == null) {
		throw new RuntimeException("       ");
	}
        
	if(dst == null) {
		throw new RuntimeException("       ");
	}
        
	if(src.getMoney() < money) {
		throw new RuntimeException("        ");
    }
     
    //     ,     
	src.setMoney(src.getMoney() - money);
	dst.setMoney(dst.getMoney() + money);
        
    //       
    accountDao.update(src);
    
    //      ,        
    int i = 100/0;
    
    //       
    accountDao.update(dst);
}

テストメソッドを実行すると、プログラムに異常が発生しますが、srcはデータベースでお金が減少します.
振込プロセス分析:
  • 振り込みの流れが大きい原則は、ソース口座のお金が減少し、目的口座のお金が増加することである.
  • ソース口座のお金が減少し、目的口座のお金が増加した操作は同時に成功するか、同時に失敗するか.
  • は、同時に成功するために、同時に失敗した操作を同じトランザクションに包むべきである.
  • 同じトランザクション・オペレーションでは、同じ接続(Connection)を使用する必要があります.

  • 現在の問題:現在の振り替えプロセスではトランザクションが開かれておらず、すべての操作で独立した接続が使用されています.
    二、コードアップグレード
    前の分析によると、私たちは工事を修正して、事務をサポートさせ、満たすべき原則は以下の通りです.
  • は、サービス・レベルでトランザクションを処理します(トランザクション、コミット、ロールバックをオンにします).
  • Daoレイヤは、データベース操作のみを担当し、業務を担当しない.すなわち、Dao操作ごとに最も微細なCRUD操作のみを行う.
  • Daoレイヤは例外を処理せず,出現した例外はサービスレイヤに投げ出される.

  • 2.1、取引をサポートするツールクラスの作成
    package org.codeaction.util;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import javax.sql.DataSource;
    import java.sql.Connection;
    import java.sql.SQLException;
    
    @Component
    public class JdbcUtils {
        private static DataSource dataSource;
        //           
        private static ThreadLocal tl = new ThreadLocal();
        //       
        public static DataSource getDataSource() {
              return dataSource;
        }
    
        //      
    	@Autowired
    	public void setDataSource(DataSource dataSource) {
    		JdbcUtils.dataSource = dataSource;
    	}
    
    	//    
        public static Connection getConnection() throws SQLException {
    		System.out.println(dataSource);
        	Connection conn = tl.get();
        	if(conn == null) {
        		return dataSource.getConnection();
        	}
        	return conn;
        }
        //    
        public static void beginTransaction() throws SQLException {
        	Connection conn = tl.get();
    		if(conn != null) {
    			throw new SQLException("      ,      ");
    		}
    		conn = getConnection();
    		conn.setAutoCommit(false);
    		tl.set(conn);
    	}
        //    
        public static void commitTransaction() throws SQLException {
        	Connection conn = tl.get();
    		if(conn == null) {
    			throw new SQLException("    ,      ");
    		}
    		conn.commit();
    		conn.close();
    		tl.remove();
    	}
        //    
        public static void rollbackTransaction() throws SQLException {
    		Connection conn = tl.get();
    		if (conn == null) {
    			throw new SQLException("    ,      ");
    		}
    		conn.rollback();
    		conn.close();
    		tl.remove();
    	}
    }
    

    説明:
  • この工事はThreadLocalを使用し、マルチスレッド環境で使用できることを保証した.
  • dataSourceプロパティは@AutoWiredではなく、setメソッドで@AutoWiredを使用しています.なぜならdataSourceはstaticタイプであり、staticタイプ変数はSpringコンテナの作成よりも早く作成され、クラスローダが静的変数をロードした場合、Springコンテキストはまだロードされていないためです.したがって、クラスローダはbeanに静的クラスを正しく注入せず、失敗します.

  • 2.2.Daoの実装クラスを修正する
    package org.codeaction.dao.impl;
    
    import org.apache.commons.dbutils.QueryRunner;
    import org.apache.commons.dbutils.handlers.BeanHandler;
    import org.apache.commons.dbutils.handlers.BeanListHandler;
    import org.codeaction.dao.IAccountDao;
    import org.codeaction.domain.Account;
    import org.codeaction.util.JdbcUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Repository;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.util.List;
    
    @Repository("accountDao")
    public class AccountDaoImpl implements IAccountDao {
        @Autowired
        private QueryRunner queryRunner;
    
        public void setQueryRunner(QueryRunner queryRunner) {
            this.queryRunner = queryRunner;
        }
    
        @Override
        public List findAll() throws SQLException {
            Connection conn = JdbcUtils.getConnection();
            String sql = "select * from account";
            List list = queryRunner.query(conn, sql, new BeanListHandler(Account.class));
            return list;
        }
    
        @Override
        public Account findById(Integer id) throws SQLException {
            Connection conn = JdbcUtils.getConnection();
            String sql = "select * from account where id = ?";
            Account account = queryRunner.query(conn, sql, new BeanHandler(Account.class), id);
            return account;
        }
    
        @Override
        public void save(Account account) throws SQLException {
            Object[] params = {account.getName(), account.getMoney()};
            Connection conn = JdbcUtils.getConnection();
            String sql = "insert into account(name, money) values(?, ?)";
            queryRunner.update(conn, sql, params);
        }
    
        @Override
        public void update(Account account) throws SQLException {
            Object[] params = {account.getName(), account.getMoney(), account.getId()};
            Connection conn = JdbcUtils.getConnection();
            String sql = "update account set name=?, money=? where id=?";
            queryRunner.update(conn, sql, params);
        }
    
        @Override
        public void delete(Integer id) throws SQLException {
            Object[] params = {id};
            Connection conn = JdbcUtils.getConnection();
            String sql = "delete from account where id=?";
            queryRunner.update(conn, sql, id);
        }
    }
    

    2.3、サービス層実装クラスの修正
    package org.codeaction.service.impl;
    
    import org.codeaction.dao.IAccountDao;
    import org.codeaction.domain.Account;
    import org.codeaction.service.IAccountService;
    import org.codeaction.util.JdbcUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.sql.SQLException;
    import java.util.List;
    
    @Service("accountService")
    public class AccountServiceImpl implements IAccountService {
        @Autowired
        private IAccountDao accountDao;
    
        public void setAccountDao(IAccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        @Override
        public List findAll() {
            List list = null;
    
            try {
                //1.    
                JdbcUtils.beginTransaction();
                //2.  
                list = accountDao.findAll();
                //3.    
                JdbcUtils.commitTransaction();
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    //    
                    JdbcUtils.rollbackTransaction();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
    
            return list;
        }
    
        @Override
        public Account findById(Integer id) {
            Account account = null;
    
            try {
                //1.    
                JdbcUtils.beginTransaction();
                //2.  
                account = accountDao.findById(id);
                //3.    
                JdbcUtils.commitTransaction();
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    //    
                    JdbcUtils.rollbackTransaction();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
            return account;
        }
    
        @Override
        public void save(Account account) {
            try {
                //1.    
                JdbcUtils.beginTransaction();
                //2.  
                accountDao.save(account);
                //3.    
                JdbcUtils.commitTransaction();
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    //    
                    JdbcUtils.rollbackTransaction();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
        }
    
        @Override
        public void update(Account account) {
            try {
                //1.    
                JdbcUtils.beginTransaction();
                //2.  
                accountDao.update(account);
                //3.    
                JdbcUtils.commitTransaction();
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    //    
                    JdbcUtils.rollbackTransaction();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
    
        }
    
        @Override
        public void delete(Integer id) {
            try {
                //1.    
                JdbcUtils.beginTransaction();
                //2.  
                accountDao.delete(id);
                //3.    
                JdbcUtils.commitTransaction();
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    //    
                    JdbcUtils.rollbackTransaction();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
        }
    
        @Override
        public void transfer(Integer srcId, Integer dstId, Float money) {
            try {
                //1.    
                JdbcUtils.beginTransaction();
                //2.  
                Account src = accountDao.findById(srcId);
                Account dst = accountDao.findById(dstId);
    
                if(src == null) {
                    throw new RuntimeException("       ");
                }
    
                if(dst == null) {
                    throw new RuntimeException("       ");
                }
    
                if(src.getMoney() < money) {
                    throw new RuntimeException("        ");
                }
    
                src.setMoney(src.getMoney() - money);
                dst.setMoney(dst.getMoney() + money);
    
                accountDao.update(src);
    
                //int x = 1/0;//    
    
                accountDao.update(dst);
                //3.    
                JdbcUtils.commitTransaction();
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    //    
                    JdbcUtils.rollbackTransaction();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }
    

    2.4.JdbcConfig構成クラスの変更
    package org.codeaction.config;
    
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    import org.apache.commons.dbutils.QueryRunner;
    import org.codeaction.util.JdbcUtils;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import javax.sql.DataSource;
    
    @Configuration
    public class JdbcConfig {
        @Value("${jdbc.driverClass}")
        private String driverClass;
        @Value("${jdbc.jdbcUrl}")
        private String jdbcUrl;
        @Value("${jdbc.user}")
        private String user;
        @Value("${jdbc.password}")
        private String password;
    
    
        @Bean("queryRunner")
        public QueryRunner queryRunner() {
            return new QueryRunner();
        }
    
        @Bean("dataSource")
        public DataSource dataSource() {
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            try {
                dataSource.setDriverClass(driverClass);
                dataSource.setJdbcUrl(jdbcUrl);
                dataSource.setUser(user);
                dataSource.setPassword(password);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return dataSource;
        }
    }
    

    2.5、試験方法の実行
    @Test
    public void testTrans() {
    	accountService.transfer(1, 2, 10F);
    }
    

    このテスト方法を実行すると、転送に成功します.
    2.4のint i = 1/0;の注釈を取り消し、試験方法を実行し、異常が発生し、正常にロールバックできる.
    2.6、現在コードに存在する問題
    現在のコードはトランザクションのサポートを実現していますが、次のような問題があります.
  • コードは煩雑で、サービス層の実装クラスは前のバージョンに比べて煩雑で、業務を担当する以外に事務の操作を担当しなければならない.
  • JdbcUtilsクラスのトランザクション・オペレーションに関連するメソッドに変更がある場合、サービス・レイヤ・コードに対応するコール・ロケーションは変更されます.プロジェクトが膨大であれば、開発者にとっては悪夢です.

  • 次の節ではSpring AOPを深く学び、上記の問題を解決します.