天天看點

寫出你自己的ORM架構(二)一、完成query接口二 、完成 executeDML三 、完成MysqlQuery的删除功能四 、完成增加功能五 、完成跟新六、測試

  • 代碼位址
  • 寫出你自己的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);
    }

} 

           

繼續閱讀