基于JDBC的原生代码+反射机制,简单模拟DBUtils的实现方式

写在开头的话:

现代开发基于框架,mybatis、mybatis-plus才是常用的,DBUtils其实都很少用了,这篇笔记只是我当初刚了解JAVA与数据库的连接时,基于JAVA反射机制的一些钻研,认识也并不算非常准确,仅供参考


问题描述

如果使用JDBC最原始的方式连接数据库,那么在执行查询的sql语句时,我们需要使用动态数组存放数据,比如下图的students对象,然后循环的将结果集中每一行数据与对象的成员变量对应,从而实现把结果集上的数据都存入对象中的目的

但是对比查询全表和查询单个数据的代码,我们发现这里的代码看起来重复性太高了,而且当SQL语句变化后,对应着这一赋值加取值的代码就得变化,代码复用性很低

左图是查询单个数据,右图是查询全表,将数据库表中的数据存入对象

如果我们可以在获取到RS结果集的同时,能确切的知道结果集的每一列的列名以及对应的数据类型,那么我们就可以通过循环的方式让JAVA自动根据结果集去调用Student的set函数去储存数据。

这样无论SQL语句如何变导致结果集如何变,我们只要把对象的成员变量定义好,就能实现自动存入数据,不用再去跟随SQL的变化而去修改存储数据的代码

这里面涉及两个难点:

①等式右边 由结果集给出列名、列的总数以及对应的数据类型

②等式左边 自动根据结果集所给数据调用作为容器的对象的对应set函数

第一个难点不是问题,毕竟结果集就存在rs对象中,而rs对象提供了许多方法让我们想查什么就查什么

注意rs对象提供的方法中获取列名的两种方法的区别:

带Label的会返回列名的别名,如果没有别名则返回本名

带Name的只会返回列的本名

ResultSetMetaData中getColumnLabel和getColumnName的区别 - freeTimeWY - 博客园 (cnblogs.com)

第二个难点则比较难解决

假想JAVA从数据库获得了结果集,要想自动的给JAVA一个容器,那么我们可以通过定义一个对象,然后使用动态数组储存对象,只要RS.next()为true,就表明还有新的“行”数据,此时利用循环,让结果集每一行数据都能对应一个新建的对象即可实现存储。

接下来问题就变成了一行有多列,如何把每一列的数据对应到对象的成员变量中,注意每一列的列名、数据类型不同:

  

其实问题就是如何让JAVA自动实现上图的代码

对于结果集,要根据不同的列相应的数据类型使用不同的get函数,例如getString() getData() getInt()

对于创建的对象,要根据不同的列名对应上对象中的成员变量,例如数据库中的id 对应对象中的private Integer id 此时就要用setId;数据库中的sname 对应对象中的private String name 此时就要用setSName;

此时只需要解决下面的四个问题就能实现我们需要的“自动封装”

①我们要如何让JAVA能自动的根据RS结果集所给的列名反推出set函数名

②又如何自动的反推出set函数需要的参数类型

③接着又如何操作这个类创建出一个实例对象

④最后又如何自动去使用这个实例对象执行set函数去存储RS结果返回的数据


我们从头捋一遍思路:

①首先我们的目的很简单

在调用查询语句时要让JAVA自动的把结果集每一行数据存入我们声明的对象中,这些对象则组成集合,这样我们就能通过集合获取结果集每一行的数据

所以我们定义SQL语句、定义集合并声明集合元素为我们要储存的数据类型、使用我们写好的一个查询方法

查询方法相比传统 多了一个Student.class 这样就等于传入了一个指向Student的对象 JAVA就能使用这个对象自动操作这个类去创建student对象 并且调用对象的set函数存值

②进入我们写好的查询方法

首先整个方法使用了泛型 所以代码复用性更高了

接着我们看到了一个Class类对象 clazz 这就是我们外部传入的Student.class 所以clazz指向的是Student

③接着我们先处理第一个难点,让结果集返回我们需要的数据

基于RS.getMetaData() 我们能获得一个专门的对象 metaData用于获取结果集中的列的属性和数量

基于metaData.getColumnCount() 我们就能知道结果集有多少列 这就能决定要循环多少次 毕竟每一次循环就是将一列数据中的单个数据存入一个对象中

