• 手把手教你写一个java的orm(完)


    生成sql:select

    上一篇讲了怎样生成一个sqlwhere的一部分,之后我们要做事情就简单很多了,就只要像最开始一样的生成各种sql语句就好了,之后只要再加上我们需要的条件,一个完整的sql就顺利的做好了。

    现在我们开始写生成查询语句的sql。一个查询语句大致上是这样的:

    SELECT name, id, create_date, age, mark, status FROM user
    

    这里可以看出来,一个基础的查询语句基本上就是一个 SELECT 后面加上需要查询的字段,跟上 FROM 和要查询的表名称就好了。 最多后面可能需要加上 ORDER BY/GROUP BY/LIMIT ....之类的就好了,因为比较简单,这里就不写了。(太复杂的就直接写sql就好了,我自己不需要这种操作)

    思路

    1. 从之前拿到的映射关系中拿到属性和字段名的映射,然后拼接sql。
    2. 执行sql,并取出结果。
    3. 实例化class,使用反射给class的属性赋值。

    这几步都还是比较好做的,第一步很简单,仿照着之前写的就可以了。因为这里在执行sql的时候,我使用的是JdbcTemplate,这里有一个不大不小的坑,下面我说一下。

    一个不大不小的坑

    这个坑是我在使用我写好的这个项目给公司做报表的时候碰到的。原因是这样,因为数据库中有些字段是datetime类型的,这个字段有时候在表中的值是:0000-00-00 00:00:00,(我也不知道这个值是怎么进去的,但是就是存在/(ㄒoㄒ)/~~)但是这个值是无法转换成为java中的Date类型。所以这里会报错。

    我在这里写了一个继承SpringJdbc中的ColumnMapRowMapper的类,是这样的:

    import org.springframework.jdbc.core.ColumnMapRowMapper;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * 捕获取值的错误
     *
     * @author hjx
     */
    public class PlusColumnMapRowMapper extends ColumnMapRowMapper {
    
        /**
         * 数据库类型为时间时, 如果值为 0000-00-00 00:00:00
         * 会报错,所以重写此方法,返回null
         *
         * @param rs
         * @param index
         * @return
         * @throws SQLException
         */
        @Override
        protected Object getColumnValue(ResultSet rs, int index) throws SQLException {
            Object columnValue = null;
            try {
                columnValue = super.getColumnValue(rs, index);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return columnValue;
    
        }
    }
    

    这个类具体在哪里使用,会在下面说明。

    实现

    现在说一下怎么实现上面的思路,首先因为第一步比较简单,就不写了。我直接从第二步开始。

    1. 执行sql,并取出结果。

      这里我用的是JdbcTemplate的方法,这给我们提供了一个方法:

      <T> List<T> query(String sql, Object[] args, RowMapper<T> rowMapper)
      

      这里前两个参数比较好理解,一个是sql,一个是sql中的参数。第三个是需要传一个接口RowMapper,这个接口具体是干啥的上网一查就知道了~~~

      这里面有一个方法:

      T mapRow(ResultSet rs, int rowNum) throws SQLException
      

      第一个参数是查询的结果,第二个是指现在在第几行结果,返回值是你要返回什么对象。这里我们需要重写这个方法,把查询出的结果转换成为我们需要的对象。我们可以这么写:

      /**
       * 把数据库查询的结果与对象进行转换
       *
       * @param resultSet
       * @param rowNum
       * @return
       * @throws SQLException
       */
      @Override
      public T mapRow(ResultSet resultSet, int rowNum) throws SQLException {
          Map<String, Object> resultMap = columnMapRowMapper.mapRow(resultSet, rowNum);
         。。。。
      

      这个方法中的columnMapRowMapper 就是上面我们写的PlusColumnMapRowMapper,它的作用就是将查询结果第 rowNum 拿出来,并且将结果转换过成为一个 Map<String, Object>。其中:

      key :是表字段名称。

      Object :该字段的值。

      上面写的PlusColumnMapRowMapper主要作用就是在获取值的时候如果发生异常,返回一个null

      在这一步里我们已经拿到了执行sql的结果,现在我们要将结果转换过为我们需要的class。

    2. 将结果转换为class

      在上一步我们拿到了存放结果Map,现在只需要将map遍历一下,然后实例化java对象,根据字段和属性的映射关系使用反射将属性一个个的set进去就好了。现在贴上上一步的完整代码:

      public T mapRow(ResultSet resultSet, int rowNum) throws SQLException {
          Map<String, Object> resultMap = columnMapRowMapper.mapRow(resultSet, rowNum);
          T instance = getInstance(tableClass);
          for (Map.Entry<String, Object> entry : resultMap.entrySet()) {
              //数据库字段名
              String key = entry.getKey();
              if (!columnFieldMapper.containsKey(key)) {
                  continue;
              }
              Field declaredField = columnFieldMapper.get(key);
              if (declaredField == null) {
                  continue;
              }
              //数据库字段值
              Object value = entry.getValue();
              setFieldValue(instance, declaredField, value);
          }
          return instance;
      }
      

      其中 columnFieldMapper 是一个Map<String, Field>key是表的字段个名称。value是对应的class的属性。

      下面是 setFieldValue的具体代码:

      boolean setFieldValue(T t, Field field, Object value) {
          field.setAccessible(true);
          try {
              if (value != null) {
                  field.set(t, value);
                  return true;
              }
          } catch (IllegalAccessException e) {
              e.printStackTrace();
          }
          return false;
      }
      

      这样,就可以将查询出的结果根据映射关系转换成为我们需要的class了。

    其他的

    如果查询需要添加条件的话,可以使用之前讲的 生成条件的工具将条件的sql拼接在这里的sql后面,相应的,where里的参数也要按照顺序添加进数组就好了。

    相同的,如果要添加 ORDER BY/GROUP BY/LIMIT这些东西的话也是一样的操作。主要还是要看自己的代码是怎么设计的了。我自己用的只写了ORDER BYLIMIT 。可以在我的github上找到。地址在这里:https://github.com/hjx601496320/JdbcPlus

    生成sql:delete

    思路

    诶呀, 这个太简单了。不写了哦~~~

    参照我之前写的,分析一下,想一想思路,然后每一步要怎么做,一点一点的就写好了。

    ~~~

    实现

    你自己写咯~~~。

    其他的

    这一篇写的真快~~

    生成sql:update

    最后一部分了,马上就写完了。写东西真的好累啊~~~

    思路

    更新的语句也比较好做,sql后面的条件因为在之前已经写了where这一篇,所以这里就只写sqlwhere左边的一部分。现在还是先分析一下 **update **语句:

    UPDATE user SET name = ? , id = ? , create_date = ? , age = ? , status = ? WHERE id = ?
    

    可以看到的,大体上就是 UPDATE 表名称 SET 字段名称 = ? 这个样子的。(因为现在不写WHERE右边的

    所以具体的思路就是:

    1. 根据映射关系拼装sql。

      这里可能有一个可以选择的地方,就是如果某一个属性的值是null,这时要不要把这个属性更新为null

    2. 拿到要更新的值。

    3. 执行sql。

    实现

    1. 从映射中拿到所有的属性。

      这一步的代码就不放了~~~,和前面写的没有什么区别。

    2. 拿到要更新的属性名称,和值。

      这里我们需要三个参数:

      1:用来标示更新的时候是否需要忽略值是null的属性。 boolean ignoreNull

      2:用来保存需要更新的字段的有序集合。 List updataColumn

      3:保存需要更新的字段的值的有序集合。 List values

      代码是这样的:

      List<String> columnNames = new ArrayList<>(entityTableRowMapper.getColumnNames());
      Map<String, Field> columnFieldMapper = entityTableRowMapper.getColumnFieldMapper();
      List<Object> values = new ArrayList<>();
      for (int i = 0; i < columnNames.size(); i++) {
          String columnName = columnNames.get(i);
          if (!sqlColumns.contains(columnName)) {
              continue;
          }
          Field field = columnFieldMapper.get(columnName);
          Object value = EntityUtils.getValue(entity, field);
          //如果class中的值是null,并且设置忽略null,跳过
          if (ignoreNull && value == null) {
              continue;
          }
          updataColumn.add(columnName);
          values.add(value);
      }
      
    3. 根据拿到的数据拼装sql

      拿到上面需要的数据后,我们还需要拿到表的名称,这一步直接从映射关系中取就好了。下面的是拼装sql的代码:

      StringBuilder sql = new StringBuilder();
      sql.append("UPDATE ").append(getTableName()).append(StringUtils.SPACE);
      sql.append("SET ");
      for (int i = 0; i < updataColumn.size(); i++) {
          String column = updataColumn.get(i);
          if (i == 0) {
              sql.append(StringUtils.append(column, " = ? "));
          } else {
              sql.append(StringUtils.append(", ", column, " = ? "));
          }
      }
      

      这样就好了,大致上是这样的:

      UPDATE user SET name = ? , id = ? , create_date = ? , age = ? , status = ? 
      

      条件的话,用之前写的where生成就好了,where中的值加在集合values的后面就好了。

    4. 执行sql。

      太简单了,就不写了~

    5. 最后

      终于写完了。

      还是说一下,因为代码已经在github上了,所以没有把全部的代码写在上面,主要还是以说明思路为主。另外刚开始写博客,有些可能表达的不是很明白。吃了没文化的亏啊~~~

      这个项目还有很多可以但是还没有实现的功能,比如一些比较复杂的查询,执行函数之类的。我并没去写它。一是不需要,因为这个东西平时主要是做导出报表的时候用的,二是我自己写项目的话压根就不会用到这些东西,能用java写的我都用java写了。数据库嘛,对我来说就存个数据就好了,数据处理上的事情还是交给java来做好一点。

      完了

    6. 相关阅读:
      一起学ORBSLAM2(10)ORBSLAM中的相似变换矩阵的计算
      一起学ORBSLAM2(9)ORBSLAM的PNP解决方案
      一起学ORBSLAM2(8)ORBSLAM的loopclosing
      一起学ORBSLAM2(7)ORBSLAM中的优化问题
      一起学ORBSLAM2(6)ORBSLAM中的特征匹配
      IntelliJ IDEA 2018.2.2远程调试Tomcat的配置方法
      window.location.href 跳转页面时传递参数并且在新页面接收参数
      form.loadRecord(record)后isDirty总是true的解决办法
      ExtJS让被遮盖的窗体显示在最前面以及解决Ext.MessageBox提示框被TabPanel覆盖的方法【转】
      extjs store中数据转换成json
    7. 原文地址:https://www.cnblogs.com/hebaibai/p/10330016.html
    8. Copyright © 2020-2023  润新知