最近在复习jdbc一些东西,感觉用多了spring全家桶之后只知道怎么使用它封装好的注解,却忘了一些基本的东西,好像变成了spring程序员而不是java程序员了,然而在复习jdbc时发现有些经常重复的操作没法省略,所以想了个办法把它简化,我知道现在市面上使用的如JdbcTemplate和Mybatis的工具都能很好的解决这些问题,但我就是想自己写一个简单的工具,通过反射和泛型去解决这一麻烦。当然了,实际开发中肯定还是用mybatis跟mp,这个解决方法只不过是一个小实践,尝试去手写一个简易版JdbcTemplate而已。
先来看看原生jdbc的操作:
public class TestJDBC {
Connection connection;
@Before
public void before(){
try {
Class.forName("com.mysql.cj.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql:///db_test","root", "root");
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testJdbc() throws SQLException {
PreparedStatement ps = connection.prepareStatement("select * from t_user where id = ?");
ps.setString(1,"1");
//...有多少个占位符set多少次
//dql用executeQuery,dml用execute
ResultSet rs = ps.executeQuery();//ps.execute
User user = new User();
while (rs.next()){
user.setId(rs.getLong("id"));
user.setUsername(rs.getString("username"));
//...有多少个字段就要set多少次,同理rs就要get多少次
}
//return user;
}
}
里面会出现大量的ps.setxxx方法和执行dql时操作rs的重复代码,并且当我要获取另一个实体类时,又要重新写一个方法去set属性又rs.get属性,所以我写了一个类似于JdbcTemplate的东西,可以在查询单条数据和多条数据时复用。
首先是写了一个方法能通过反射获取该类所有成员变量,并找到他的set属性,再通过成员变量名找到结果集中对应字段给实体类附上值,当然,这样子的话数据库字段名必须跟实体类的成员变量名一致,否则会赋值失败。
这里稍微说下获取set属性的方式,个人感觉还有其他更好的方法,毕竟getDeclaredMethods会把equals、toString等方法一起获取,减慢了效率。但是没有去深究。
privateT autoInject(Class clazz,ResultSet rs,Object instance) throws SQLException, InvocationTargetException, IllegalAccessException { //反射获取所有声明的成员变量 Field[] declaredFields = clazz.getDeclaredFields(); //反射获取所有声明的get,set方法 Method[] declaredMethods = clazz.getDeclaredMethods(); for (Field field : declaredFields) { String fieldName = field.getName(); Class> type = field.getType(); Date date = null; Object object= null; //获取日期时调用getDate方法,否则后面强转Date会失败,我也不知道为什么 if (type == Date.class){ date = rs.getDate(fieldName); }else { //其他情况调用getObject方法就行 object = rs.getObject(fieldName); } for (Method method : declaredMethods) { String methodNameWithoutGS = method.getName().substring(3); String methodNameOnlyGS = method.getName().substring(0, 3); //如果方法名去掉get和set三个字母后的名字和成员变量名相同,调用set方法为泛型类赋值,并排除get方法 if (methodNameWithoutGS.equalsIgnoreCase(fieldName) && !methodNameOnlyGS.equalsIgnoreCase("get")){ if (type == Date.class){ method.invoke(instance,date); break; } method.invoke(instance,object); break; } } } return (T) instance; }
通过这个方法,就可以为实体类中的所有属性赋值,当然还是那句话,rs(结果集)中的字段名必须跟成员变量名一致。
接下来需要解决ps.setxxx的这个问题,这里通过for循环给它赋值就好了
private PreparedStatement getPS(String sql,Object ...params) throws SQLException {
connection = dataSource.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//填充占位符
for (int i = 0; i < params.length; i++) {
ps.setObject(i+1,params[i]);
}
return ps;
}
最后把他放到查询方法中,这里只写了dql了,dml由于没有结果集的操作,会方便很多。
查询一条数据的通用方法:
publicT queryOne(String sql,Class clazz,Object ...params) throws Exception{ PreparedStatement ps = getPS(sql, params); ResultSet rs = ps.executeQuery(); try { //因为是查询一个结果,返回的对象造外面,如果结果集有多个,仅返回最后一个结果 T emptyInstance = clazz.getDeclaredConstructor().newInstance(); while (rs.next()){ T injectedInstance = autoInject(clazz, rs, emptyInstance); return injectedInstance; } } finally { closeResource(rs,ps); } return null;//查不到结果 }
查询多个结果的:
publicList queryLots(String sql,Class clazz,Object ...params) throws Exception{ List list = new ArrayList<>(); PreparedStatement ps = getPS(sql, params); ResultSet rs = ps.executeQuery(); try { while (rs.next()){ T emptyInstance = clazz.getDeclaredConstructor().newInstance(); T injectedInstance = autoInject(clazz, rs, emptyInstance); list.add(injectedInstance); } return list; } finally { closeResource(rs,ps); } }
虽然跟JdbcTemplate相比,这些功能还有很多不完善或者会出bug的地方,但是感觉自己通过反射和泛型解决掉使用原生jdbc会出现的重复操作感觉还是很好的,虽然没什么实际用处就是了。。
整个类完整代码如下:
public abstract class BaseDao {
protected static Connection connection;
protected static DataSource dataSource;
static {
try (
InputStream is = BaseDao.class.getClassLoader().getResourceAsStream("druid.properties");
){
Properties prop = new Properties();
prop.load(is);
dataSource = DruidDataSourceFactory.createDataSource(prop);
} catch (Exception e) {
e.printStackTrace();
}
}
public T queryOne(String sql,Class clazz,Object ...params) throws Exception{
PreparedStatement ps = getPS(sql, params);
ResultSet rs = ps.executeQuery();
try {
//因为是查询一个结果,返回的对象造外面,如果结果集有多个,仅返回最后一个结果
T emptyInstance = clazz.getDeclaredConstructor().newInstance();
while (rs.next()){
T injectedInstance = autoInject(clazz, rs, emptyInstance);
return injectedInstance;
}
} finally {
closeResource(rs,ps);
}
return null;//查不到结果
}
public List queryLots(String sql,Class clazz,Object ...params) throws Exception{
List list = new ArrayList<>();
PreparedStatement ps = getPS(sql, params);
ResultSet rs = ps.executeQuery();
try {
while (rs.next()){
T emptyInstance = clazz.getDeclaredConstructor().newInstance();
T injectedInstance = autoInject(clazz, rs, emptyInstance);
list.add(injectedInstance);
}
return list;
} finally {
closeResource(rs,ps);
}
}
private PreparedStatement getPS(String sql,Object ...params) throws SQLException {
connection = dataSource.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//填充占位符
for (int i = 0; i < params.length; i++) {
ps.setObject(i+1,params[i]);
}
return ps;
}
private T autoInject(Class clazz,ResultSet rs,Object instance) throws SQLException, InvocationTargetException, IllegalAccessException {
//反射获取所有声明的成员变量
Field[] declaredFields = clazz.getDeclaredFields();
//反射获取所有声明的get,set方法
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Field field : declaredFields) {
String fieldName = field.getName();
Class> type = field.getType();
Date date = null;
Object object= null;
//获取日期时调用getDate方法,否则后面强转Date会失败,我也不知道为什么
if (type == Date.class){
date = rs.getDate(fieldName);
}else {
//其他情况调用getObject方法就行
object = rs.getObject(fieldName);
}
for (Method method : declaredMethods) {
String methodNameWithoutGS = method.getName().substring(3);
String methodNameOnlyGS = method.getName().substring(0, 3);
//如果方法名去掉get和set三个字母后的名字和成员变量名相同,调用set方法为泛型类赋值,并排除get方法
if (methodNameWithoutGS.equalsIgnoreCase(fieldName) && !methodNameOnlyGS.equalsIgnoreCase("get")){
if (type == Date.class){
method.invoke(instance,date);
break;
}
method.invoke(instance,object);
break;
}
}
}
return (T) instance;
}
public void closeResource(ResultSet rs,PreparedStatement ps){
try {
if (connection != null)
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (rs != null)
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (ps != null)
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void closeResource(PreparedStatement ps){
closeResource(null,ps);
}
}



