盒子
盒子
文章目录
  1. 一、完成query接口
  2. 二 、完成 executeDML
  3. 三 、完成MysqlQuery的删除功能
    1. 3.1 定义clazzAllTable
    2. 3.2 接下来完成根据主键删除,根据主键删除的有二个重载方法
  4. 四 、完成增加功能
  5. 五 、完成跟新
  6. 六、测试

'写出你自己的ORM框架(二) '

  • 代码地址
  • 写出你自己的ORM框架(一)
  • 写出你自己的ORM框架(二)
  • 写出你自己的ORM框架(三)
  • 写出你自己的ORM框架(四)

    一、完成query接口

    query是一个接口,是因为每一个数据库的实现方法都不样,所以定义为接口,我们这里就只完成mysql的普通CRUD即可!
    query接口中的方法如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    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方法代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * 处理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方法代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * 处理执行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方法就是如下了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @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

1
2
3
4
5
/**
* 对应数据库中所有的表
* key 为表的名称, value为表对应的bean
*/
private static Map<String, TableInfo> allTables = new HashMap<>();

但是我们根据指定的规则使用名称来找,可能会出现问题,所以我们这里再定义个Map,根据clazz来找,这下绝对不会出错,定义如下

1
2
3
4
/**
* 将生成的pojo用class与数据库中的表关联起来
*/
private static Map<Class ,TableInfo> clazzAllTable = new HashMap<>();

填充clazzAllTable其实与allTables差不多,只不过,一个Key是表的名称,一个是表对应的pojo的class对象,实现如下

1
2
3
//配置clazzAllTales
Class<?> clazz = Class.forName(DBManager.getConfiguration().getPojoPackage() + "." + StringUtils.toUpperCaseHeadChar(tableName));
clazzAllTable.put(clazz,tableInfo);

3.2 接下来完成根据主键删除,根据主键删除的有二个重载方法

/**

* 根据 id 删除数据
* @param clazz 数据库中表对应的java对象
* @param primaryKey 主键的值
*  delete from User where id = ?
*/
  • public void delete(Class clazz, Object primaryKey)
1
2
3
4
5
6
7
8
9
10
@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});
}

/**

* 根据与数据库对应的表的对象来删除
* 根据主键删除 主键值在传入的Object对象中
* @param table
*/
  • public void delete(Object table)
    这个方法主键的值并没有显式的展示出来,在传入的对象中,但是我们无法直接调用get方法获取,该属性的值,因为传入是object,所以我们可以根据主键的名称拼接该属性的get方法,通过反射调用即可
    反射方法定义实现如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * 根据属性名称调用其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;
    }

完成该方法,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
@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方法去执行就可以了
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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());
}

六、测试

本篇完成了增删改,为方便测试,建立一个测试类,专门测试,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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);
}

}

希望对您有所帮助
May all the ordinary are great, all the ignoble bloom
  • smile