JDBC代码冗余问题
在上一篇文章中封装了连接JDBC的部分,解决了连接性能的问题。但还有一部分问题是关于代码的冗余,我们在连接数据库进行增删改操作的时候,大部分的代码都是一样的,唯一的不同就在于SQL语句以及给SQL语句传递的参数。和增删改操作相比,查询语句需要有返回值(返回查询的结果),而且除了SQL语句和传递的参数之外,查询语句还要处理结果集。
大部分的代码都是相似的,所以我们需要把这部分相似的代码抽取出来。(本次的封装是基于上一篇文章的封装)
封装步骤
Sql语句的写法需要改变:
之前:update atm set account = ? where account = ?;
改变后:update atm set account = #{account} where account = #{account}
为什么要做改变:如果我们按照之前的写法,那么我们调用方法时传递的参数必须要是有序,否则我们没有办法判定哪个参数在前哪个参数在后。而改变sql语句的写法之后,我们通过解析sql语句,把#{}内的字符串解析出来,就能够很清晰的看出每个位置需要传递的参数名
首先先封装一个类SqlAndKey
这个类的目的是为了存储被解析之后的SQL语句以及SQL语句上的参数(#{}内的字符串)
public class SQLAndKey { private StringBuilder sql = new StringBuilder(); //采用List集合是因为List集合是有序的,能够确认参数的顺序 private List<String> keyList = new ArrayList<>(); public SQLAndKey(StringBuilder sql, List<String> keyList){ this.sql = sql; this.keyList = keyList; } public String getSQL(){ return sql.toString(); } public List<String> getKeyList(){ return keyList; } }
封装一个Handler类
该类的目的是为了解析Sql语句以及设置Sql语句中的参数
public class Handler { //==========解析SQL语句============ SQLAndKey parseSQL(String sql){ //newSql是为了拼接sql语句,keyList为了存储键值 StringBuilder newSql = new StringBuilder(); List<String> keyList = new ArrayList<>(); //解析SQL while (true){ //查找#{和}的位置 int left = sql.indexOf("#{"); int right = sql.indexOf("}"); //判断两个符号的位置是否合法 if (left != -1 && right != -1 && left < right){ //截取#{之前的字符串 newSql.append(sql.substring(0, left)); //sql后面追加? newSql.append("?"); keyList.add(sql.substring(left + 2, right)); }else { //证明已经没有#{和}成对出现了 newSql.append(sql); break; } //将}及}之前的全部截取掉 sql = sql.substring(right + 1); } return new SQLAndKey(newSql, keyList); } //==============SQL语句的拼接================== //分别负责map 和 domain类型的拼接 private void setMap(PreparedStatement pstate, Object obj, List<String> keyList) throws SQLException { Map map = (Map) obj; for (int i = 0; i < keyList.size(); i ++){ pstate.setObject(i+1, map.get(keyList.get(i))); } } private void setObject(PreparedStatement pstat, Object obj, List<String> keyList){ try { //找到obj对应的类 Class clazz = obj.getClass(); for (int i = 0; i < keyList.size(); i ++){ //找到key String key = keyList.get(i); //通过反射找到Obj对象中的属性 Field field = clazz.getDeclaredField(key); field.setAccessible(true); //获取私有属性的值 Object value = field.get(obj); pstat.setObject(i + 1, value); } } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } //设计一个方法,负责将SQL和问号组装完整 //参数:pstat对象,Object对象,KeyList全部的Key void handleParameter(PreparedStatement pstat, Object obj, List<String> keyList) throws SQLException { //1、通过反射获取obj对应的class Class clazz = obj.getClass(); //2、判断该clazz是什么类型 if (clazz == int.class || clazz == Integer.class){ pstat.setInt(1, (Integer) obj); }else if (clazz == float.class || clazz == Float.class){ pstat.setFloat(1, (Float) obj); }else if (clazz == double.class || clazz == Double.class){ pstat.setString(1, (String) obj); }else if (clazz == String.class){ pstat.setString(1, (String) obj); }else if (clazz.isArray()){ }else { if (obj instanceof Map){ this.setMap(pstat, obj, keyList); }else { this.setObject(pstat, obj, keyList); } } } //============通过反射获取对象=============== private Map getMap(ResultSet rs) throws SQLException { //1、创建Map Map<String, Object> map = new HashMap<>(); //2、获取结果集中的全部信息 ResultSetMetaData rsmd = rs.getMetaData(); //3、遍历结果集 for (int i = 1; i <= rsmd.getColumnCount(); i ++){ //获取列名 String columnName = rsmd.getColumnName(i); //获取列值 Object value = rsmd.getColumnName(i); //存入map中 map.put(columnName, value); } return map; } private Object getObject(ResultSet rs, Class resultType) throws SQLException { //1、创建Object Object obj = null; try { //2、通过反射创建对象 obj = resultType.newInstance(); //3、获取结果集中的全部信息 ResultSetMetaData rsmd = rs.getMetaData(); //4、遍历结果集 for (int i = 1; i <= rsmd.getColumnCount(); i ++){ //获取列名 String columnName = rsmd.getColumnName(i); //通过反射找到列名字对应的属性 Field field = resultType.getDeclaredField(columnName); //操作私有属性 field.setAccessible(true); //给属性赋值 field.set(obj, rs.getObject(columnName)); } } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } return obj; } public Object handleResult(ResultSet rs, Class resultType) throws SQLException { //1、通过反射创建对象 Object result = null; if (resultType == int.class || resultType == Integer.class){ result = rs.getInt(1); }else if (resultType == float.class || resultType == Float.class){ result = rs.getFloat(1); }else if (resultType == double.class || resultType == Double.class){ result = rs.getDouble(1); } else if (resultType == String.class) { result = rs.getString(1); }else { if (resultType == Map.class){ result = this.getMap(rs); }else { result = this.getObject(rs, resultType); } } return result; } }
封装增删改查的方法
增删改:由于增删改操作很相似,所以我们只需要完成更改的操作,增加和删除的操作只需要调用更改的方法。
//改 //参数:SQL语句,传递的参数 public void update(String sql, Object obj){ try { //1、解析sql语句 SQLAndKey sqlAndKey = handler.parseSQL(sql); //2、获取连接池对象 ConnectionPool pool = ConnectionPool.getInstance(); //3、获取连接对象 Connection conn = pool.getConnection(); //4、获取状态参数 PreparedStatement pstate = conn.prepareStatement(sqlAndKey.getSQL()); //5、将SQL语句和问号值组装完整,调用handler的方法将obj对象替代掉? if (obj != null){ handler.handleParameter(pstate, obj, sqlAndKey.getKeyList()); } pstate.executeUpdate(); pstate.close(); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } //删 public void delete(String sql, Object obj){this.update(sql, obj); } //增 public void insert(String sql, Object obj){this.update(sql, obj); }
- 查找的方法:
查找的方法除了传入sql语句以及参数之外,还需要传入查询之后的返回值的类
```
//查询单条
public <T> T selectOne(String sql, Object obj, Class resultType){
return (T) this.selectList(sql, obj, resultType).get(0);
}
//查询多条
public <T> List<T> selectList(String sql, Object obj, Class resultType){
List<T> list = new ArrayList<>();
try {
//1、解析SQL
SQLAndKey sqlAndKey = handler.parseSQL(sql);
//2、获取连接对象
ConnectionPool pool = ConnectionPool.getInstance();
Connection conn = pool.getConnection();
//3、获取状态参数
PreparedStatement pstat = conn.prepareStatement(sql);
//4、把SQL和问号拼接在一起
if (obj != null){
handler.handleParameter(pstat, obj, sqlAndKey.getKeyList());
}
//5、执行操作
ResultSet rs = pstat.executeQuery();
//6、处理结果
while (rs.next()){
//通过handleResult获取到
T result = (T) handler.handleResult(rs, resultType);
list.add(result);
}
rs.close();
pstat.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
```
- 动态代理
封装完增删改查之后,那么之后我们在执行增删改查的时候只需要调用我们封装的方法,例如:
public void insert(User user){
String sql = "insert into atm values(#{account}, #{password}, #{balance})";
new SqlSession().insert(sql, user);
}
那么其实我们可以去掉方法体的代码,只留下一个方法名。我们通过一个代理对象来实现这些方法,我们用SqlSession类来定义一个方法,具体的方法代码上面已经粘贴出来了。通过这个方法我们可以获得一个代理对象来操作实际的dao类。那么之后我们在写增删改查的时候,只需要写sql语句,就能够实现。(实现的方式是通过注解),比如如下的代码:
public interface Dao {
@Insert("insert into atm values(#{account}, #{password}, #{balance})")
void insert(User user);
@Delete("delete from atm where account = #{account}")
void delete(String account);
@Update("update atm set account=#{account}, password=#{password}, balance=#{balance} where account=#{account}")
void update(String account);
@Select("SELECT *FROM ATM WHERE ACCOUNT = ?")
User selectOne(String account);
@Select("SELECT *FROM ATM")
List<User> selectList();
}
具体调用
当我们需要对数据库进行操作的时候,只需要获取代理对象,调用代理对象的方法就能够实现,封装了JDBC冗余的代码,具体调用如下:public class TestMain { public static void main(String[] args) { Dao dao = new SqlSession().getMapper(Dao.class); dao.delete("2024"); } }
因为整个封装的代码量比较多,该篇文章也主要是为了记录自己的学习过程,所以有一些地方可能说得不到位,如果有问题都可以留言提出来
整个JDBC封装的代码:https://github.com/Cing-self/...
使用的时候记得改一下configuration.properties配置文件
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。