java设计模式之DAO设计模式与ORM设计模式


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;
}

文章作者: 穿山甲
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 穿山甲 !
  目录