Java基础——编写自己的JDBC框架

这篇博客介绍了如何编写自己的Java JDBC框架,重点在于如何实现通用的update和query方法。通过分析update操作,展示了如何构建万能更新SQL。对于query操作,讨论了如何处理不同查询结果并利用BeanUtils将数据复制到对象中,同时提出了对非全字段查询的解决方案,即用户根据需求封装对象。最后提到了Apache的dbutils作为参考,以及如何改造query方法,将结果处理部分交给用户自定义实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

到现在我们先想想,我们学习了数据库操作哪些内容?????

  1. 先是JDBC入门学习,学习Java连接数据库

  2. 然后将连接数据库getConnection和释放连接release提取出来, 封装成jdbcutils

  3. 然后就是事务,主要是学习下面几个函数使用

    Connection.setAutoCommit(false);//开启事务(start transaction)
    
    Connection.rollback();//回滚事务(rollback)
    
    Connection.commit();//提交事务(commit)
    
    
    
    Savepoint sp = conn.setSavepoint();
    
    Conn.rollback(sp);
    
    Conn.commit();//回滚后必须通知数据库提交事务

     

  4. 然后就是学习数据库连接池,主要学习两个 DBCP和C3P0,主要是改进jdbcutils

  5. 然后就是编写自己的JDBC框架,说白了就是将CRUD整合成update函数和query函数


我们先来说说 update 万能更新

看看下面的几个sql语句

INSERT INTO Persons VALUES ('Gates', 'Bill', 'Xuanwumen 10', 'Beijing')

UPDATE Person SET FirstName = 'Fred' WHERE LastName = 'Wilson'

DELETE FROM Person WHERE LastName = 'Wilson'

认真观察, 实体的CUD操作代码基本相同,仅仅发送给数据库的SQL语句不同而已,我们就把sql语句拆分成一个框架sql语句和实体sql对象 String sql, Object params[]

这个好理解,所以写成万能update也简单

/**
    * @Method: update
    * @Description: 万能更新
    * @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);
        }
    }

 


但是!!!!!!

query不好理解,因为select * from table 这个* 不同select 就有不同的对象,所以写万能query就有点麻烦。

万幸有Object对象来指向它的万物子孙

Object obj = new Person() ; Object obj1 = new Cat() ; Object obj2 = new Shop() ; .......

因此, 我们需要将具体的类传入到参数中,来保存select查询的结果

我们来一步步看看用户的操作思路

首先,我们要从数据库中把我们想要查询的字段给查询出来嘛,

每个使用select 的用户知道数据库的table , 知道table包含哪些字段嘛, 所以就会设置相应的类来存储这些字段

不仅获取到table内容,

我们还要知道字段名,这个很重要 ,你想想,我们光光从数据库取出数据是不够的, 还要把数据装填给对象啊,

比如:select * from student ,执行了条sql后, 内容保存在哪里呢?

好吧,我们在java的domain 写个Student类, 然后new一个Student对象嘛,

但是咋个一一赋值呢? 所以我们要知道字段名,下面这几行代码就可以获取到,

    Connection conn = JdbcUtils.getConnection();
    String sql = "select * from account ";
    PreparedStatement st = conn.prepareStatement(sql);
    ResultSet rs = st.executeQuery();
    ResultSetMetaData metadata = rs.getMetaData();

弱弱解释一下ResultSetMetaData可以干什么事情,如下:

/**  
 * <p>Title: testResultSetMetaData</p>  
 * <p>Description:  获取结果集的元数据</p>    
 * @throws SQLException 
 * 
 * getColumnCount() 返回resultset对象的列数
 * getColumnName(int column) 获得指定列的名称
 * getColumnTypeName(int column)获得指定列的类型
 */

接下来就是写类, 类的成员变量要跟数据库的字段一一对应,

然后怎么把查询的字段内容复制给这个类呢???

我们来梳理一下正常操作流程,先查询数据库 ,获取到列名和列的内容,

