背景
最近在学习Mybatis,Mybatis在处理JDBC返回值与Java业务对象之间的转换非常方便,定义XML,标明互相之间的转换关系,即可轻松完成转换。
Mybatis是JDBC的封装,我们先来看看如果用原生的JDBC,如何完成ResultSet和Java业务对象之间的转换,会遇到哪些不便。
示例代码
Java业务PO
CityPO,包含三个字段 id,cityId,cityName。我们会写使用原生的JDBC来获取返回值。
Integer id;
Long cityId;
String cityName;
获取返回值
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/demo", "root", "123456");
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM SU_City limit 10");
preparedStatement.execute();
ResultSet resultSet = preparedStatement.getResultSet(); //从数据库获取的数据
ResultSetMetaData meta = resultSet.getMetaData(); //从数据库返回的数据的元数据,包含列的基本信息
int cols = meta.getColumnCount();
List<CityPO> cityPOS = new ArrayList<CityPO>();
对象转换
我们要完成的工作就是将ResultSet转换为cityPOS。
方案一
一个比较笨的方法,就是依次判断每个列的名字,因为开发者知道数据库的哪一列对应的是业务PO中的哪个属性,如果列名和属性名相等,那么调用对应属性的set方法,如下面的代码所示。
while (resultSet.next()) {
CityPO cityPO = new CityPO();
for (int i = 1; i <= cols; i++) {
if (meta.getColumnName(i).equals("id")) {
cityPO.setId((Integer) resultSet.getObject(i));
} else if (meta.getColumnName(i).equals("city_id")) {
cityPO.setCity_id(Long.valueOf((Integer)resultSet.getObject(i)));
} else if (meta.getColumnName(i).equals("city_name")) {
cityPO.setCity_name((String) resultSet.getObject(i));
}
cityPOS.add(cityPO);
}
}
缺点
- 转换完全和具体的业务类绑定在了一起,你如果新处理一个业务类,你得再重新写一套差不多的代码,而且随着类的属性字段的增多,你的if/else代码会越来越多,听起来是不是很恐怖。
- 在调用set方法时,我们需要处理类型转换,又是一长串的代码。我们这个方案没用反射,不知道set属性的类型,只能靠人为判断,很有可能出现异常。
我在cityPO.setCity_id(Long.valueOf((Integer)resultSet.getObject(i))) 就遇到了把Integer直接强转Long失败,报出了异常才改成了现在这样,如果这样的代码出现在你应用的各个角落,你慌不?
结论
方案1的处理和具体的代码绑定的比较死,依靠一堆判断来完成赋值,那么我们想到可以利用反射来完成对属性的赋值,这样字段再多,也不怕啦。
方案二
利用反射,通过mysql字段的名称,直接反射找到对应属性的set方法,执行调用。简单的代码如下所示。
Class<?> clazz = Class.forName("po.CityPO");
Object obj = clazz.newInstance();
for (int i = 1; i <= cols; i++) {
Field field = null;
field = clazz.getDeclaredField(meta.getColumnName(i));
field.setAccessible(true);
field.set(obj, resultSet.getObject(i));
}
cityPOS.add((CityPO) obj);
这段我为了测试,是直接把对应的对象的属性名改成了数据库中的列名。
缺点
- 因为是通过数据库的列名反射出对应的方法,而通常我们在Java中使用的是驼峰命名,怎么处理列名到对象类名的转换,这里应该有一定的代码量,当然也可以在select的时候 as成对应的属性名,这样就解决了这个问题。
- 我们依然需要处理类型转换。
- 我们可能会多选了一些列,而这个方案会统统去找反射的方法,可能会抛出异常。
结论
我觉得这应该是框架会采取的方案。
总结
直接用原生的JDBC完成数据库到Java业务对象的转换,肯定是非常繁琐的,从目前来看,框架应该也是用的方案2,只不过会比方案案强大的多。
从使用Mybatis这一段时间来看,其在这个方案做了这么几件事。
- 解决了数据库列名到Java列名的映射。
- 解决了数据库类型到Java类型的转换工作。
- 在转换过程中具备一定的容错能力。
后续
后续会研究下Mybatis在这方面所做的工作和源码,有兴趣可以持续关注我的微信公众号。