手把手教你寫一個java的orm(完)
生成sql:select
上一篇講了怎樣生成一個sql 中where 的一部分,之後我們要做事情就簡單很多了,就只要像最開始一樣的生成各種sql 語句就好了,之後只要再加上我們需要的條件,一個完整的sql 就順利的做好了。
現在我們開始寫生成查詢語句的sql 。一個查詢語句大致上是這樣的:
SELECT name, id, create_date, age, mark, status FROM user
這裡可以看出來,一個基礎的查詢語句基本上就是一個SELECT 後面加上需要查詢的欄位,跟上FROM 和要查詢的表名稱就好了。 最多後面可能需要加上ORDER BY /GROUP BY /LIMIT ....之類的就好了,因為比較簡單,這裡就不寫了。(太複雜的就直接寫sql就好了,我自己不需要這種操作)
思路
- 從之前拿到的對映關係中拿到屬性和欄位名的對映 ,然後拼接sql。
- 執行sql,並取出結果。
- 例項化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; } }
這個類具體在哪裡使用,會在下面說明。
實現
現在說一下怎麼實現上面的思路,首先因為第一步比較簡單,就不寫了。我直接從第二步開始。
-
執行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。
-
將結果轉換為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 BY 和LIMIT 。可以在我的github上找到。地址在這裡:https://github.com/hjx601496320/JdbcPlus 。
生成sql:delete
思路
誒呀, 這個太簡單了。不寫了哦~~~
參照我之前寫的,分析一下,想一想思路,然後每一步要怎麼做,一點一點的就寫好了。
~~~
實現
你自己寫咯~~~。
其他的
這一篇寫的真快~~
生成sql:update
最後一部分了,馬上就寫完了。寫東西真的好累啊~~~
思路
更新的語句也比較好做,sql後面的條件因為在之前已經寫了where 這一篇,所以這裡就只寫sql 中where 左邊的一部分。現在還是先分析一下update 語句:
UPDATE user SET name = ? , id = ? , create_date = ? , age = ? , status = ? WHERE id = ?
可以看到的,大體上就是UPDATE 表名稱 SET 欄位名稱 = ? 這個樣子的。(因為現在不寫WHERE右邊的 )
所以具體的思路就是:
-
根據對映關係拼裝sql。
這裡可能有一個可以選擇的地方,就是如果某一個屬性的值是null ,這時要不要把這個屬性更新為null 。
-
拿到要更新的值。
-
執行sql。
實現
-
從對映中拿到所有的屬性。
這一步的程式碼就不放了~~~,和前面寫的沒有什麼區別。
-
拿到要更新的屬性名稱,和值。
這裡我們需要三個引數:
1:用來標示更新的時候是否需要忽略值是null 的屬性。 boolean ignoreNull
2:用來儲存需要更新的欄位的有序集合。 List
3:儲存需要更新的欄位的值的有序集合。 List
-
根據拿到的資料拼裝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 的後面就好了。
-
執行sql。
太簡單了,就不寫了~
最後
終於寫完了。
還是說一下,因為程式碼已經在github上了,所以沒有把全部的程式碼寫在上面,主要還是以說明思路為主。另外剛開始寫部落格,有些可能表達的不是很明白。吃了沒文化的虧啊~~~
這個專案還有很多可以但是還沒有實現的功能,比如一些比較複雜的查詢,執行函式之類的。我並沒去寫它。一是不需要,因為這個東西平時主要是做匯出報表的時候用的,二是我自己寫專案的話壓根就不會用到這些東西,能用java寫的我都用java寫了。資料庫嘛,對我來說就存個數據就好了,資料處理上的事情還是交給java來做好一點。