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