JDBC工具类——JdbcUtils(4)
前言
本系列文章介绍JDBC工具类——JdbcUtils
的封装,部分实现参考了Spring框架的JdbcTemplate
。
完整项目地址:https://github.com/byx2000/JdbcUtils
回顾
在上一篇文章中,我们实现了下面两个RowMapper
:
public class UserRowMapper implements RowMapper<User>
{
@Override
public User map(ResultSet rs) throws Exception
{
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
return user;
}
}
public class BookRowMapper implements RowMapper<Book>
{
@Override
public User map(ResultSet rs) throws Exception
{
Book book = new Book();
book.setId(rs.getInt("id"));
book.setName(rs.getString("name"));
book.setAuthor(rs.getString("author"));
return book;
}
}
这两个RowMapper
虽然代码有很大的不同,但是它们本质上都在做同一件事:把结果集中的一行转换成一个JavaBean。当然,这要求JavaBean中的属性名与结果集中的列名一一对应,同时数据类型也要匹配。可以想象,如果我们的项目中有n个不同的JavaBean,那么就要写n个不同的RowMapper
,而且每个RowMapper
的实现都是十分枯燥乏味的。
要解决这个问题,我们需要实现一个通用的JavaBean行转换器。
实现BeanRowMapper<T>
以下是BeanRowMapper<T>
的实现,用于将ResultSet
的当前行转换成指定的JavaBean:
public class BeanRowMapper<T> implements RowMapper<T>
{
private final Class<T> type;
public BeanRowMapper(Class<T> type)
{
this.type = type;
}
@Override
public T map(ResultSet rs) throws Exception
{
T t = type.getDeclaredConstructor().newInstance();
BeanUtils.populate(t, new MapRowMapper().map(rs));
return t;
ResultSetMetaData metaData = rs.getMetaData();
int count = metaData.getColumnCount();
T bean = type.getDeclaredConstructor().newInstance();
for (int i = 1; i <= count; i++)
{
PropertyDescriptor pd = new PropertyDescriptor(metaData.getColumnLabel(i), type);
Method setter = pd.getWriteMethod();
setter.invoke(bean, rs.getObject(i));
}
return bean;
}
}
在BeanRowMapper<T>
的构造函数中,传入了JavaBean的Class<T>
:
public BeanRowMapper(Class<T> type)
{
this.type = type;
}
在map
方法中,首先获取了ResultSet
的总列数:
ResultSetMetaData metaData = rs.getMetaData();
int count = metaData.getColumnCount();
然后在for循环中遍历ResultSet
的所有列。在for循环体内,首先获取当前列名,然后通过调用JavaBean的getter方法,对JavaBean的同名属性赋值,其中用到了JDK中的反射和内省机制:
for (int i = 1; i <= count; i++)
{
PropertyDescriptor pd = new PropertyDescriptor(metaData.getColumnLabel(i), type);
Method setter = pd.getWriteMethod();
setter.invoke(bean, rs.getObject(i));
}
有了BeanRowMapper<T>
,再配合上一篇文章中实现的ListResultSetMapper<T>
,列表查询需求就能进一步简化:
// 查询所有id大于5的用户
List<User> users = JdbcUtils.query("SELECT * FROM users WHERE id > ?",
new ListResultSetMapper<>(new BeanRowMapper<>(User.class)),
5);
// 查询所有图书
List<Book> books = JdbcUtils.query("SELECT * FROM books",
new ListResultSetMapper<>(new BeanRowMapper<>(Book.class)));
所有对JavaBean的转换都能复用BeanRowMapper<T>
,用户再也不用单独对每一种实体类型都写一个转换器了!
实现SingleRowRecordMapper<T>
有时候,查询的结果集中只有一行,例如查询指定id的用户:
SELECT * FROM users WHERE id = 1001
对于这种情况,可以写一个SingleRowRecordMapper<T>
来从结果集中获取第一行数据:
public class SingleRowRecordMapper<T> implements RecordMapper<T>
{
private final RowMapper<T> rowMapper;
public SingleRowRecordMapper(RowMapper<T> rowMapper)
{
this.rowMapper = rowMapper;
}
@Override
public T map(Record record)
{
if (record.next())
{
return rowMapper.map(record.getCurrentRow());
}
return null;
}
}
rowMapper
字段可以传入预定义的行处理器,也可以传入用户自定义的行处理器。这个类可以配合BeanRowMapper<T>
一起使用:
// 查询id为1001的用户
User user = JdbcUtils.query("SELECT * FROM users WHERE id = ?",
new SingleRowRecordMapper<>(new BeanRowMapper<>(User.class)),
1001);
注意,由于query
函数是泛型函数,所以编译器可以自动推断返回类型。
实现SingleColumnRowMapper<T>
有时候,查询的结果集中只有一个值,例如查询用户总数:
SELECT COUNT(*) FROM users
对于这种情况,可以写一个SingleColumnRowMapper<T>
来从行中获取唯一的列值:
public class SingleColumnRowMapper<T> implements RowMapper<T>
{
private final Class<T> type;
public SingleColumnRowMapper(Class<T> type)
{
this.type = type;
}
@Override
public T map(ResultSet rs) throws Exception
{
return type.cast(rs.getObject(1));
}
}
这里的type
字段用来指定列值的类型(一般来说是Integer
、String
等基本类型)。
为实现查询用户总数的需求,需要配合前面的SingleRowRecordMapper<T>
使用:
// 查询用户总数
Integer count = JdbcUtils.query("SELECT COUNT(*) FROM users",
new SingleRowRecordMapper<>(new SingleColumnRowMapper<>(Integer.class)));
总结
这次我们实现了几个通用的RowMapper
和ResultSetMapper
,用户通过组合不同的RowMapper
和ResultSetMapper
就能实现不同的查询需求,从中我们可以体会到”结果集处理器“和”行处理器“这两个抽象概念的强大之处。