javaweb学习总结(四十)——编写自己的JDBC框架

javaweb学习总结(四十)——编写自己的JDBC框架

一、元数据介绍

  元数据指的是"数据库"、"表"、"列"的定义信息。

1.1、DataBaseMetaData元数据

  Connection.getDatabaseMetaData()获得代表DatabaseMetaData元数据的DatabaseMetaData对象。
  DataBaseMetaData对象的常用方法:

  • getURL():返回一个String类对象,代表数据库的URL。
  • getUserName():返回连接当前数据库管理系统的用户名。
  • getDatabaseProductName():返回数据库的产品名称。
  • getDatabaseProductVersion():返回数据库的版本号。
  • getDriverName():返回驱动驱动程序的名称。
  • getDriverVersion():返回驱动程序的版本号。
  • isReadOnly():返回一个boolean值,指示数据库是否只允许读操作。
  • /**
        * @Method: testDataBaseMetaData
        * @Description: 获取数据库的元信息
        * @Anthor:奋斗的小虾米
        *
        * @throws SQLException
        */
        @Test
        public void testDataBaseMetaData() throws SQLException {
            Connection conn = JdbcUtils.getConnection();
            DatabaseMetaData metadata = conn.getMetaData();
            //getURL():返回一个String类对象,代表数据库的URL
            System.out.println(metadata.getURL());
            //getUserName():返回连接当前数据库管理系统的用户名
            System.out.println(metadata.getUserName());
            //getDatabaseProductName():返回数据库的产品名称
            System.out.println(metadata.getDatabaseProductName());
            //getDatabaseProductVersion():返回数据库的版本号
            System.out.println(metadata.getDatabaseProductVersion());
            //getDriverName():返回驱动驱动程序的名称
            System.out.println(metadata.getDriverName());
            //getDriverVersion():返回驱动程序的版本号
            System.out.println(metadata.getDriverVersion());
            //isReadOnly():返回一个boolean值,指示数据库是否只允许读操作
            System.out.println(metadata.isReadOnly());
            JdbcUtils.release(conn, null, null);
        }

    运行结果如下:

      

    1.2、ParameterMetaData元数据

      PreparedStatement.getParameterMetaData() 获得代表PreparedStatement元数据的ParameterMetaData对象。
      Select * from user where name=? And password=?
      ParameterMetaData对象的常用方法:

  • getParameterCount(): 获得指定参数的个数
  • getParameterType(int param):获得指定参数的sql类型,MySQL数据库驱动不支持
    /**
        * @Method: testParameterMetaData
        * @Description: 获取参数元信息
        * @Anthor:奋斗的小虾米
        *
        * @throws SQLException
        */
        @Test
        public void testParameterMetaData() throws SQLException {
            Connection conn = JdbcUtils.getConnection();
            String sql = "select * from user wherer name=? and password=?";
            //将SQL预编译一下
            PreparedStatement st = conn.prepareStatement(sql);
            ParameterMetaData pm = st.getParameterMetaData();
            //getParameterCount() 获得指定参数的个数
            System.out.println(pm.getParameterCount());
            //getParameterType(int param):获得指定参数的sql类型,MySQL数据库驱动不支持
            System.out.println(pm.getParameterType(1));
            JdbcUtils.release(conn, null, null);
        }

    1.3、ResultSetMetaData元数据

      ResultSet. getMetaData() 获得代表ResultSet对象元数据的ResultSetMetaData对象。
      ResultSetMetaData对象的常用方法:

  • getColumnCount() 返回resultset对象的列数
  • getColumnName(int column) 获得指定列的名称
  • getColumnTypeName(int column)获得指定列的类型
    /**
        * @Method: testResultSetMetaData
        * @Description: 结果集的元数据
        * @Anthor:奋斗的小虾米
        *
        * @throws Exception
        */
        @Test
        public void testResultSetMetaData() throws Exception {
            Connection conn = JdbcUtils.getConnection();
            String sql = "select * from account";
            PreparedStatement st  = conn.prepareStatement(sql);
            ResultSet rs = st.executeQuery();
            //ResultSet.getMetaData()获得代表ResultSet对象元数据的ResultSetMetaData对象
            ResultSetMetaData metadata = rs.getMetaData();
            //getColumnCount() 返回resultset对象的列数
            System.out.println(metadata.getColumnCount());
            //getColumnName(int column) 获得指定列的名称
            System.out.println(metadata.getColumnName(1));
            //getColumnTypeName(int column)获得指定列的类型
            System.out.println(metadata.getColumnTypeName(1));
            JdbcUtils.release(conn, st, rs);
        }

    二、使用元数据封装简单的JDBC框架

      系统中所有实体对象都涉及到基本的CRUD操作
      所有实体的CUD操作代码基本相同,仅仅发送给数据库的SQL语句不同而已,因此可以把CUD操作的所有相同代码抽取到工具类的一个update方法中,并定义参数接收变化的SQL语句。
      实体的R操作,除SQL语句不同之外,根据操作的实体不同,对ResultSet的映射也各不相同,因此可义一个query方法,除以参数形式接收变化的SQL语句外,可以使用策略模式由qurey方法的调用者决定如何把ResultSet中的数据映射到实体对象中。

    2.1、封装通用的update方法和qurey方法

      定义一个JdbcUtils工具类,工具类负责获取数据库连接,释放资源,执行SQL的update和query操作,代码如下:

  • package me.gacl.util;
    
    import java.io.InputStream;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.Properties;
    
    public class JdbcUtils {
    
        private static String driver = null;
        private static String url = null;
        private static String username = null;
        private static String password = null;
    
        static{
            try{
                //读取db.properties文件中的数据库连接信息
                InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
                Properties prop = new Properties();
                prop.load(in);
    
                //获取数据库连接驱动
                driver = prop.getProperty("driver");
                //获取数据库连接URL地址
                url = prop.getProperty("url");
                //获取数据库连接用户名
                username = prop.getProperty("username");
                //获取数据库连接密码
                password = prop.getProperty("password");
    
                //加载数据库驱动
                Class.forName(driver);
    
            }catch (Exception e) {
                throw new ExceptionInInitializerError(e);
            }
        }
    
        /**
        * @Method: getConnection
        * @Description: 获取数据库连接对象
        * @Anthor:奋斗的小虾米
        *
        * @return Connection数据库连接对象
        * @throws SQLException
        */
        public static Connection getConnection() throws SQLException{
            return DriverManager.getConnection(url, username,password);
        }
    
        /**
        * @Method: release
        * @Description: 释放资源,
        *     要释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
        * @Anthor:奋斗的小虾米
        *
        * @param conn
        * @param st
        * @param rs
        */
        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();
                }
            }
        }
    
        /**
        * @Method: update
        * @Description: 万能更新
        * 所有实体的CUD操作代码基本相同,仅仅发送给数据库的SQL语句不同而已,
        * 因此可以把CUD操作的所有相同代码抽取到工具类的一个update方法中,并定义参数接收变化的SQL语句
        * @Anthor:奋斗的小虾米
        * @param sql 要执行的SQL
        * @param params 执行SQL时使用的参数
        * @throws SQLException
        */
        public static void update(String sql,Object params[]) throws SQLException{
            Connection conn = null;
            PreparedStatement st = null;
            ResultSet rs = null;
            try{
                conn = getConnection();
                st = conn.prepareStatement(sql);
                for(int i=0;i<params.length;i++){
                    st.setObject(i+1, params[i]);
                }
                st.executeUpdate();
    
            }finally{
                release(conn, st, rs);
            }
        }
    
        /**
        * @Method: query
        * @Description:万能查询
        * 实体的R操作,除SQL语句不同之外,根据操作的实体不同,对ResultSet的映射也各不相同,
        * 因此可义一个query方法,除以参数形式接收变化的SQL语句外,可以使用策略模式由qurey方法的调用者决定如何把ResultSet中的数据映射到实体对象中。
        * @Anthor:奋斗的小虾米
        *
        * @param sql 要执行的SQL
        * @param params 执行SQL时使用的参数
        * @param rsh 查询返回的结果集处理器
        * @return
        * @throws SQLException
        */
        public static Object query(String sql,Object params[],ResultSetHandler rsh) throws SQLException{
    
            Connection conn = null;
            PreparedStatement st = null;
            ResultSet rs = null;
    
            try{
                conn = getConnection();
                st = conn.prepareStatement(sql);
                for(int i=0;i<params.length;i++){
                    st.setObject(i+1, params[i]);
                }
                rs = st.executeQuery();
                /**
                 * 对于查询返回的结果集处理使用到了策略模式,
                 * 在设计query方法时,query方法事先是无法知道用户对返回的查询结果集如何进行处理的,即不知道结果集的处理策略,
                 * 那么这个结果集的处理策略就让用户自己提供,query方法内部就调用用户提交的结果集处理策略进行处理
                 * 为了能够让用户提供结果集的处理策略,需要对用户暴露出一个结果集处理接口ResultSetHandler
                 * 用户只要实现了ResultSetHandler接口,那么query方法内部就知道用户要如何处理结果集了
                 */
                return rsh.handler(rs);
    
            }finally{
                release(conn, st, rs);
            }
        }
    }

     在设计query方法时,对于查询返回的结果集处理使用到了策略模式,query方法事先是无法知道用户对返回的查询结果集如何进行处理的,即不知道结果集的处理策略, 那么这个结果集的处理策略就让用户自己提供,query方法内部就调用用户提交的结果集处理策略进行处理, 为了能够让用户提供结果集的处理策略,需要对用户暴露出一个结果集处理接口ResultSetHandler, 结果集处理器接口ResultSetHandler的定义如下:

    package me.gacl.util;
    
    import java.sql.ResultSet;
    
    /**
    * @ClassName: ResultSetHandler
    * @Description:结果集处理器接口
    * @author: 奋斗的小虾米
    * @date: 2020-11-15 下午12:01:27
    *
    */
    public interface ResultSetHandler {
    
        /**
        * @Method: handler
        * @Description: 结果集处理方法
        * @Anthor:奋斗的小虾米
        *
        * @param rs 查询结果集
        * @return
        */
        public Object handler(ResultSet rs);
    }

    用户只要实现了ResultSetHandler接口,那么就是针对查询结果集写了一个处理器,在query方法内部就调用用户自己写的处理器处理结果集。

  • 2.2、编写常用的结果集处理器

      为了提高框架的易用性,我们可以事先就针对结果集写好一些常用的处理器,比如将结果集转换成bean对象的处理器,将结果集转换成bean对象的list集合的处理器。

    2.2.1、BeanHandler——将结果集转换成bean对象的处理器

    package me.gacl.util;
    
    import java.lang.reflect.Field;
    import java.sql.ResultSet;
    import java.sql.ResultSetMetaData;
    
    /**
    * @ClassName: BeanHandler
    * @Description: 将结果集转换成bean对象的处理器
    * @author: 奋斗的小虾米
    * @date: 2020-11-15 下午12:00:33
    *
    */
    public class BeanHandler implements ResultSetHandler {
        private Class<?> clazz;
        public BeanHandler(Class<?> clazz){
            this.clazz = clazz;
        }
        public Object handler(ResultSet rs) {
            try{
                if(!rs.next()){
                    return null;
                }
                Object bean = clazz.newInstance();
                //得到结果集元数据
                ResultSetMetaData metadata = rs.getMetaData();
                int columnCount = metadata.getColumnCount();//得到结果集中有几列数据
                for(int i=0;i<columnCount;i++){
                    String coulmnName = metadata.getColumnName(i+1);//得到每列的列名
                    Object coulmnData = rs.getObject(i+1);
                    Field f = clazz.getDeclaredField(coulmnName);//反射出类上列名对应的属性
                    f.setAccessible(true);
                    f.set(bean, coulmnData);
                }
                return bean;
            }catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    2.2.2、BeanListHandler——将结果集转换成bean对象的list集合的处理器

    package me.gacl.util;
    
    import java.lang.reflect.Field;
    import java.sql.ResultSet;
    import java.sql.ResultSetMetaData;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
    * @ClassName: BeanListHandler
    * @Description: 将结果集转换成bean对象的list集合的处理器
    * @author: 奋斗的小虾米
    * @date: 2014-10-5 下午12:00:06
    *
    */
    public class BeanListHandler implements ResultSetHandler {
        private Class<?> clazz;
        public BeanListHandler(Class<?> clazz){
            this.clazz = clazz;
        }
    
        public Object handler(ResultSet rs) {
            try{
                List<Object> list = new ArrayList<Object>();
                while(rs.next()){
                    Object bean = clazz.newInstance();
    
                    ResultSetMetaData  metadata = rs.getMetaData();
                    int count = metadata.getColumnCount();
                    for(int i=0;i<count;i++){
                        String name = metadata.getColumnName(i+1);
                        Object value = rs.getObject(name);
    
                        Field f = bean.getClass().getDeclaredField(name);
                        f.setAccessible(true);
                        f.set(bean, value);
                    }
                    list.add(bean);
                }
                return list.size()>0?list:null;
    
            }catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

     当框架自身提供的结果集处理器不满足用户的要求时,那么用户就可以自己去实现ResultSetHandler接口,编写满足自己业务要求的结果集处理器。

      有了上述的JdbcUtils框架之后,针对单个实体对象CRUD操作就非常方便了,如下所示:

    package me.gacl.dao;
    
    import java.sql.SQLException;
    import java.util.List;
    import me.gacl.domain.Account;
    import me.gacl.util.BeanHandler;
    import me.gacl.util.BeanListHandler;
    import me.gacl.util.JdbcUtils;
    
    public class AccountDao {
    
        public void add(Account account) throws SQLException{
            String sql = "insert into account(name,money) values(?,?)";
            Object params[] = {account.getName(),account.getMoney()};
            JdbcUtils.update(sql, params);
        }
    
    
        public void delete(int id) throws SQLException{
            String sql = "delete from account where id=?";
            Object params[] = {id};
            JdbcUtils.update(sql, params);
        }
    
        public void update(Account account) throws SQLException{
    
            String sql = "update account set name=?,money=? where id=?";
            Object params[] = {account.getName(),account.getMoney(),account.getId()};
            JdbcUtils.update(sql, params);
    
        }
    
        public Account find(int id) throws SQLException{
            String sql = "select * from account where id=?";
            Object params[] = {id};
            return (Account) JdbcUtils.query(sql, params, new BeanHandler(Account.class));
        }
    
        public List<Account> getAll() throws SQLException{
            String sql = "select * from account";
            Object params[] = {};
            return (List<Account>) JdbcUtils.query(sql, params,new BeanListHandler(Account.class));
        }
    }

    编写的这个JDBC框架就是模拟Apache的DBUtils框架的实现,下一篇将具体介绍Apache的DBUtils框架。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奋斗的小虾米

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值