使用過幾個ORM框架,都感受並不合適我使用,SpringData與Hibernate雖然強大,可是太多功能我平常工做或者學習上並不須要,且在調優問題上有必定的阻礙,而MyBatis又須要把sql寫到xml文件或者註解裏面,而我更喜歡sql直接寫到代碼裏面用Java代碼寫邏輯控制sql語句。就想着封裝一個簡單方便適合本身平常學習使用的ORM框架。相比直接封裝原生的JDBC,Spring提供的JdbcTemplate工具類已經提供了一條捷徑,因此此次簡單封裝一下JdbcTemplate,使平時學習能更加順手sql
先展現一下使用(下面用的都是main函數測試,由於不想引入太多依賴致使後面我學習使用時還要再刪除):數據庫
下面是上面調用的testSave()函數app
下面是上面調用的save()函數框架
save()執行完之後數據庫多了條數據而且把執行的sql打印出來了(打印用的是System.out.println打印,上圖截完才加上的),個人數據庫id是自增的,因此插入的時候沒有設置值,又把我剛封裝好時測試的數據刪除了,因此就從11開始函數
下面內容僅用於記錄封裝,不作太詳細說明工具
一,映射註解學習
ORM框架都是由對象與數據映射關係組成,爲了標明某個對象映射某個表的,對象的字段對應表的哪一個字段,因此先寫了2個註解 @Column 與 @Table測試
@Table僅用於註明是映射哪一個表this
@Column用於註明映射哪一個字段,及是否爲主鍵spa
/** * 用於標識類對應數據庫的表 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Table { String value() default ""; }
/** * 用於標識字段對應數據庫的列名稱 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Column { String value() default ""; boolean isKey() default false; }
下面是使用方式,getXxx()和setXxx()函數就不復製出來了,另外最後一個test字段沒使用@Column則不進行映射關係,其餘用到註解的地方都說明了對應的是哪一個表,哪一個字段,若是是聯合主鍵則多字段使用 isKey=true
@Table("sys_user") public class User implements Serializable { public final static String TABLE_USER="sys_user"; @Column(value = "id",isKey = true) private Long id; @Column("user_name") private String userName; @Column("password") private String password; @Column("nick_name") private String nickName; @Column("gender") private Integer gender; @Column("mobile") private String mobile; @Column("email") private String email; private String test;
二,根據映射對象生成DML語句
編寫了一個DaoUtils的泛型類,這樣就能夠根據各類對象動態生成sql
裏面有13個函數(一個main方法,前期測試使用),1個屬性
table屬性:主要用於存儲經過analysisTable(T t)函數反射獲取到的表信息與字段信息
/** * 儲存表信息與字段信息 */ private Map<String,Object> table;
analysisTable(T t):經過反射獲取到的表信息和字段信息
下面是table屬性儲存的屬性
tableName:存儲表名
tableColumns:儲存表字段信息
keyColumns:儲存主鍵信息
/** * 根據對象獲取表信息,表名,字段名,字段值 * @return * @throws Exception */ private Map<String,Object> analysisTable(T t) throws DtoAnalysisTableException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class<?> clazz = t.getClass(); // 對象全部的信息集合,表,字段,字段值 Map<String,Object> result = new HashMap<String,Object>(); // 儲存字段信息 Map<String,Object> fieldMap = new TreeMap<String, Object>(); // 儲存主鍵信息 Map<String,Object> keyMap = new TreeMap<String, Object>(); // 獲取類名,類的Table註解 Table table = clazz.getAnnotation(Table.class); if(table == null) { throw new DtoAnalysisTableException("Object not has Table of Annotation"); } // 獲取全部字段名稱 Field[] fields = clazz.getDeclaredFields(); // 遍歷每個字段字段的信息 for (Field field: fields) { // 獲取字段映射信息 Column annotation = field.getAnnotation(Column.class); // 當存在映射信息時候才進行儲存 if(annotation != null){ // 獲取getXxx()函數 Method getMethod = clazz.getDeclaredMethod(MethodUtils.getMethod(field.getName())); // 根據getXxx函數獲取值 Object invoke = getMethod.invoke(t); // 儲存該數據庫字段對應的值 fieldMap.put(annotation.value(), invoke); // 判斷是否主鍵,若是是則存儲到主鍵信息 if(annotation.isKey()){ keyMap.put(annotation.value(),invoke); } } } // 添加表名 result.put("tableName", table.value()); // 添加字段信息 result.put("tableColumns", fieldMap); // 添加主鍵字段信息 result.put("keyColumns",keyMap); return result; }
上面用到的自定義類 MethodUtils 與自定義異常 DtoAnalysisTableException
MethodUtils 主要提供了根據字段名獲取getXxx()和setXxx()的函數
/** * 傳入一個字段名,獲取該字段的 * getXxx() * @param fieldName * @return */ public static String getMethod(String fieldName){ if(fieldName == null || fieldName.length() < 1){ return ""; } return "get"+fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); }
/** * 傳入一個字段名,獲取該字段的 * setXxx() * @param fieldName * @return */ public static String setMethod(String fieldName){ if(fieldName == null || fieldName.length() < 1){ return ""; } return "set"+fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); }
DtoAnalysisTableException這是該類沒有表映射關係的異常
/** * 數據庫映射異常 */ public class DtoAnalysisTableException extends Exception{ public DtoAnalysisTableException(String message) { super(message); } }
接着說回DaoUtils的函數
getTable(T t) 是獲取 table的函數,避免同一DaoUtils對象屢次調用analysisTable(T t)浪費資源
/**
* 用於獲取類映射的表信息
* @param t
* @return
*/
private Map<String, Object> getTable(T t) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException {
if(table == null) {
table = analysisTable(t);
}
return table;
}
insertCols() 與 updateCols()則是獲取的 sql 的部分語句 例以下面圖片紅圈出:
/** * 根據getTable()返回的集合 tableColumns 獲取持久化對象的屬性名字符串 * @param colMap * @return */ private String insertCols(Map<String,Object> colMap){ StringBuffer insertCols = new StringBuffer(""); // 獲取key(表字段名) Set<String> keySet = colMap.keySet(); // 遍歷key獲取對應的數據庫,拼接部分sql語句,例如:" fieldName1,fieldName2,fieldName3 " for (String col : keySet) { if(insertCols.length() > 0){ insertCols.append(","); } insertCols.append(col); } return insertCols.toString(); } /** * 根據getTable()返回的集合 tableColumns 獲取持久化對象的屬性名字符串 * @param colMap * @return */ private String updateCols(Map<String,Object> colMap){ StringBuffer insertCols = new StringBuffer(""); // 獲取key(表字段名) Set<String> keySet = colMap.keySet(); // 遍歷key獲取對應的數據庫,拼接部分sql語句,例如:"fieldName1=?,fieldName2=?,fieldName3=? " for (String col : keySet) { if(insertCols.length() > 0){ insertCols.append(","); } insertCols.append(col).append("=?"); } return insertCols.toString(); }
下面的 insertSql(T t) 、insertBatchSql(T t,int size)、updateSql(T t)、deleteSql(T t) 則是獲取插入、批量插入、根據主鍵修改、根據主鍵刪除的SQL
/** * 根據表信息,獲取insert sql語句 * @return */ public String insertSql(T t) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException { // 反射獲取實體內容 Map<String, Object> tableByDto = getTable(t); // 獲取表名 String tableName = tableByDto.get("tableName")+""; // 獲取表字段 Map<String, Object> colMap = (Map<String, Object>) tableByDto.get("tableColumns"); // 拼接sql StringBuffer sql = new StringBuffer(" INSERT INTO "); // 獲取 "" 這樣的內容並拼接 sql.append(tableName).append(" (").append(insertCols(colMap)).append(") "); // 獲取 "?,?,?,?,?" 這樣的內容並拼接 sql.append(" VALUES ").append(" (").append(insertParam(colMap)).append(") "); return sql.toString(); } /** * 根據表信息,批量獲取insert sql語句 * @param size 批量的數量 * @return */ public String insertBatchSql(T t,int size) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException { // 反射獲取實體內容 Map<String, Object> tableByDto = getTable(t); // 獲取表字段 Map<String, Object> colMap = (Map<String, Object>) tableByDto.get("tableColumns"); StringBuffer sql = new StringBuffer(insertSql(t)); // 獲取 "?,?,?,?,?" 這樣的內容 String insertParam = insertParam(colMap); // 拼接sql for (int i = 0 ; i < size - 1; i++){ sql.append(" , (").append(insertParam).append(") "); } return sql.toString(); } /** * 根據表信息,獲取update sql語句 * @return */ public String updateSql(T t) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException { // 反射獲取實體內容 Map<String, Object> tableByDto = getTable(t); // 獲取表字段 Map<String, Object> colMap = (Map<String, Object>) tableByDto.get("tableColumns"); // 獲取表主鍵 Map<String, Object> keyMap = (Map<String, Object>) tableByDto.get("keyColumns"); // 獲取標名 String tableName = tableByDto.get("tableName")+""; Set<String> keys = keyMap.keySet(); // 拼接UPDATE SQL StringBuffer sql = new StringBuffer(" UPDATE "); sql.append(tableName).append(" set ").append(updateCols(colMap)).append(" "); sql.append(" where "); int i = 0; for (String key : keys) { sql.append(key).append("=? ").append((i++ < keys.size()-1) ? " and " : ""); } return sql.toString(); } /** * 根據表信息,獲取根據主鍵delete sql語句 * @return */ public String deleteSql(T t) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException { // 反射獲取實體內容 Map<String, Object> tableByDto = getTable(t); // 獲取表名 String tableName = tableByDto.get("tableName")+""; // 獲取主鍵 Map<String, Object> keyMap = (Map<String, Object>) tableByDto.get("keyColumns"); Set<String> keys = keyMap.keySet(); // 拼接sql StringBuffer sql = new StringBuffer(" DELETE from "); sql.append(tableName).append(" WHERE "); int i = 0; for (String key : keys) { sql.append(key).append("=? ").append((i++ < keys.size()-1) ? " and " : ""); } return sql.toString(); }
上面有使用到 insertParam() 函數 主要是用於獲取插入語句的 「?,?,?,?」 這樣的內容
/** * 獲取參數字符串 格式:?,?,?,? * @param colMap * @return */ private String insertParam(Map<String,Object> colMap){ StringBuffer param = new StringBuffer(""); for (int i = 0;i < colMap.size(); i++){ param.append("?"); if(i < colMap.size()-1){ param.append(","); } } return param.toString(); }
再接下來是獲取使用 JdbcTemplate 的接口時候須要使用的參數值。insertValues(T t)、insertBatchValues(List<T> list)、updateValues(T t)、deleteValues(T t) 對應的分別是獲取插入、批量插入、根據主鍵修改和根據主鍵刪除的函數
/** * 根據getTable()返回的集合 tableColumns 獲取對象的屬性值 * @param t * @return */ public Object[] insertValues(T t) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException { // 獲取表信息 Map<String, Object> table = getTable(t); // 獲取表字段 Map<String, Object> colMap = (Map<String, Object>) table.get("tableColumns"); Object[] values = new Object[colMap.size()]; // 根據字段名獲取值 Set<String> keySet = colMap.keySet(); int i = 0; for (String col : keySet) { values[i++] = colMap.get(col); } return values; } /** * 根據getTable()返回的集合 tableColumns 獲取對象的屬性值 * @return */ public Object[] insertBatchValues(List<T> list) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException { // 用於儲存全部參數 List<Object> values = new ArrayList<Object>(); // 變量獲取參數 for (int i = 0;i < list.size(); i++) { // 調用insertValues獲取該對象的參數值 Object[] objects = insertValues(list.get(i)); // 把參數添加到列表 values.addAll(Arrays.asList(objects)); } return values.toArray(); } /** * 根據getTable()返回的集合 tableColumns 獲取對象的屬性值 * @param t * @return */ public Object[] updateValues(T t) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException { // 獲取表信息 Map<String, Object> table = getTable(t); // 獲取全部表字段 Map<String, Object> colMap = (Map<String, Object>) table.get("tableColumns"); // 獲取主鍵字段 Map<String, Object> keyMap = (Map<String, Object>) table.get("keyColumns"); List<Object> values = new ArrayList<Object>(); // 獲取全部字段 Set<String> keySet = colMap.keySet(); // 根據key獲取對應的值 for (String col : keySet) { values.add(colMap.get(col)); } // 獲取主鍵字段 Set<String> keys = keyMap.keySet(); // 根據key獲取對應的值 for (String key : keys) { values.add(colMap.get(key)); } return values.toArray(); } /** * 根據getTable()返回的集合 tableColumns 獲取對象的屬性值 * @param t * @return */ public Object[] deleteValues(T t) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException { // 獲取表信息 Map<String, Object> table = getTable(t); // 獲取字段信息 Map<String, Object> colMap = (Map<String, Object>) table.get("tableColumns"); // 獲取主鍵信息 Map<String, Object> keyMap = (Map<String, Object>) table.get("keyColumns"); // 獲取key(表屬性) Set<String> keys = keyMap.keySet(); List<Object> values = new ArrayList<Object>(); // 根據key獲取對應的值 for (String key : keys) { values.add(colMap.get(key)); } return values.toArray(); }
對於這個類獲取SQL的測試,使用main()調用
// 測試使用
public static void main(String[] args) {
try{
User user = new User();
user.setUserName("11111");
user.setPassword("fdsfds");
user.setMobile("137");
user.setEmail("email");
// 添加sql
String insertSql = new DaoUtils<User>().insertSql(user);
System.out.println(insertSql);
// 修改sql
String updateSql = new DaoUtils<User>().updateSql(user);
System.out.println(updateSql);
// 修改sql
String deleteSql = new DaoUtils<User>().deleteSql(user);
System.out.println(deleteSql);
// 批量添加sql
String insertBatchSql = new DaoUtils<User>().insertBatchSql(user,3);
System.out.println(insertBatchSql);
} catch (Exception e){
e.printStackTrace();
}
}
打印結果:
下面就是封裝好的 BaseDao
public class BaseDao<T> { protected JdbcTemplate jdbcTemplate; /** * 插入函數 * @param t */ public void save(T t) throws InvocationTargetException, NoSuchMethodException, DtoAnalysisTableException, IllegalAccessException { // 創建Util對象 DaoUtils<T> du = new DaoUtils<T>(); // 獲取sql語句 String s = du.insertSql(t); // 獲取sql參數 Object[] params = du.insertValues(t); // 調用JdbcTemplate 執行 jdbcTemplate.update(s,params); } /** * 修改函數 * @param t */ public void update(T t) throws InvocationTargetException, NoSuchMethodException, DtoAnalysisTableException, IllegalAccessException { // 創建Util對象 DaoUtils<T> du = new DaoUtils<T>(); // 獲取sql語句 String s = du.updateSql(t); // 獲取sql參數 Object[] params = du.updateValues(t); // 調用JdbcTemplate 執行 jdbcTemplate.update(s,params); } /** * 刪除函數 * @param t */ public void delete(T t) throws InvocationTargetException, NoSuchMethodException, DtoAnalysisTableException, IllegalAccessException { // 創建Util對象 DaoUtils<T> du = new DaoUtils<T>(); // 獲取sql語句 String s = du.deleteSql(t); // 獲取sql參數 Object[] params = du.deleteValues(t); // 調用JdbcTemplate 執行 jdbcTemplate.update(s,params); } /** * 批量插入函數 * @param list */ public void batchSave(List<T> list) throws InvocationTargetException, NoSuchMethodException, DtoAnalysisTableException, IllegalAccessException { // 創建Util對象 DaoUtils<T> du = new DaoUtils<T>(); // 獲取sql語句 String s = du.insertBatchSql(list.get(0), list.size()); // 獲取sql參數 Object[] params = du.insertBatchValues(list); // 調用JdbcTemplate 執行 jdbcTemplate.update(s,params); } // 測試代碼 public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; } public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }
固然,最開始只是由於測試,因此直接使用的BaseDao,實際寫一個UserDao再繼承BaseDao使用更方便,這樣可使用增刪改的工具函數,也能定義屬於User類本身的sql例如:
public class UserDao extends BaseDao<User> { public List<User> list(){ return jdbcTemplate.query(" select * from " + User.TABLE_USER , new Object[]{},new LocalRowMapper<User>(User.class)); } }
上面BaseDao的增刪改工具函數就再也不進行測試了
三,下面開始說說查詢
JdbcTemplate 原生的query()是不支持類字段和表字段映射規則不同的查詢操做,例如:數據庫字段 user_name 與 類屬性 userName 這2個能夠映射到,query時會根據下劃線"_"的位置進行駝峯命名,可是若是遇到 user_name 與 userName001 這樣的字段,則不能經過映射,下面測試一下
這是表的內容
先測試用JdbcTemplate提供的 BeanPropertyRowMapper 執行
查詢list函數
測試函數
main()
看看執行內容:證實若是是user_name和userName可以映射
再把咱們上面的User類代碼的userName改爲userName1,
main()執行結果,能夠看到userName1的字段是null,並無映射到:
下面再改爲我本身重寫RowMapper後的 LocalRowMapper,其餘代碼不變,把BeanPropertyRowMapper 改爲 LocalRowMapper
執行結果,換成了LocalRowMapper以後userName1與user_name也能映射到,userName與user_name更不用說了:
userName1
userName
下面看看 LocalRowMapper 類
/** * 重寫RowMapper,使它能夠使用@Table 與 @Column * @param <T> */ public class LocalRowMapper<T> implements RowMapper<T>{ private Class<?> clazz; // 保存字段與數據庫字段的映射關系 private Map<String,String> fieldMap; // 保存字段與setXxx()函數的參數類型 private Map<String,Class> fieldTypeMap; public LocalRowMapper(Class<?> clazz){ try { // 設置calss this.clazz = clazz; // 初始化 fieldMap this.fieldMap = new HashMap<String, String>(); // 初始化 fieldTypeMap this.fieldTypeMap = new HashMap<String, Class>(); this.analysisTable(); } catch (Exception e) { e.printStackTrace(); } } /** * 重寫 RowMapper 的 mapRow() 函數 * @param rs * @param arg * @return */ public T mapRow(ResultSet rs, int arg) { T t= null; try { // 根據class 實例化對象 t = (T)clazz.newInstance(); // 獲取與數據庫映射的字段 Set<String> keySet = this.fieldMap.keySet(); for (String key: keySet) { // 獲取setXxx() Method setMethod = clazz.getDeclaredMethod(MethodUtils.setMethod(key),this.fieldTypeMap.get(key)); // 根據字段名稱獲取ResultSet返回字段所在下標 // 通過下標獲取到對於字段的值 // 執行setXxx() 把值設置到對象 setMethod.invoke(t,rs.getObject(rs.findColumn(this.fieldMap.get(key)))); } } catch (Exception e) { e.printStackTrace(); } return t; } /** * 獲取字段映射信息函數 * @throws DtoAnalysisTableException * @throws NoSuchMethodException * @throws InvocationTargetException * @throws IllegalAccessException */ private void analysisTable() throws DtoAnalysisTableException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class<?> clazz = this.clazz; // 獲取全部字段名稱 Field[] fields = clazz.getDeclaredFields(); // 遍歷每個字段字段的信息 for (Field field: fields) { Column annotation = field.getAnnotation(Column.class); if(annotation != null){ // 保存字段與數據庫字段 this.fieldMap.put(field.getName(), annotation.value()); // 保存字段與setXxx()函數的參數類型 this.fieldTypeMap.put(field.getName(),field.getType()); } } } }
以上就是對此次 JdbcTemplate 封裝的全部內容了,下面是源碼連接(OSCHINA不能上傳附件只能放雲盤了)
https://pan.baidu.com/s/1e0DsZKP_D9dWVcZ4TRayHQ
提取碼:awip