引言
上一篇jdbc的文章《JDBC——概述与JDBC的使用》介绍了JDBC的概念和背景知识,同时也讨论了获取数据库连接的方式,以及简单的实现了入库操作(更新、删除同理)。
本篇博客将会聚焦 PreparedStatement 的查询操作、以及 ResultSet 的结果集处理逻辑,结合 ResultSetMetaData 和反射技术实现通用的查询方法。
一、Java与SQL对应数据类型转换表
Java类型 | SQL类型 |
boolean | BIT |
byte | TINYINT |
short | SMALLINT |
int | INTEGER |
long | BIGINT |
String | CHAR、VARCHAR、LONGVARCHAR |
byte array | BINARY、VAR BINARY |
java.sql.Date | DATE |
java.sql.Time | TIME |
java.sql.timestamp | TIMESTAMP |
二、相关类
JDBCUtils 封装了获取连接和关闭资源等通用操作(封装逻辑见《JDBC——概述与JDBC的使用》):
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JDBCUtils {
public static Connection getConnection() {
Connection connection = null;
try {
// 默认的识别路径就是 src 目录下
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties props = new Properties();
props.load(is);
String url = props.getProperty("url");
String username = props.getProperty("username");
String password = props.getProperty("password");
String driverName = props.getProperty("driverName");
// 加载驱动类
Class.forName(driverName);
// 获取连接
connection = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
public static void closeResource(Connection conn, Statement statement, ResultSet rs) {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (statement != null) {
statement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
二、查询一条记录
public static <T> T selectOne(Class<T> clazz, String sql, Object... args) {
Connection connection = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 获取数据连接
connection = JDBCUtils.getConnection();
// 获取预编译语句对象
ps = connection.prepareStatement(sql);
// 填充属性值,注意下标从 1 开始
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 执行查询,获取结果集
rs = ps.executeQuery();
// 获取结果集的元数据:ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
// 通过元数据获取结果集中的列数
int columnCount = rsmd.getColumnCount();
// rs.next()方法判断是否存在下一条,相当于集合迭代器的 hasNext()
if (rs.next()) {
// 实体类必须包含空参构造器,才可以正常执行 newInstance()
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
// 获取别名(getColumnName()是获取列名,不建议使用)
// 下标同样是从 1 开始
String columnLabel = rsmd.getColumnLabel(i + 1);
// 获取列值
Object columnVal = rs.getObject(i + 1);
// 通过反射封装对象
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnVal);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection, ps, rs);
}
return null;
}
三、查询集合
将 if (rs.next()) 换成 while(rs.next()) 即可,并通过构造一个 List 存储取得的对象元素。
总结
只要掌握了获取一条数据的通用方法,列表的处理逻辑就不在话下了。
需要关注一些重点:
1、PreparedStatement 填充占位符时,下标都是从 1 开始,通用的情况往往不知道填充的参数是何种类型,通过 setObject(..) 即可
2、与增删改不同的是,执行查询时,我们需要使用 executeQuery() 方法,并接收返回的 ResultSet 对象
3、ResultSet 并没有直接存储返回列的数量,我们需要获取到 ResultSetMetaData 获取列的数量,以及列的别名
4、rs.next() 实例方法用于判断结果集中是否存在下一条数据,它的作用相当于集合迭代器中的 hasNext() ,同时带有指针下移的操作
5、在使用 clazz.newInstance() 方法获取对象时,一定要记得实体类必须要有空参构造器
6、使用反射方法封装对象是唯一通用的方法,这样才能保证不论我们封装的是何种类型,都可以在运行时获取到对应的属性信息