基于metaData.getColumnClassName(列的索引) 我们就能获取到结果集每一列的数据类型 是int 还是string 还是data

基于metaData.getColumnLabel(列的索引) 我们就能获取到结果集每一列的别名,如果没有别名则获得本名

最后RS基于列名 使用getObject方法 不去考虑获取到的数据的具体类型 就能实现逐列取出数据的目的

这里RS也能根据索引 i来取值 比较在for循环中i与列名一一对应 此处用列名还是i都行

④在搞定了等式右边后,我们回看等式左边

我们写了一个invoke方法 分别放入一个 “clazz调用自己指向的类的无参构造”创建的对象object 某一列的数据类型 某一列的列名 clazz 当前所在行列对应的值value

⑤我们进入invoke方法

在这里可以看到 我们做的第一个事情就是使用我们写的一个工具类,拼接set函数名

这样我们就获得了一个方法名 假如此时的列名是id 那么methodName就是setId 这个方法名可以理解为就是我们声明的类Student内部的那个setid()

接着我们又使用了一个工具类,获得一个指向某个类的class对象

这样我们就获得了第二个指向某个类的对象aClass 假如此时列名是id 对应数据类型为int 那么此时aClass就指向Integer类

然后我们使用了clazz.getMethod

基于这个方法 我们传入了set方法的全名 以及这个方法需要的参数 aClass ——>一个指向Integer类的Class类的对象  为什么要传参   为什么 setId() 传入的参数是Integer类

这样就能获得clazz指向的类中与methodName同名的且声明的参数是Integer的方法 setId(Integer a)

最后使用Method类的对象独有的invoke方法 传入要执行method这个方法的对象object以及method这个方法需要的值value

以ID列为例,就等于令student.setId(value)

上图中的知识点:

拼接set函数名:

获得一个指向某个类的class对象:

为什么要传参:

如果不给参数就等于去调用同名但无参的另一个方法 这属于方法重载下会产生的偏差 所以必须传参

具体到例子中就是获得了clazz指向的Student类中setId(传入一个Integer类参数) 方法

如果不传参 那就只能获得一个Student类中的无参的setId()方法 但这个方法可不存在

为什么 setId() 传入的参数是Integer类:

当然是因为我们基于面向对象的思维去声明对象时定义的 Student类中所有成员变量都是类

⑥保存结果 重新循环

在上面的操作后 以后我们每执行一次invoke方法 就等于自动让一个object对象执行了一个set函数 存入的是结果集中循环到的某个行列下的value值

然后将object对象存入List<Student>集合中 就实现了自动将结果集存入集合的操作

⑦关于可能的异常

可能会遇到一个类型不匹配的异常,会报异常,但是不影响数据存储:

1.关于数据库数据存入对象时虽然成功但是报类型不匹配异常的原因以及解决办法

⑧最后我们回答一下上面整理的四个问题

解答①:

我们通过基于数据表设置了容器对象 要求对象中的成员变量的名字与数据表的列名一一对应 因此可以根据RS结果集所给的列名拼接出对应的set函数名

解答②:

我们知道如果希望获取类中的某个函数 例如这里的set函数 除了有函数名 还要求对应上参数表的数据类型 否则基于方法重载的机制,我们会获取到错误的方法

而我们又知道当我们set某个列的数时,对应的set函数的参数表就应该是这个列的数据类型

所以我们可以基于RS结果集所给的列的数据类型(给的是一个全类名) 对应到JAVA中的类(基于class.forName()) 而class.forName返回的就是set函数参数表需要的参数类型

然后已经有了指向某个类的对象clazz 以及具体的方法名、方法需要的参数表 那么获取到类中具体的某个方法自然就很简单了——基于getMethon()

这样我们就获得了需要的方法 并且以Method类的对象来保存这个方法

解答③:

我们拥有指向类的对象 clazz 那么直接调用clazz.newInstance() 即可调用无参构造获得一个对象object

解答④:

我们已经有了一个Method类的对象 那么基于这个对象的invoke方法 传入要执行这个方法的对象object以及值value

就实现了将RS结果集返回的数据存入相应对象的目的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值