1.利用反射机制手写一个简单的CURD工具类
1.1 框架抽取
为了将一个特例的JDBC的CRUD进行推广,我们先从一个最基本的CRUD的DAO开始分析
public class StudentDAOImpl implements StudentDAO {
private Connection connection = SqlSessionFactory.getConnection();
@Override
public boolean addStudent(Student student) {
boolean result = false;
String sql = "insert into student(id,name,tutor) values(?,?,?)";
try {
PreparedStatement pre = connection.prepareStatement(sql);
pre.setString(1,student.getId());
pre.setString(2,student.getName());
pre.setString(3,student.getTutor());
result = pre.execute();
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
}
我们从代码中抽取出一个核心的框架
- 第一步:编写SQL
- 第二步,绑定SQL中需要的参数
- 第三步,执行SQL
这是最基本的代码执行逻辑,然后我们将这一套代码又细分为CRUD四个操作
Create(add操作)
- 第一步:编写SQL
我们先从SQL是如何编写的开始分析,sql大致可以分解为
insert into #这部分不变
table_name #需要解析出一个表名
(value1,value2,...,value3)#需要解析出这个实体类的所有属性名
values( #这部分不变
?,?,?,#若干个?
)#这部分不变
这样的话就分析出了需要的材料
- 实体类所对应的表名
- 实体类中的所有属性名
- 实体类中属性名的个数
注:为了简化代码逻辑,体现底层核心的DAO是如何Generic的,这里的话默认数据库表名和字段名没有下划线,实际上mybaits内部也没有做这个映射,而是在后续的springboot中集成了这个表名-类名、字段名-属性名映射的操作,因此在这里简化了。
对于一个正确的orm
映射而言,其表名应该全都是小写的,因此对于表名的处理策略是对传进来的Object,我们解析其类名,获取其类名并且转为小写,就得到了所需的表名,这里的话先写一个工具类,便于对类中属性的解析
1.2 insert实现
public class SQLUtil {
private SQLUtil(){}
/**
*
* @param className:全限定类名
* @return
*/
public static String parseTableName(String className){
//TODO:这里可以改写为小驼峰->蛇形命名的规范
String[] split = className.split("\\.");
return split[split.length-1].toLowerCase(Locale.ROOT);//全部转成小写
}
/**
* 获取得到属性名的方法,方便invoke
* @param attributeName
* @return
*/
public static String parseGetMethod(String attributeName){
return "get"+attributeName.toUpperCase(Locale.ROOT).charAt(0)+attributeName.substring(1);
}
/**
* 获取设置属性名的方法,方便invoke
* @param attributeName
* @return
*/
public static String parseSetMethod(String attributeName){
return "set"+attributeName.toUpperCase(Locale.ROOT).charAt(0)+attributeName.substring(1);
}
}
获取所有的属性和值,鉴于插入是第一个,我们做复杂点,我们这里的实体类设计是面向对象的设计,因此里面含有实体类,所以的话还有获取实体类
public boolean addObject(Object pojo) {
boolean result = false;
//1.获取表名
String tableName = SQLUtil.parseTableName(pojo.getClass().getName());
//2.通过获取所有属性
Class<?> pojoClass = pojo.getClass();
Field[] declaredFields = pojoClass.getDeclaredFields();
//属性名->属性值
List<Entry> valueObject = new ArrayList<>();
//先过滤出那些我们可以直接进行操作的属性
for (Field declaredField : declaredFields) {
//获取实际参数类型
if(map.get(declaredField.getType())!=null){//是基本数据类型
if(!declaredField.getType().isAssignableFrom(Collection.class)){//并且不是集合对象
//获取值
Object value = pojoClass.getDeclaredMethod(SQLUtil.parseGetMethod(declaredField.getName())).invoke(pojo);
valueObject.add(new Entry(declaredField.getName(),value));
}
}else{
if(Collection.class.isAssignableFrom(declaredField.getType())){
continue;
}
//不是基本数据类型,并且不是集合对象
//这时候就是自己封装的实体类,进行操作
//TODO:这里是有缺陷的,如果是实体类封装到这里面来,如果不借助于外部的映射条件,不知道它映射哪个字段
//所以的话没办法,这里默认设置它映射的是id,获取它的值
//获取内部实体对象
String getObjectMethod = SQLUtil.parseGetMethod(declaredField.getName());
Method declaredMethod = pojoClass.getDeclaredMethod(getObjectMethod);
Object nestedObject = declaredMethod.invoke(pojo);
//然后获取其主键
//TODO:如果对象之下还有对象,则需要递归,但是这里不做那么复杂了
String getIdMethod = SQLUtil.parseGetMethod("id");
Object value = nestedObject.getClass().getDeclaredMethod(getIdMethod).invoke(nestedObject);
valueObject.add(new Entry(declaredField.getName(),value));
}
}
//测试能否得到所有值
//好了,得到了所有值之后,就可以开始拼装sql了
//有多少个key,就有多少个value
String insertSql = SQLUtil.getInsertSql(tableName, valueObject);
//然后开始JDBC
PreparedStatement pre = connection.prepareStatement(insertSql);
int length = valueObject.size();
for(int i = 0;i<length;i++){
pre.setObject(i+1,valueObject.get(i).getValue());
}
result = pre.execute();
return result;
}
然后就是编写查询的,关于查询,我们假设用户不会用实体类对象进行查询,因此这样的话简化我们的流程
1.3 select实现
首先对于select而言,其基本sql形式如下:
select * from table_name where key1 = ?,key2 = ?,key3 = ?,...;
因此我们所需要的材料是
- 表的名字
- 要查询的参数的名字
这就相对比较简单了,我们直接看下代码怎么写的
public Object findObject(Class<?> clazz, Object param) {
//这里简化设计,假设不会使用实体类进行查询
//首先获取这个对象,进行赋值
Object result = new Object();
try{
//获取所有的参数,然后我们忽略掉实体类和集合类,仅对基本数据类型进行判断
Field[] declaredFields = clazz.getDeclaredFields();
result = clazz.newInstance();
//primaryKey field
String primaryKeyName ="id";
//然后拼接sql
List<Entry> entryList = new ArrayList<>();
entryList.add(new Entry(primaryKeyName,param));
String selectSql = SQLUtil.getSelectSql(SQLUtil.parseTableName(clazz.getName()), entryList);
System.out.println(selectSql);
//然后开启jdbc
PreparedStatement pre = connection.prepareStatement(selectSql);
pre.setObject(1,param);
ResultSet resultSet = pre.executeQuery();
if(resultSet.next()){
//然后将字段设置上去
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
for(int i = 1;i<=columnCount;i++){
String columnName = metaData.getColumnName(i);
//然后映射到字段方法
//在设置之前还要鉴定一下类型,如果类型是非基本类型,我们按照insert中,对于对象的处理逻辑进行处理
Field field = clazz.getDeclaredField(columnName);
if(map.get(field.getType())!=null){
//基本数据类型
//直接设置
String colName = metaData.getColumnName(i);
String colVal = resultSet.getString(i);
String setMethod = SQLUtil.parseSetMethod(colName);
System.out.println(field.getType());
clazz.getDeclaredMethod(setMethod,field.getType()).invoke(result,colVal);
}else{
//否则就是非基本数据类型,我们去取它的id
//1.取到这个类型的名称,根据这个名称,去查表
//然后查询这个表,然后将其设置到我们源对象中
Object nestedObject = findObject(field.getType(), resultSet.getObject(i));
String nestedName = metaData.getColumnName(i);
String setMethod = SQLUtil.parseSetMethod(nestedName);
//设置好了
clazz.getDeclaredMethod(setMethod,nestedObject.getClass()).invoke(result,nestedObject);
}
}
}
System.out.println(result);
}catch (Exception e){
e.printStackTrace();
}
return result;
}
1.4 delete删除实现
删除的实现是基于主键删除的方式进行实现的。
先写一个基本的删除语句
delete from student where id = ?;
public boolean deleteObject(Class<?> clazz,Object params) {
boolean result = false;
try{
//对于params而言,我们认为它是主键,然后删除数据库中的内容
//1.获取表的名字
String tableName= SQLUtil.parseTableName(clazz.getName());
//对params进行包装
List<Entry> entryList = new ArrayList<>();
//TODO:这里可以将object... agrs进行更改
entryList.add(new Entry("id",params));
String deleteSql = SQLUtil.getDeleteSql(tableName, entryList);
System.out.println(deleteSql);
//然后执行sql语句
PreparedStatement pre = connection.prepareStatement(deleteSql);
int length = entryList.size();
for(int i = 0;i < length;i++){
pre.setObject(i+1,entryList.get(i).getValue());
}
result = pre.execute();
}catch (Exception e){
e.printStackTrace();
}
return result;
}
1.5 更新操作
update student set key1 = ?,key2 = ? where id = ?
public boolean updateObject(Object pojo,String primaryKeyName) {
//更新数据库中的内容
boolean result = false;
try{
Class<?> clazz = pojo.getClass();
Field[] declaredFields = clazz.getDeclaredFields();
//然后检查主键
Object primaryKeyValue = null;
primaryKeyValue = clazz.getMethod(SQLUtil.parseGetMethod(primaryKeyName)).invoke(pojo);
List<Entry> entryList = new ArrayList<>();
for (Field declaredField : declaredFields) {
//如果是基本数据类型
if(declaredField.getName().equals(primaryKeyName)){
continue;
}
if(map.get(declaredField.getType())!= null){
String key = declaredField.getName();
Object value = clazz.getMethod(SQLUtil.parseGetMethod(declaredField.getName())).invoke(pojo);
entryList.add(new Entry(key,value));
}else{
if(Collection.class.isAssignableFrom(declaredField.getType())){
continue;//跳过集合
}
//如果是实体类,那么拿到这个实体类里面的id
String getMethod = SQLUtil.parseGetMethod(declaredField.getName());
Object nestedObject = clazz.getMethod(getMethod).invoke(pojo);
Object id = nestedObject.getClass().getDeclaredMethod(SQLUtil.parseGetMethod("id")).invoke(nestedObject);
//然后封装到entry中
entryList.add(new Entry(declaredField.getName(),id));
}
//然后封装sql
}
String updateSql = SQLUtil.getUpdateSql(pojo, primaryKeyName, entryList);
System.out.println(updateSql);
int length = entryList.size();
PreparedStatement pre = connection.prepareStatement(updateSql);
for(int i = 0;i<length;i++){
pre.setObject(i+1,entryList.get(i).getValue());
}
pre.setObject(length+1,primaryKeyValue);
result = pre.execute();
}catch (Exception e){
e.printStackTrace();
}
return result;
}