手把手教你寫一個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來做好一點。

    完了

    posted @ 2019-01-28 14:11  何白白  閱讀( 1734)  評論( 0編輯  收藏


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM