- 代碼位址
- 寫出你自己的ORM架構(一)
- 寫出你自己的ORM架構(二)
- 寫出你自己的ORM架構(三)
- 寫出你自己的ORM架構(四)
一、完成query接口
query是一個接口,是因為每一個資料庫的實作方法都不樣,是以定義為接口,我們這裡就隻完成mysql的普通CRUD即可!
query接口中的方法如下:
package cn.gxm.sorm.core;
import cn.gxm.sorm.bean.TableInfo;
import java.util.List;
import java.util.function.Function;
/**
* @author GXM www.guokangjie.cn
* @date 2019/5/15
*
* 操作資料庫的核心接口
*/
public interface Query/*<T extends TableInfo>*/ {
/**
* 直接執行一個sql語句
* @param sql 需要執行的sql語句,參數用 ? 代替
* @param params sql語句的參數,用于替換 ?
* @return 執行sql語句影響的行數
*/
int executeDML(String sql,Object[] params);
/**
* 向資料庫中插入一條資料
* @param table 即java中的pojo對應的資料庫中的表名稱
*/
void insert(Object table);
/**
* 根據 id 删除資料
* @param clazz 資料庫中表對應的java對象
* @param primaryKey 主鍵
* delete from User where id = ?
*/
void delete(Class clazz,Object primaryKey);
/**
* 根據與資料庫對應的表的對象來删除
* 根據主鍵删除
* @param table
*/
void delete(Object table);
/**
* 跟新表資料
* @param table 資料庫中表對應的java對象
* @param params 跟新的數值
* @return 影響的條數
* update User set name='' where id = ''
*/
int update(Object table,String[] params);
/**
* 指定查詢多條記錄,并将記錄封裝到指定的class對象中
* @param sql 需要執行查詢的sql語句
* @param clazz 查詢結果封裝的對象
* @param params sql語句的參數
* @return 将封裝的class對象變為集合傳回
* select id,name from User where name like ''
*/
List queryRows(String sql,Class clazz,String[] params);
/**
* 指定查詢多條記錄
* @param sql 需要執行查詢的sql語句
* @param params sql語句的參數
* @return 将封裝的class對象傳回
*/
Object queryOne(String sql,String[] params);
/**
* 查詢數值
* @param sql 需要執行查詢的sql語句
* @param params sql語句的參數
* @return 傳回查詢的數值
* select count(*) from User where id = ''
*/
Number queryNumber(String sql,String[] params);
}
二 、完成 executeDML
其中executeDML()為底層的代碼,用于執行拼接的sql語句!
在這裡我們将使用jdbc原始的技術完成sql語句的執行,既然執行原始的jdbc語句那麼有一點大家一定知道,關閉的問題,以及異常的捕捉,很煩人,是以我們封裝到JDBCUtils中處理。而且處理preparedStatement傳參問題也很通用,是以我們也封裝成handleParams方法
- JDBCUtils中的close方法代碼如下:
/**
* 處理jdbc關閉問題
* @param preparedStatement PreparedStatement對象
* @param connection Connection 對象
*/
public static void close(PreparedStatement preparedStatement,
Connection connection){
// 這裡關閉的順序需要注意一下,還有最重要的就是不能放在同一個try代碼塊裡處理
// 因為一旦前面的關閉錯誤,後面的會無法關閉
try {
if(preparedStatement!=null){
preparedStatement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
- JDBCUtils中的handleParams方法代碼如下:
/**
* 處理執行sql式preparedStatement的?傳參問題
* @param preparedStatement PreparedStatement對象
* @param params 參數值數組對象
*/
public static void handleParams(PreparedStatement preparedStatement, Object[] params){
// 注意PreparedStatement設定下表是從1開始的
for (int i =0;i<params.length;i++){
try {
preparedStatement.setObject(i+1,params[i]);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
- 完整的executeDML方法就是如下了
@Override
public int executeDML(String sql, Object[] params) {
PreparedStatement preparedStatement = null;
Connection connection = DBManager.getConnection();
try {
preparedStatement = connection.prepareStatement(sql);
JDBCUtils.handleParams(preparedStatement,params);
return preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.close(preparedStatement,connection);
}
return 0;
}
三 、完成MysqlQuery的删除功能
3.1 定義clazzAllTable
因為我們需要根據表來關聯資料庫中的表,是以我們之前在TableContext類中定義了allTables
/**
* 對應資料庫中所有的表
* key 為表的名稱, value為表對應的bean
*/
private static Map<String, TableInfo> allTables = new HashMap<>();
但是我們根據指定的規則使用名稱來找,可能會出現問題,是以我們這裡再定義個Map,根據clazz來找,這下絕對不會出錯,定義如下
/**
* 将生成的pojo用class與資料庫中的表關聯起來
*/
private static Map<Class ,TableInfo> clazzAllTable = new HashMap<>();
填充clazzAllTable其實與allTables差不多,隻不過,一個Key是表的名稱,一個是表對應的pojo的class對象,實作如下
//配置clazzAllTales
Class<?> clazz = Class.forName(DBManager.getConfiguration().getPojoPackage() + "." + StringUtils.toUpperCaseHeadChar(tableName));
clazzAllTable.put(clazz,tableInfo);
3.2 接下來完成根據主鍵删除,根據主鍵删除的有二個重載方法
- public void delete(Class clazz, Object primaryKey)
@Override
public void delete(Class clazz, Object primaryKey) {
//根據class找到資料庫中對應的表(這裡不能根據類名來直接得出表名)
//因為你之後還要擷取主鍵
TableInfo tableInfo = TableContext.getClazzAllTable().get(clazz);
//拼接sql,暫時不處理聯合主鍵
String sql = "delete from "+tableInfo.getName()+" where "+tableInfo.getOnlyPriKey().getName()+" =?";
executeDML(sql,new Object[]{primaryKey});
}
-
public void delete(Object table)
這個方法主鍵的值并沒有顯式的展示出來,在傳入的對象中,但是我們無法直接調用get方法擷取,該屬性的值,因為傳入是object,是以我們可以根據主鍵的名稱拼接該屬性的get方法,通過反射調用即可
反射方法定義實作如下
/**
* 根據屬性名稱調用其get方法
* @param object 該屬性屬于那個對象
* @param fieldName 屬性的名稱
* @return 調用該屬性的get方法的傳回值
*/
public static Object getMethodResult(Object object,
String fieldName){
try {
Method method = object.getClass().getDeclaredMethod("get" + StringUtils.toUpperCaseHeadChar(fieldName),null);
return method.invoke(object, null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
完成該方法,代碼如下
@Override
public void delete(Object table) {
Class<?> clazz = table.getClass();
// 擷取主鍵
TableInfo tableInfo = TableContext.getClazzAllTable().get(clazz);
ColumnInfo onlyPriKey = tableInfo.getOnlyPriKey();
//通過反射的值擷取主鍵的值(這裡無法調用table的get方法,因為你不知道具體的對象)
Object methodResult = ReflectUtils.getMethodResult(table, onlyPriKey.getName());
//拼接sql,暫時不處理聯合主鍵
String sql = "delete from "+tableInfo.getName()+" where "+onlyPriKey.getName()+" =?";
executeDML(sql,new Object[]{methodResult});
}
四 、完成增加功能
增加功能就很簡單了,拼接好sql語句後,直接交給executeDML方法去執行就可以了
代碼如下:
@Override
public void insert(Object table) {
Class<?> clazz = table.getClass();
// 擷取該對象的表資訊
TableInfo tableInfo = TableContext.getClazzAllTable().get(clazz);
//我們這裡處理主鍵是自動增長,且屬性值為空我們不傳入
//TODO 處理主鍵不是自增
//insert into emp(name,age,salary,birthday,deptId) values(?,?,?,?,?)
StringBuilder sql = new StringBuilder("insert into "+tableInfo.getName()+"(");
Field[] fields = clazz.getDeclaredFields();
int fieldValueNotNullCount = 0;
List<Object> fieldValues = new ArrayList<>();
for (Field field:fields){
Object methodResult = ReflectUtils.getMethodResult(table, field.getName());
if(methodResult!=null){
fieldValueNotNullCount++;
fieldValues.add(methodResult);
sql.append(field.getName()+",");
}
}
//去掉多餘的 , 變為 )
sql.replace(sql.lastIndexOf(","),sql.length(),") ");
// 拼接 ?
sql.append("values(");
for (int i =0;i<fieldValueNotNullCount;i++){
sql.append("?,");
}
//去掉多餘的 , 變為 )
sql.replace(sql.lastIndexOf(","),sql.length(),") ");
executeDML(sql.toString(),fieldValues.toArray());
}
五 、完成跟新
跟新也很簡單,也是拼接sql語句,不過,這裡為了友善,我們需要将修改的值的fileName顯示的傳入進來
* @param fieldNames 需要修改的值的屬性名稱,不需要傳入主鍵的fieldName
@Override
public int update(Object table, String[] fieldNames) {
//{empName,age} ----> update emp set empName=?,age=? where 主鍵=?
Class<?> clazz = table.getClass();
TableInfo tableInfo = TableContext.getClazzAllTable().get(clazz);
//周遊 fieldName 拼裝 update emp set empName=?,age=?
StringBuilder sql = new StringBuilder("update emp set ");
//屬性的值清單
List<Object> fieldValues = new ArrayList<>();
for (String fieldName:fieldNames){
sql.append(fieldName+"=?,");
Object methodResult = ReflectUtils.getMethodResult(table, fieldName);
fieldValues.add(methodResult);
}
//将最後一個 , ---> 空格
sql.replace(sql.lastIndexOf(","),sql.length()," ");
//拼接 where 主鍵=?
String priKeyName = tableInfo.getOnlyPriKey().getName();
sql.append("where "+priKeyName+"=?");
fieldValues.add(ReflectUtils.getMethodResult(table,priKeyName));
return executeDML(sql.toString(), fieldValues.toArray());
}
六、測試
本篇完成了增删改,為友善測試,建立一個測試類,專門測試,内容如下
package test.cn.gxm.sorm.core;
import cn.gxm.sorm.core.DBManager;
import cn.gxm.sorm.core.MysqlQuery;
import cn.gxm.sorm.core.TableContext;
import cn.gxm.sorm.pojo.Emp;
import org.junit.Test;
import org.junit.Before;
import org.junit.After;
import java.sql.Date;
/**
* MysqlQuery Tester.
*
* @author GXM www.guokangjie.cn
* @since
* @version 1.0
*/
public class MysqlQueryTest {
private MysqlQuery mysqlQuery;
@Before
public void before() throws Exception {
mysqlQuery = new MysqlQuery();
// 測試之前将 db.properties加載到對應映射類中
DBManager.getConnection();
// 加載所有的表資訊
TableContext.getClazzAllTable();
}
@After
public void after() throws Exception {
}
/**
*
* Method: executeDML(String sql, Object[] params)
*
*/
@Test
public void testExecuteDML() throws Exception {
//TODO: Test goes here...
}
/**
*
* Method: insert(Object table)
*
*/
@Test
public void testInsert() throws Exception {
Emp emp = new Emp();
emp.setAge(22);
emp.setBirthady(new Date(19200000));
emp.setEmpName("阿豹");
emp.setSalary(160000d);
mysqlQuery.insert(emp);
}
/**
*
* Method: delete(Class clazz, Object primaryKey)
*
*/
@Test
public void testDeleteForClazzPrimaryKey() throws Exception {
mysqlQuery.delete(Emp.class,1);
}
/**
*
* Method: delete(Object table)
*
*/
@Test
public void testDeleteTable() throws Exception {
Emp emp = new Emp();
emp.setId(2);
mysqlQuery.delete(emp);
}
/**
*
* Method: update(Object table, String[] params)
*
*/
@Test
public void testUpdate() throws Exception {
Emp emp = new Emp();
emp.setId(3);
emp.setSalary(20000d);
emp.setEmpName("明正");
int update = mysqlQuery.update(emp, new String[]{"salary", "empName"});
System.out.println(update!=0);
}
}