用BeanUtils,将字段复制给类的对象

        while (rs.next()) {
            // 2.赋值给对象
            int colCount = metaData.getColumnCount();
            Account account = new Account();
            for (int i = 1; i <= colCount; i++) {
                String colName = metaData.getColumnName(i );
                Object objCol = rs.getObject(i);
                
                BeanUtils.setProperty(account, colName, objCol);
            }
            list.add(account);
        }

主要用到了BeanUtils ,这样就把列的内容复制给对象了

  • commons-beanutils-1.9.3.jar

  • commons-logging-1.2.jar

但这里也有一个问题, 就是sql 必须是select * from *** ,

如果用户不想用这条sql呢? 怎么办 , 比如:select name , money from account where id=?

那么就制定一个规则,用户要查询什么,就封装什么对象

比如你只想查询表中某几个字段的内容, 那么你就要将这几个字段封装成对象,

没办法, 系统不可能预先知道你要查询啥子吧,只有用户你自己知道,自己知道就自己动手封装

select id , name from account where id = xxx

抽取出来, 就是 :

框架 + 参数 + 对象

 

    String sqlString = " select id,money from account where name =? ";
    Object[] paramsObjects = { "aaa"};
    Account account = new Account();
    
    query(sqlString, paramsObjects, account);

然而, apache的dbutils不是使用BeanUtils ,肯定是自己写

也就是这句话BeanUtils.setProperty(account, colName, objCol); 要想办法重新写, 咋写????

反射,还记得吗?

需要自动化赋值类成员 的代码模板

    // 1.获取Class对象
    Class clazz = Class.forName("com.reflect.domain.Student");
    
    // 2.获取一个对象,或者调用者手动传进来一个
    Object obj = clazz.getConstructor().newInstance();// 产生Student对象--》Student stu = new Student();
    
    // 3.获取某个字段(可以是私有的)
    Field field1 = clazz.getDeclaredField("phoneNum");
    
    //  4.赋值
    field1.setAccessible(true);//暴力反射,解除私有限定
    field1.set(obj, "18888889999");

上面的phoneNum是成员变量,感觉好像不智能,,,,

getColumnName(int column) 获得指定列的名称 , 忘啦??? 这一步步来着呢

 

到现在就顺了,因为我们啥都理顺了,总结一下步骤:

  1. 使用jdbcutils连接数据库

  2. 用conn.prepareStatement(sql);预执行sql语句

  3. 获取结果集的元数据

  4. 使用反射给对象赋值

  5. binggo!!!

    /**
     * @param sql 用户传递的sql语句
     * @param params sql语句中包含的参数
     * @param obj 用户传进来的对象, 比如Account account = new Account();
     */
     public void query(String sql, Object[] params, Object obj) {
        // 1.首先,获取数据库的连接
        Connection conn = JdbcUtils.getConnection();
        PreparedStatement ps = null;
        ResultSet rs = null;
        ResultSetMetaData metaData = null;
​
        try {
            //2.用conn.prepareStatement(sql);预执行sql语句
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i + 1, params[i]);
            }
            rs = ps.executeQuery();
​
            //3.获取结果集的元数据
            metaData = rs.getMetaData();
            int colCount = metaData.getColumnCount();
            
            //4.使用反射给对象赋值
            Class<? extends Object> clazz = obj.getClass();
            //这是好像应该用list,因为rs获取的结果有可能是多个
            while (rs.next()) {
                for (int i = 1; i <= colCount; i++) {
                    String colName = metaData.getColumnName(i);
                    Object objCol = rs.getObject(i);
​
                    //4.使用反射给对象赋值
                    Field field = clazz.getDeclaredField(colName);
                    field.setAccessible(true);
                    field.set(obj, objCol);
                }
            }
​
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.releaseConnection(conn, ps, rs);
        }
    }

上面就是JdbcUtils框架查询函数的流程, 只是说JdbcUtils框架将上面的函数做了处理,用BeanHandler 和BeanListHandler 来封装了上面一部分的代码,

感觉是不是高级些,取个名字都这么氧气?

BeanHandler 和BeanListHandler都实现了ResultSetHandler 这个接口, ResultSetHandler 是系统暴露给用户的接口

