• 10、JavaEEDBUtils工具类


    DBUtils框架

    commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工

    作量,同时也不会影响程序的性能。因此dbutils成为很多不喜欢hibernate的公司的首选。

    commons-dbutilsAPI介绍:

    org.apache.commons.dbutils.QueryRunner
    org.apache.commons.dbutils.ResultSetHandler
    工具类
    org.apache.commons.dbutils.DbUtils

    在src下创建c3p0-config.xml配置类来配置连接信息,然后创建JdbcUtils类:

    public class JdbcUtils {
        // 获得c3p0连接池对象
        private static ComboPooledDataSource ds = new ComboPooledDataSource();
        // 获得数据库连接对象
        public static Connection getConnection() throws SQLException {
            return ds.getConnection();
        }
        // 获得c3p0连接池对象
        public static DataSource getDataSource() {
            return ds;
        }
    }
    

    QueryRunner类

     该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。

    QueryRunner类提供了两个构造方法:

    默认的构造方法
    需要一个 javax.sql.DataSource 来作参数的构造方法。

    QueryRunner方法

    名称 描述
    public Object query(Connection conn, String sql, Object[] params, ResultSetHandler rsh) throws SQLException 执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。
    public Object query(String sql, Object[] params, ResultSetHandler rsh) throws SQLException 几乎与第一种方法一样;唯一的不同在于它不将数据库连接提供给方法,并且它是从提供给构造方法的数据源(DataSource) 或使用的setDataSource 方法中重新获得 Connection。
    public Object query(Connection conn, String sql, ResultSetHandler rsh) throws SQLException 执行一个不需要置换参数的查询操作。
    public int update(Connection conn, String sql, Object[] params) throws SQLException 用来执行一个更新(插入、更新或删除)操作。
    public int update(Connection conn, String sql) throws SQLException 用来执行一个不需要置换参数的更新操作。

    使用QueryRunner类实现CRUD

    其中使用到的测试表如下:

    create database dbutils character set utf8 collate utf8_general_ci;
    use dbutils;
    create table users(
        id int primary key auto_increment, 
        name varchar(40),
        password varchar(40), 
        email varchar(60), 
        birthday date 
    );
    

    其中使用到的代码如下:

    public class QueryRunnerCRUDTest {    
        @Test
        public void insert() throws SQLException {
            //将数据源传递给QueryRunner,QueryRunner内部通过数据源获取数据库连接
            QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
            String sql = "insert into users(name,password,email,birthday) values(?,?,?,?)";
            Object params[] = {"legend","123", "gacl@sina.com", new Date()};
            qr.update(sql, params);
        }
        
        @Test
        public void delete() throws SQLException {
            QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
            String sql = "delete from users where id=?";
            qr.update(sql, 1);
        }
        @Test
        public void update() throws SQLException {
            QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
            String sql = "update users set name=? where id=?";
            Object params[] = { "ddd", 5};
            qr.update(sql, params);
        }
        @Test
        public void find() throws SQLException {
            QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
            String sql = "select * from users where id=?";
            Object params[] = {2};
            User user = (User) qr.query(sql, params, new BeanHandler(User.class));
            System.out.println(user.getBirthday());
        }
        @Test
        public void getAll() throws SQLException {
            QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
            String sql = "select * from users";
            List list = (List) qr.query(sql, new BeanListHandler(User.class));
            System.out.println(list.size());
        }
        /**
        * @Method: testBatch
        * @Description:批处理
        * @throws SQLException
        */ 
        @Test
        public void testBatch() throws SQLException {
            QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
            String sql = "insert into users(name,password,email,birthday) values(?,?,?,?)";
            Object params[][] = new Object[10][];
            for (int i = 0; i < 10; i++) {
                params[i] = new Object[] { "aa" + i, "123", "aa@sina.com",
                        new Date() };
            }
            qr.batch(sql, params);
        }
    }
    

    其中使用到的jdbcUtils类如下:

    public class JdbcUtils {    
        private static DataSource ds = null;
        //在静态代码块中创建数据库连接池
        static{
            try{             
                //通过读取C3P0的xml配置文件创建数据源,C3P0的xml配置文件c3p0-config.xml必须放在src目录下
               ds = new ComboPooledDataSource("MySQL");//使用C3P0的命名配置来创建数据源             
            }catch (Exception e) {
                throw new ExceptionInInitializerError(e);
            }
        }
        public static Connection getConnection() throws SQLException{
            //从数据源中获取数据库连接
            return ds.getConnection();
        }
        public static void release(Connection conn,Statement st,ResultSet rs){
            if(rs!=null){
                try{
                    //关闭存储查询结果的ResultSet对象
                    rs.close();
                }catch (Exception e) {
                    e.printStackTrace();
                }
                rs = null;
            }
            if(st!=null){
                try{
                    //关闭负责执行SQL命令的Statement对象
                    st.close();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
             
            if(conn!=null){
                try{
                    //将Connection连接对象还给数据库连接池
                    conn.close();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
     
        public static DataSource getDataSource() {
            // TODO Auto-generated method stub
            return ds;
        }
    }
    

    ResultSetHandler接口

    该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式。ResultSetHandler接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)

    ResultSetHandler接口的实现类

    类名 描述
    ArrayHandler 把结果集中的第一行数据转成对象数组。
    ArrayListHandler 把结果集中的每一行数据都转成一个数组,再存放到List中。
    BeanHandler 将结果集中的第一行数据封装到一个对应的JavaBean实例中。
    BeanListHandler 将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
    ColumnListHandler 将结果集中某一列的数据存放到List中。
    KeyedHandler(name) 将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
    MapHandler 将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
    MapListHandler 将结果集中的每一行数据封装到一个Map里,然后再存放到List。

    范例:测试dbutils各种类型的处理器

    public class ResultSetHandlerTest {
        @Test
        public void testArrayHandler() throws SQLException{
            QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
            String sql = "select * from users";
            Object result[] = (Object[]) qr.query(sql, new ArrayHandler());
            System.out.println(Arrays.asList(result));  //list  toString()
        }
        
        @Test
        public void testArrayListHandler() throws SQLException{
            
            QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
            String sql = "select * from users";
            List<Object[]> list = (List) qr.query(sql, new ArrayListHandler());
            for(Object[] o : list){
                System.out.println(Arrays.asList(o));
            }
        }
        
        @Test
        public void testColumnListHandler() throws SQLException{
            QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
            String sql = "select * from users";
            List list = (List) qr.query(sql, new ColumnListHandler("id"));
            System.out.println(list);
        }
        
        @Test
        public void testKeyedHandler() throws Exception{
            QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
            String sql = "select * from users";
            
            Map<Integer,Map> map = (Map) qr.query(sql, new KeyedHandler("id"));
            for(Map.Entry<Integer, Map> me : map.entrySet()){
                int  id = me.getKey();
                Map<String,Object> innermap = me.getValue();
                for(Map.Entry<String, Object> innerme : innermap.entrySet()){
                    String columnName = innerme.getKey();
                    Object value = innerme.getValue();
                    System.out.println(columnName + "=" + value);
                }
                System.out.println("----------------");
            }
        }
        
        @Test
        public void testMapHandler() throws SQLException{
            
            QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
            String sql = "select * from users";
            
            Map<String,Object> map = (Map) qr.query(sql, new MapHandler());
            for(Map.Entry<String, Object> me : map.entrySet())
            {
                System.out.println(me.getKey() + "=" + me.getValue());
            }
        }
        
        
        @Test
        public void testMapListHandler() throws SQLException{
            QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
            String sql = "select * from users";
            List<Map> list = (List) qr.query(sql, new MapListHandler());
            for(Map<String,Object> map :list){
                for(Map.Entry<String, Object> me : map.entrySet())
                {
                    System.out.println(me.getKey() + "=" + me.getValue());
                }
            }
        }
        
        @Test
        public void testScalarHandler() throws SQLException{
            QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
            String sql = "select count(*) from users";  //[13]  list[13]
            int count = ((Long)qr.query(sql, new ScalarHandler(1))).intValue();
            System.out.println(count);
        }
    }
    
    (3)DbUtils类介绍

    DbUtils :提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法如下:

    名称 描述
    public static void close(…) throws java.sql.SQLException DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet。
    public static void closeQuietly(…) 这一类方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLEeception。
    public static void commitAndCloseQuietly(Connection conn) 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。
    public static boolean loadDriver(java.lang.String driverClassName) 这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。

    JDBC中事务处理

    在开发中,对数据库的多个表或者对一个表中的多条数据执行更新操作时要保证对多个更新操作要么同时成功,要么都不成功,这就涉及到对多个更新操作的事务管理问题了。

    比如银行业务中的转账问题,A用户向B用户转账100元,假设A用户和B用户的钱都存储在Account表,那么A用户向B用户转账时就涉及到同时更新Account表中的A用户的钱和

    B用户的钱,用SQL来表示就是:

    update account set money=money-100 where name='A'
    update account set money=money+100 where name='B'
    

    在数据访问层(Dao)中处理事务

    对于这样的同时更新一个表中的多条数据的操作,那么必须保证要么同时成功,要么都不成功,所以需要保证这两个update操作在同一个事务中进行。在开发中,我们可能会

    在AccountDao写一个转账处理方法,如下:

    /**
        * @Method: transfer
        * @Description:这个方法是用来处理两个用户之间的转账业务
        * 在开发中,DAO层的职责应该只涉及到CRUD,
        * 而这个transfer方法是处理两个用户之间的转账业务的,已经涉及到具体的业务操作,应该在业务层中做,不应该出现在DAO层的
        * 所以在开发中DAO层出现这样的业务处理方法是完全错误的
        * @param sourceName
        * @param targetName
        * @param money
        * @throws SQLException
        */ 
        public void transfer(String sourceName,String targetName,float money) throws SQLException{
            Connection conn = null;
            try{
                conn = JdbcUtils.getConnection();
                //开启事务
                conn.setAutoCommit(false);
                /**
                 * 在创建QueryRunner对象时,不传递数据源给它,是为了保证这两条SQL在同一个事务中进行,
                 * 我们手动获取数据库连接,然后让这两条SQL使用同一个数据库连接执行
                 */
                QueryRunner runner = new QueryRunner();
                String sql1 = "update account set money=money-100 where name=?";
                String sql2 = "update account set money=money+100 where name=?";
                Object[] paramArr1 = {sourceName};
                Object[] paramArr2 = {targetName};
                runner.update(conn,sql1,paramArr1);
                //模拟程序出现异常让事务回滚
                int x = 1/0;
                runner.update(conn,sql2,paramArr2);
                //sql正常执行之后就提交事务
                conn.commit();
            }catch (Exception e) {
                e.printStackTrace();
                if(conn!=null){
                    //出现异常之后就回滚事务
                    conn.rollback();
                }
            }finally{
                //关闭数据库连接
                conn.close();
            }
        }
    

    然后我们在AccountService中再写一个同名方法,在方法内部调用AccountDao的transfer方法处理转账业务,如下:

    public void transfer(String sourceName,String targetName,float money) throws SQLException{
            AccountDao dao = new AccountDao();
            dao.transfer(sourceName, targetName, money);
    }
    

    上面AccountDao的这个transfer方法可以处理转账业务,并且保证了在同一个事务中进行,但是AccountDao的这个transfer方法是处理两个用户之间的转账业务的,已经涉及到

    具体的业务操作,应该在业务层中做,不应该出现在DAO层的,在开发中,DAO层的职责应该只涉及到基本的CRUD,不涉及具体的业务操作,所以在开发中DAO层出现这样的业

    务处理方法是一种不好的设计。

    在业务层(BusinessService)处理事务

    由于上述AccountDao存在具体的业务处理方法,导致AccountDao的职责不够单一,下面我们对AccountDao进行改造,让AccountDao的职责只是做CRUD操作,将事务的处

    理挪到业务层(BusinessService),改造后的AccountDao如下:

    public class AccountDao {
        //接收service层传递过来的Connection对象
        private Connection conn = null;
        
        public AccountDao(Connection conn){
            this.conn = conn;
        }
        
        public AccountDao(){
            
        }
        
        /**
        * @Method: update
        * @Description:更新
        * @param account
        * @throws SQLException
        */ 
        public void update(Account account) throws SQLException{
            
            QueryRunner qr = new QueryRunner();
            String sql = "update account set name=?,money=? where id=?";
            Object params[] = {account.getName(),account.getMoney(),account.getId()};
            //使用service层传递过来的Connection对象操作数据库
            qr.update(conn,sql, params);
            
        }
        
        /**
        * @Method: find
        * @Description:查找
        * @param id
        * @return
        * @throws SQLException
        */ 
        public Account find(int id) throws SQLException{
            QueryRunner qr = new QueryRunner();
            String sql = "select * from account where id=?";
            //使用service层传递过来的Connection对象操作数据库
            return (Account) qr.query(conn,sql, id, new BeanHandler(Account.class));
        }
    }
    

    其中使用到的建表语句:

    account测试表
    create table account(
        id int primary key auto_increment,
        name varchar(40),
        money float
    )character set utf8 collate utf8_general_ci;
    insert into account(name,money) values('A',1000);
    insert into account(name,money) values('B',1000);
    insert into account(name,money) values('C',1000);
    

    接着对AccountService(业务层)中的transfer方法的改造,在业务层(BusinessService)中处理事务

    public class AccountService {
        
        /**
        * @Method: transfer
        * @Description:这个方法是用来处理两个用户之间的转账业务
        * @param sourceid
        * @param tartgetid
        * @param money
        * @throws SQLException
        */ 
        public void transfer(int sourceid,int tartgetid,float money) throws SQLException{
            Connection conn = null;
            try{
                //获取数据库连接
                conn = JdbcUtils.getConnection();
                //开启事务
                conn.setAutoCommit(false);
                //将获取到的Connection传递给AccountDao,保证dao层使用的是同一个Connection对象操作数据库
                AccountDao dao = new AccountDao(conn);
                Account source = dao.find(sourceid);
                Account target = dao.find(tartgetid);
                
                source.setMoney(source.getMoney()-money);
                target.setMoney(target.getMoney()+money);
                
                dao.update(source);
                //模拟程序出现异常让事务回滚
                int x = 1/0;
                dao.update(target);
                //提交事务
                conn.commit();
            }catch (Exception e) {
                e.printStackTrace();
                //出现异常之后就回滚事务
                conn.rollback();
            }finally{
                conn.close();
            }
        }
    }
    

    程序经过这样改造之后就比刚才好多了,AccountDao只负责CRUD,里面没有具体的业务处理方法了,职责就单一了,而AccountService则负责具体的业务逻辑和事务的处理,

    需要操作数据库时,就调用AccountDao层提供的CRUD方法操作数据库。

    使用ThreadLocal进行更加优雅的事务处理

    上面的在businessService层这种处理事务的方式依然不够优雅,为了能够让事务处理更加优雅,我们使用ThreadLocal类进行改造,ThreadLocal一个容器,向这个容器存储的

    对象,在当前线程范围内都可以取得出来,向ThreadLocal里面存东西就是向它里面的Map存东西的,然后ThreadLocal把这个Map挂到当前的线程底下,这样Map就只属于这个线程了

    ThreadLocal类的使用范例如下:

    public class ThreadLocalTest {
        public static void main(String[] args) {
            //得到程序运行时的当前线程
            Thread currentThread = Thread.currentThread();
            System.out.println(currentThread);
            //ThreadLocal一个容器,向这个容器存储的对象,在当前线程范围内都可以取得出来
            ThreadLocal<String> t = new ThreadLocal<String>();
            //把某个对象绑定到当前线程上 对象以键值对的形式存储到一个Map集合中,对象的的key是当前的线程,如: map(currentThread,"aaa")
            t.set("aaa");
            //获取绑定到当前线程中的对象
            String value = t.get();
            //输出value的值是aaa
            System.out.println(value);
        }
    }
    

    使用使用ThreadLocal类进行改造数据库连接工具类JdbcUtils,改造后的代码如下:

    public class JdbcUtils2 {
        
        private static ComboPooledDataSource ds = null;
        //使用ThreadLocal存储当前线程中的Connection对象
        private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
        
        //在静态代码块中创建数据库连接池
        static{
            try{
                //通过代码创建C3P0数据库连接池
                /*ds = new ComboPooledDataSource();
                ds.setDriverClass("com.mysql.jdbc.Driver");
                ds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbcstudy");
                ds.setUser("root");
                ds.setPassword("XDP");
                ds.setInitialPoolSize(10);
                ds.setMinPoolSize(5);
                ds.setMaxPoolSize(20);*/
                
                //通过读取C3P0的xml配置文件创建数据源,C3P0的xml配置文件c3p0-config.xml必须放在src目录下
                //ds = new ComboPooledDataSource();//使用C3P0的默认配置来创建数据源
                ds = new ComboPooledDataSource("MySQL");//使用C3P0的命名配置来创建数据源
                
            }catch (Exception e) {
                throw new ExceptionInInitializerError(e);
            }
        }
        
        /**
        * @Method: getConnection
        * @Description: 从数据源中获取数据库连接
        * @return Connection
        * @throws SQLException
        */ 
        public static Connection getConnection() throws SQLException{
            //从当前线程中获取Connection
            Connection conn = threadLocal.get();
            if(conn==null){
                //从数据源中获取数据库连接
                conn = getDataSource().getConnection();
                //将conn绑定到当前线程
                threadLocal.set(conn);
            }
            return conn;
        }
        
        /**
        * @Method: startTransaction
        * @Description: 开启事务
        */ 
        public static void startTransaction(){
            try{
                Connection conn =  threadLocal.get();
                if(conn==null){
                    conn = getConnection();
                     //把 conn绑定到当前线程上
                    threadLocal.set(conn);
                }
                //开启事务
                conn.setAutoCommit(false);
            }catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        
        /**
        * @Method: rollback
        * @Description:回滚事务
        */ 
        public static void rollback(){
            try{
                //从当前线程中获取Connection
                Connection conn = threadLocal.get();
                if(conn!=null){
                    //回滚事务
                    conn.rollback();
                }
            }catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        
        /**
        * @Method: commit
        * @Description:提交事务
        */ 
        public static void commit(){
            try{
                //从当前线程中获取Connection
                Connection conn = threadLocal.get();
                if(conn!=null){
                    //提交事务
                    conn.commit();
                }
            }catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        
        /**
        * @Method: close
        * @Description:关闭数据库连接(注意,并不是真的关闭,而是把连接还给数据库连接池)
        */ 
        public static void close(){
            try{
                //从当前线程中获取Connection
                Connection conn = threadLocal.get();
                if(conn!=null){
                    conn.close();
                     //解除当前线程上绑定conn
                    threadLocal.remove();
                }
            }catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        
        /**
        * @Method: getDataSource
        * @Description: 获取数据源
        * @return DataSource
        */ 
        public static DataSource getDataSource(){
            //从数据源中获取数据库连接
            return ds;
        }
    }
    

    对AccountDao进行改造,数据库连接对象不再需要service层传递过来,而是直接从JdbcUtils2提供的getConnection方法去获取,改造后的AccountDao如下:

    public class AccountDao2 {
        public void update(Account account) throws SQLException{
            
            QueryRunner qr = new QueryRunner();
            String sql = "update account set name=?,money=? where id=?";
            Object params[] = {account.getName(),account.getMoney(),account.getId()};
            //JdbcUtils2.getConnection()获取当前线程中的Connection对象
            qr.update(JdbcUtils2.getConnection(),sql, params);
            
        }
        
        public Account find(int id) throws SQLException{
            QueryRunner qr = new QueryRunner();
            String sql = "select * from account where id=?";
            //JdbcUtils2.getConnection()获取当前线程中的Connection对象
            return (Account) qr.query(JdbcUtils2.getConnection(),sql, id, new BeanHandler(Account.class));
        }
    }
    

    对AccountService进行改造,service层不再需要传递数据库连接Connection给Dao层,改造后的AccountService如下:

    public void transfer(int sourceid,int tartgetid,float money) throws SQLException{
            try{
                //开启事务,在业务层处理事务,保证dao层的多个操作在同一个事务中进行
                JdbcUtils2.startTransaction();
                AccountDao2 dao = new AccountDao2();
                
                Account source = dao.find(sourceid);
                Account target = dao.find(tartgetid);
                source.setMoney(source.getMoney()-money);
                target.setMoney(target.getMoney()+money);
                
                dao.update(source);
                //模拟程序出现异常让事务回滚
                int x = 1/0;
                dao.update(target);
                
                //SQL正常执行之后提交事务
                JdbcUtils2.commit();
            }catch (Exception e) {
                e.printStackTrace();
                //出现异常之后就回滚事务
                JdbcUtils2.rollback();
            }finally{
                //关闭数据库连接
                JdbcUtils2.close();
            }
        }
    }
    

    这样在service层处理事务比较优雅。ThreadLocal类在开发中使用得是比较多的,程序运行中产生的数据要想在一个线程范围内共享,只需要把数据使用ThreadLocal进行存储即可。

    ThreadLocal + Filter 处理事务

    上面介绍了JDBC开发中事务处理的3种方式,下面再介绍的一种使用ThreadLocal + Filter进行统一的事务处理,这种方式主要是使用过滤器进行统一的事务处理,如下图所示:

    1、编写一个事务过滤器TransactionFilter

     public class TransactionFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }
        @Override
        public void doFilter(ServletRequest request, ServletResponse response,
                FilterChain chain) throws IOException, ServletException {
            Connection connection = null;
            try {
                //1、获取数据库连接对象Connection
                connection = JdbcUtils.getConnection();
                //2、开启事务
                connection.setAutoCommit(false);
                //3、利用ThreadLocal把获取数据库连接对象Connection和当前线程绑定
                ConnectionContext.getInstance().bind(connection);
                //4、把请求转发给目标Servlet
                chain.doFilter(request, response);
                //5、提交事务
                connection.commit();
            } catch (Exception e) {
                e.printStackTrace();
                //6、回滚事务
                try {
                    connection.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
                HttpServletRequest req = (HttpServletRequest) request;
                HttpServletResponse res = (HttpServletResponse) response;
                //req.setAttribute("errMsg", e.getMessage());
                //req.getRequestDispatcher("/error.jsp").forward(req, res);
                //出现异常之后跳转到错误页面
                res.sendRedirect(req.getContextPath()+"/error.jsp");
            }finally{
                //7、解除绑定
                ConnectionContext.getInstance().remove();
                //8、关闭数据库连接
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        @Override
        public void destroy() {
        }
    }
    

    我们在TransactionFilter中把获取到的数据库连接使用ThreadLocal绑定到当前线程之后,在DAO层还需要从ThreadLocal中取出数据库连接来操作数据库,因此

    需要编写一个ConnectionContext类来存储ThreadLocal,ConnectionContext类的代码如下:

    public class ConnectionContext {
        /**
         * 构造方法私有化,将ConnectionContext设计成单例
         */
        private ConnectionContext(){
            
        }
        //创建ConnectionContext实例对象
        private static ConnectionContext connectionContext = new ConnectionContext();
        
        /**
        * @Method: getInstance
        * @Description:获取ConnectionContext实例对象
        * @return
        */ 
        public static ConnectionContext getInstance(){
            return connectionContext;
        }
        
        /**
        * @Field: connectionThreadLocal
        *         使用ThreadLocal存储数据库连接对象
        */ 
        private ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<Connection>();
        
        /**
        * @Method: bind
        * @Description:利用ThreadLocal把获取数据库连接对象Connection和当前线程绑定
        * @param connection
        */ 
        public void bind(Connection connection){
            connectionThreadLocal.set(connection);
        }
        
        /**
        * @Method: getConnection
        * @Description:从当前线程中取出Connection对象
        * @return
        */ 
        public Connection getConnection(){
            return connectionThreadLocal.get();
        }
        
        /**
        * @Method: remove
        * @Description: 解除当前线程上绑定Connection
        */ 
        public void remove(){
            connectionThreadLocal.remove();
        }
    }
    

    在DAO层想获取数据库连接时,就可以使用ConnectionContext.getInstance().getConnection()来获取,如下所示:

    /*
    create table account(
        id int primary key auto_increment,
        name varchar(40),
        money float
    )character set utf8 collate utf8_general_ci;
    insert into account(name,money) values('A',1000);
    insert into account(name,money) values('B',1000);
    insert into account(name,money) values('C',1000);
    */
    public class AccountDao3 {
        public void update(Account account) throws SQLException{
            
            QueryRunner qr = new QueryRunner();
            String sql = "update account set name=?,money=? where id=?";
            Object params[] = {account.getName(),account.getMoney(),account.getId()};
            //ConnectionContext.getInstance().getConnection()获取当前线程中的Connection对象
            qr.update(ConnectionContext.getInstance().getConnection(),sql, params);
            
        }
        
        public Account find(int id) throws SQLException{
            QueryRunner qr = new QueryRunner();
            String sql = "select * from account where id=?";
            //ConnectionContext.getInstance().getConnection()获取当前线程中的Connection对象
            return (Account) qr.query(ConnectionContext.getInstance().getConnection(),sql, id, new BeanHandler(Account.class));
        }
    }
    

    businessService层也不用处理事务和数据库连接问题了,这些统一在TransactionFilter中统一管理了,businessService层只需要专注业务逻辑的处理即可,如下所示:

    public class AccountService3 {
        
        /**
        * @Method: transfer
        * @Description:在业务层处理两个账户之间的转账问题
        * @param sourceid
        * @param tartgetid
        * @param money
        * @throws SQLException
        */ 
        public void transfer(int sourceid, int tartgetid, float money)
                throws SQLException {
            AccountDao3 dao = new AccountDao3();
            Account source = dao.find(sourceid);
            Account target = dao.find(tartgetid);
            source.setMoney(source.getMoney() - money);
            target.setMoney(target.getMoney() + money);
            dao.update(source);
            // 模拟程序出现异常让事务回滚
            int x = 1 / 0;
            dao.update(target);
        }
    }
    

    Web层的Servlet调用businessService层的业务方法处理用户请求,需要注意的是:调用businessService层的方法出异常之后,继续将异常抛出,这样在Transaction

    Filter就能捕获到抛出的异常,继而执行事务回滚操作,如下所示:

    public class AccountServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            AccountService3 service = new AccountService3();
            try {
                service.transfer(1, 2, 100);
            } catch (SQLException e) {
                e.printStackTrace();
                //注意:调用service层的方法出异常之后,继续将异常抛出,这样在TransactionFilter就能捕获到抛出的异常,继而执行事务回滚操作
                throw new RuntimeException(e);
            }
        }
        public void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            doGet(request, response);
        }
    }
    
  • 相关阅读:
    国外程序猿整理的机器学习资源大全
    一个改动配置文件的linux shell script
    python高精度浮点型计算的诡异错误
    错误:'dict' object is not callable
    AssertionError while merging cells with xlwt (Python)
    Python => ValueError: unsupported format character 'Y' (0x59)
    [转]Python的3种格式化字符串方法
    python requirements使用方法
    conda虚拟环境实践
    迭代器中next()的用法
  • 原文地址:https://www.cnblogs.com/pengjingya/p/14904720.html
Copyright © 2020-2023  润新知