关于java:用Java手动封装JDBC二

39次阅读

共计 6841 个字符,预计需要花费 18 分钟才能阅读完成。

JDBC 代码冗余问题

在上一篇文章中封装了连贯 JDBC 的局部,解决了连贯性能的问题。但还有一部分问题是对于代码的冗余,咱们在连贯数据库进行增删改操作的时候,大部分的代码都是一样的,惟一的不同就在于 SQL 语句以及给 SQL 语句传递的参数。和增删改操作相比,查问语句须要有返回值(返回查问的后果),而且除了 SQL 语句和传递的参数之外,查问语句还要处理结果集。
大部分的代码都是类似的,所以咱们须要把这部分类似的代码抽取进去。(本次的封装是基于上一篇文章的封装)

封装步骤

Sql 语句的写法须要扭转:
之前:update atm set account = ? where account = ?;
扭转后:update atm set account = #{account} where account = #{account}
为什么要做扭转:如果咱们依照之前的写法,那么咱们调用办法时传递的参数必须要是有序,否则咱们没有方法断定哪个参数在前哪个参数在后。而扭转 sql 语句的写法之后,咱们通过解析 sql 语句,把 #{}内的字符串解析进去,就可能很清晰的看出每个地位须要传递的参数名

  1. 首先先封装一个类 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;}
        }
  2. 封装一个 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;
         }
     }
  3. 封装增删改查的办法

    • 增删改:因为增删改操作很类似,所以咱们只须要实现更改的操作,减少和删除的操作只须要调用更改的办法。

       // 改
          // 参数: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;
    }
```
  1. 动静代理

封装完增删改查之后,那么之后咱们在执行增删改查的时候只须要调用咱们封装的办法,例如:

 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();}
  1. 具体调用
    当咱们须要对数据库进行操作的时候,只须要获取代理对象,调用代理对象的办法就可能实现,封装了 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 配置文件

正文完
 0