还是那个话,用户query数据库, 数据库返回结果集, 这个结果集该咋处理, 系统不知道, 只有用户知道,

用户要是都不知道, 那用户干嘛查询来了?????

既然用户知道,那我系统就给你一个接口, 你来实现呗,只要你按照我的框架协议写就好了

 

我们现在来看看上面的query如何改造嘛:

第1、2 步骤是标准数据库操作,这个不宜分离开来

第3、4步则是处理结果集合,我们想办法改造,说是改造, 还不如说向Apache的dbutils靠拢, 看看他们是怎么写的

我们来看看 3、4步,

传入的参数是ResultSet rs , 返回的是Object obj 或者是List<Object> ,这也说明了返回的结果不相同,众口难调,于是系统写个接口, 让用户自个儿实现

我们先想想,系统如何写接口???? 本身就这几行代码,系统能玩出什么花样????

既然是接口, 那么就要抽出共性,步骤4肯定不能, 因为返回的是Object的话,就没有while循环,如果是list,则有while循环,这个就是个性

步骤3又跟步骤4紧密联系,也不能作为共性,所以干脆来个猛的,

系统写个接口, 啥都不干,就来规范!!!

/**
* @ClassName: ResultSetHandler
* @Description:结果集处理器接口
*
*/ 
public interface ResultSetHandler {
    
    /**
    * @Method: handler
    * @Description: 结果集处理方法
    * @param rs 查询结果集
    * @return
    */ 
    public Object handler(ResultSet rs);
}
把个性的操作全部放在handler中实现,

接下来就是BeanHandler 和BeanListHandler , 一个是返回Ojbect 一个是返回list

一个个来

BeanHandler 其实就是不用while嘛

BeanHandler 写法,跟上面的代码有点不一样, 但说白了,都差不多, 我们来看嘛

/**
* @ClassName: BeanHandler
* @Description: 将结果集转换成bean对象的处理器
*
*/ 
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);
        }
    }
}

BeanHandler 把clazz作为了成员变量, 然后遵守

public Object handler(ResultSet rs); 返回bean object

是不是差不多嘛???

不过它这里有点巧妙的,就是把clazz作为了成员变量 , 返回Object bean = clazz.newInstance(); 也就是传入进去的对象是什么,然后装填该对象, 返回的就是什么对象, 从这个角度来看,BeanHandler 像是一个容器,可以装可乐,也可以装芬达。。。。 但是主体框架没有变化,有点类似 Object obj = new student();

想哈我们怎么调用它嘛

(Account) JdbcUtils.query(sql, params, new BeanHandler(Account.class));

对不对, 传入的是BeanHandler框架,只是BeanHandler装的内容不一样,返回的就是该内容

 

BeanListHandler

其实也一样, 无非就多了个while循环嘛

看代码

/**
* @ClassName: BeanListHandler
* @Description: 将结果集转换成bean对象的list集合的处理器
*
*/ 
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);
        }
    }
}

是不是多了个while嘛?

然后看调用

(List<Account>) JdbcUtils.query(sql, params,new BeanListHandler(Account.class));

有点意思

 

 

我们回过头来说说query ,现在我们是不是改造了嘛,该咋写

第1、2步不变,第3、4步封装成接口,让用户自己去实现,实现的方式有BeanHandler 和BeanListHandler

所以

/**
    * @Method: query
    * @Description:万能查询
    * 实体的R操作,除SQL语句不同之外,根据操作的实体不同,对ResultSet的映射也各不相同,
    * @param sql 要执行的SQL
    * @param params 执行SQL时使用的参数
    * @param rsh 查询返回的结果集处理器
    * @return
    * @throws SQLException
    */ 
    public static Object query(String sql,Object params[],ResultSetHandler rsh) throws SQLException{
        // 1.首先,获取数据库的连接
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        
        try{
            conn = getConnection();
            //2.用conn.prepareStatement(sql);预执行sql语句
            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);
        }
    }

基本上就说完了。接下来我们学习Apache的dbutils

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值