• 模拟实现 DBUtils 工具 , 技术原理浅析


    申明:本文采用自己 C3P0 连接池工具进行测试

    自定义的 JDBCUtils 可以获取 Connection:
    package com.test.utils;
    
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    import javax.sql.DataSource;
    
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    
    public class JDBCUtils {
        private static ComboPooledDataSource dataSource  = new ComboPooledDataSource();
        
        public static Connection getConnection() {
            try {
                return dataSource.getConnection();
            } catch (SQLException e) {
                throw new RuntimeException("数据连接获取失败!");
            }
        }
        
        public static DataSource getDataSource() {
            return dataSource;
        }
        
        /**
         * 释放资源
         * @param conn
         * @param st
         * @param rs
         */
        public static void colseResource(Connection conn,Statement st,ResultSet rs) {
            closeResultSet(rs);
            closeStatement(st);
            closeConnection(conn);
        }
        
        /**
         * 释放连接 Connection
         * @param conn
         */
        public static void closeConnection(Connection conn) {
            if(conn !=null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //等待垃圾回收
            conn = null;
        }
        
        /**
         * 释放语句执行者 Statement
         * @param st
         */
        public static void closeStatement(Statement st) {
            if(st !=null) {
                try {
                    st.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //等待垃圾回收
            st = null;
        }
        
        /**
         * 释放结果集 ResultSet
         * @param rs
         */
        public static void closeResultSet(ResultSet rs) {
            if(rs !=null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //等待垃圾回收
            rs = null;
        }
    }

     一、DBUtils 简介

      1.1 什么是 DBUtils:

        Dbutils是由Apache公司提供。

        主要是封装了JDBC的代码,简化dao层的操作。帮助java程序员,开发Dao层代码的简单框架。

        框架的作用:帮助程序员,提高程序的开发效率。
      1.2 为什么需要 Dbutils (为什么不用 JDBC):
        在使用 DBUtils 之前,我们Dao层使用的技术是 JDBC,那么分析一下JDBC的弊端:
                - 数据库链接对象、sql语句操作对象,封装结果集对象,这三大对象会重复定义
                - 封装数据的代码重复,而且操作复杂,代码量大
                - 释放资源的代码重复
      二、基础知识准备
      2.1 元信息获取
        数据库元信息包括:数据库产品本身和表等的定义信息。
        元信息包括:
          - 数据库的元信息:
          - 参数元信息:执行的 SQL 语句中的占位符元信息(个数,参数类型等)。
          - 结果集元信息:字段的类型
       2.2 数据库的元信息获取
        创建 Connection 之后,通过 Connection 对象的 getMetaData() 方法进行数据库元信息的获取
        效果图:
        

        从上图中可以看出 Connection 的 getMetaData() 方法可以获取用户使用的是什么数据库的 Connecton

        当框架知道了数据库的连接是哪个数据库来的,则可以用相应的 SQL 语句进行数据库操作。

       2.3 参数的元信息获取

        参数的元信息是指:SQL语句中的参数个数、参数所对应的字段类型等。

        搞清楚元信息怎么获取之前,要思考一下为什么要获取参数的元信息,看下图为一个正常的 JDBC 数据查询操作:

        

        站在程序员的角度来说,我们当然能看出 SQL 语句中需要多少个参数,因此向 Statement 中 set 参数的时候,传入相应对的占位符位置号:1,2,……

        那么如果站在框架的角度,当用户扔一个 sql 语句给框架,让框架给我执行 SQL 语句,

        比如 “ select * from student where name like ? and age in (? ,?)” 这样的 sql 语句,框架怎么知道这个 sql 里面有几个占位符呢?

        这里就要解释一下:Statement 接口的牛逼之处,只要使用 “?” 作为占位符,通过 getParameterMetaData() 方法就能获取到

        sql 语句中的占位符 “?”的个数。来一个极端的例子:

        

        2.4 结果集元信息的获取

        获取结果集中的列表的字段个数、类型等信息

        

        总结:  

          元信息获取方法:
            - 数据库元信息:

              DatabaseMetaData metaData = conn.getMetaData();  // 得到数据库的元信息集合

            - 参数元信息:
              ParameterMetaData pmd = st.getParameterMetaData();   // 得到参数的元信息集合
            - 结果集元信息:

              ResultSetMetaData metaData = rs.getMetaData();   // 得到结果集的元信息集合

     三、DBUtils 工具框架编写

      3.1 框架不依赖数据源:

        自定义一个 BDAssist 类来模拟 DBUtils,创建一个私有的 DataSource 属性,创建构造函数,当调用者调用的时候,把数据源传进来,

        框架拿到数据源的具体实例,框架中使用接口指向这个具体实例,对数据库的一系列操作都是用接口的方法操作,从而摆脱了具体实例的依赖。 

      private DataSource dataSource;

      public DBAssist(DataSource dataSource){
        this.dataSource = dataSource;
      }

        数据的增加、删除、修改、查询分为: DML (增加、删除、修改)和 查询操作

        思考用户向框架里仍一条带有参数的 sql 语句,调用 DML 方法的时候,框架就知道怎么操作,并且把用户传递的参数和 sql 语句一一对应起来

        那么可以先定义一个 update(String sql,Object...params) 方法,负责把 sql 语句和参数值接收进来,在执行 sql 语句之前,判断参数是否和 sql 语句中

        的 “?” 占位符数量一致,如果一致继续对占位符进行设置值,最后执行 sql 语句即可:

      /**
         * 能够执行 DML 语句:insert update delete
         */
        public void update(String sql,Object...params) {
            Connection conn = null;
            PreparedStatement st = null;
            ResultSet rs = null;
            
            try {
                // 与数据源无关
                conn = dataSource.getConnection();
                
                st = conn.prepareStatement(sql);
                int paramsCount = st.getParameterMetaData().getParameterCount();
                
                // 设置参数
                if(paramsCount>0) {
                    // 判断参数是否有效且和sql语句中的占位符个数相等
                    if(params==null || params.length !=paramsCount) {
                        throw new RuntimeException("传递的参数数量不匹配!");
                    }
                    
                    // 设置 sql 语句中的参数占位符
                    for (int i = 0; i < paramsCount; i++) {
                        st.setObject(i+1, params[i]);
                    }
                }
                st.executeUpdate();            
                
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }finally {
                colseResource(conn, st, rs);
            }
        }

      3.2 查询操作

        对于查询比较麻烦的是框架执行完 sql 语句之后,需要将查询的结果封装到一个 javabean 里返回给用户。

        那么可以定义一个框架的 query(String sql,Object...params) 方法,那不同的 sql 查询语句返回不同的数据对象,框架自己怎么知道需要返回的是什么 javabean呢?

        如果是查询一条记录的 sql 语句,如果查到了,框架给用户返回的是一个 javabean 对象。

        如果是查询多条记录的 sql 语句,如果查到了,框架给用户返回的是一个 javabean 对象的集合,那么集合需要用什么容器来装呢?

        上面这些问题的答案估计只有用户自己知道,好比,框架就是一个生产固型塑料的机器,工人在机器入口放入原材料就可以在机器出口等着成型的塑料。

        那工人要求机器生产球型的塑料,机器就能作出球型的塑料,工人要求机器生产矩型的塑料,机器就能作出矩型的塑料,而且两种情况下,原料都是一样的。

        说明机器一定是得到了某一个指令,要求机器做出什么形状。所以能看出谁用谁知道框架应该要输出的结果集是什么。

      用户1 用框架说我给你指明给我返回 ArrayList 结果集,

      用户2 用框架说我给你指明给我返回 HashMap 结果集,

      用户3 用框架说我给你指明给我返回 LinkedList 结果集,

      ……

      框架要是只要是一个结果集就都做一个可以返回这个结果集的功能,那估计框架设计者就不干了,做框架就是做标准,总是牵着用户的鼻子走,遇到“奇葩”用户

      那不得增加功能到死,所以,框架得面向接口设计,都给我实现一个接口,框架里面我都是使用接口中的方法操作程序的,所以谁来“各种花式”需求,都给我实现接口,那这个框架架爱怎么用就怎么用,自己用的姿势舒服就行。

    具体代码实现:

    DBAssist 代码实现:

    package com.test.DBAssist;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    import javax.sql.DataSource;
    
    import org.junit.Test;
    
    public class DBAssist {
        private DataSource dataSource;
    
        public DBAssist(DataSource dataSource){
            this.dataSource = dataSource;
        }
        
        /**
         * 能够执行 DML 语句:insert update delete
         */
        public void update(String sql,Object...params) {
            Connection conn = null;
            PreparedStatement st = null;
            ResultSet rs = null;
            
            try {
                // 与数据源无关
                conn = dataSource.getConnection();
                
                st = conn.prepareStatement(sql);
                int paramsCount = st.getParameterMetaData().getParameterCount();
                
                // 设置参数
                if(paramsCount>0) {
                    // 判断参数是否有效且和sql语句中的占位符个数相等
                    if(params==null || params.length !=paramsCount) {
                        throw new RuntimeException("传递的参数数量不匹配!");
                    }
                    
                    // 设置 sql 语句中的参数占位符
                    for (int i = 0; i < paramsCount; i++) {
                        st.setObject(i+1, params[i]);
                    }
                }
                st.executeUpdate();            
                
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }finally {
                colseResource(conn, st, rs);
            }
        }
        
        /**
         * 能够执行 DML 语句:insert update delete
         */
        public Object query(String sql,ResultHandler handler,Object...params) {
            Connection conn = null;
            PreparedStatement st = null;
            ResultSet rs = null;
            
            try {
                // 与数据源无关
                conn = dataSource.getConnection();
                
                st = conn.prepareStatement(sql);
                int paramsCount = st.getParameterMetaData().getParameterCount();
                
                // 设置参数
                if(paramsCount>0) {
                    // 判断参数是否有效且和sql语句中的占位符个数相等
                    if(params==null || params.length !=paramsCount) {
                        throw new RuntimeException("传递的参数数量不匹配!");
                    }
                    
                    // 设置 sql 语句中的参数占位符
                    for (int i = 0; i < paramsCount; i++) {
                        st.setObject(i+1, params[i]);
                    }
                }
                rs = st.executeQuery();
                return handler.handler(rs);
                
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }finally {
                colseResource(conn, st, rs);
            }
        }
        
        
        /**
         * 释放资源
         * @param conn
         * @param st
         * @param rs
         */
        private void colseResource(Connection conn,Statement st,ResultSet rs) {
            closeResultSet(rs);
            closeStatement(st);
            closeConnection(conn);
        }
        
        /**
         * 释放连接 Connection
         * @param conn
         */
        private void closeConnection(Connection conn) {
            if(conn !=null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //等待垃圾回收
            conn = null;
        }
        
        /**
         * 释放语句执行者 Statement
         * @param st
         */
        private void closeStatement(Statement st) {
            if(st !=null) {
                try {
                    st.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //等待垃圾回收
            st = null;
        }
        
        /**
         * 释放结果集 ResultSet
         * @param rs
         */
        private void closeResultSet(ResultSet rs) {
            if(rs !=null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //等待垃圾回收
            rs = null;
        }
    }

    ResultHandler 接口代码实现:

    package com.test.DBAssist;
    
    import java.sql.ResultSet;
    
    // 抽象策略
    public interface ResultHandler {
        
        /**
         * 把结果集中的数据封装到 ResultHandler接口的具体对象中
         * @param rs
         * @return
         */
        Object handler(ResultSet rs);
    }

    ResultHandler 接口的实现类代码实现(BeanHandler 类,返回单个 javabean):

    package com.test.DBAssist;
    
    import java.lang.reflect.Field;
    import java.sql.ResultSet;
    
    import com.test.daomain.Student;
    //抽象策略的具体实现
    /**
     * 只对封装一条记录的结果集
     * 返回值:封装好的javabean
     *
     */
    public class BeanHandler implements ResultHandler {
        private Class clazz;
        
        public BeanHandler(Class clazz) {
            this.clazz = clazz;
        }
        
        @Override
        public Object handler(ResultSet rs) {
            try {
                // 判断是否能查询到结果
                if(rs.next()) {
                    Object bean = clazz.newInstance();
                    // 封装数据
                    // 要求javabean 的字段名和数据库的列名是一致的
                    
                    int count = rs.getMetaData().getColumnCount();
                    
                    for (int i = 0; i < count; i++) {
                        // 获取到结果集的列名称(与javabean 的属性名一致)
                        String columnName = rs.getMetaData().getColumnName(i+1);
                        // 获取列值
                        Object columnValue = rs.getObject(columnName);
                        
                        // 得到javabean的对应字段
                        Field field = clazz.getDeclaredField(columnName);
                        field.setAccessible(true);
                        field.set(bean,columnValue);
                    }
                    return bean;
                }
                return null;
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("封装数据失败!");
            }
            
        }
    
    }

    ResultHandler 接口的实现类代码实现(BeanHandler 类,返回 javabean 类型的 list 集合):

    package com.test.DBAssist;
    
    import java.lang.reflect.Field;
    import java.sql.ResultSet;
    import java.util.ArrayList;
    import java.util.List;
    
    public class BeanListHandler implements ResultHandler {
        private Class clazz;
        
        public BeanListHandler(Class clazz) {
            this.clazz = clazz;
        }
        
        @Override
        public Object handler(ResultSet rs) {
            try {
                List list = new ArrayList();
                // 判断是否能查询到结果
                while(rs.next()) {
                    Object bean = clazz.newInstance();
                    // 封装数据
                    // 要求javabean 的字段名和数据库的列名是一致的
                    
                    int count = rs.getMetaData().getColumnCount();
                    
                    for (int i = 0; i < count; i++) {
                        // 获取到结果集的列名称(与javabean 的属性名一致)
                        String columnName = rs.getMetaData().getColumnName(i+1);
                        // 获取列值
                        Object columnValue = rs.getObject(columnName);
                        
                        // 得到javabean的对应字段
                        Field field = clazz.getDeclaredField(columnName);
                        field.setAccessible(true);
                        field.set(bean,columnValue);
                    }
                    list.add(bean);
                }
                return list;
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("封装数据失败!");
            }
            
        }
    
    } 
  • 相关阅读:
    安卓高级6 拍照或者从相册获取图片 并检测旋转角度或者更新画册扫描
    安卓高级6 玩转AppBarLayout,更酷炫的顶部栏 Toolbar
    安卓高级6 CoordinatorLayout
    安卓高级6 SnackBar
    android addCategory()等说明
    Eclipse如何解决启动慢?
    Java基础知识总结(绝对经典)
    java反射详解 三
    java反射详解 二
    java反射详解 一
  • 原文地址:https://www.cnblogs.com/mujingyu/p/7883946.html
Copyright © 2020-2023  润新知