天天看点

写出你自己的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);
    }

} 

           

继续阅读