自研了Java Orm层后,我就开始上手使用了

679 阅读5分钟

上一篇中,实现了对数据库层的抽象。有些读者朋友提到了,里面使用的是HashMap作为入参,不符合ORM标准的操作。说的没错,不过上一篇是抽象基础查询,弄成hashMap作为入参,只需要业务查询层在继承的时候去实现Object类型的查询。再来看看数据库层抽象的架构图
image.png
这一部分,我就以实现业务部分的数据库查询为例,将数据库层进一步封装。

一个简单的业务例子

让我们以每个系统中都有的组织架构场景为例,来使用上述抽象查询。

建表

首先,创建组织表sys_organization

--组织表
CREATE TABLE IF NOT EXISTS sys_organization
(
    id              bigint PRIMARY KEY   DEFAULT random_id(), --记录标识
    org_name        varchar(64) NOT NULL,                     --组织全称
    org_type        varchar(64) NOT NULL DEFAULT 'unit',      --组织类型
    father_org_id   bigint      NOT NULL DEFAULT '0',         --上级组织id
    father_org_name varchar(64) NOT NULL DEFAULT '',          --上级组织全称
    social_code     varchar(64) NOT NULL DEFAULT '',          --统一社会纳税人识别号

    --通用管理信息
	... ...
);

开始定义业务层基础代码

使用自动生成代码的方式,生成对应的Model如下:

//组织表
public class  SysOrganization {
    private Long id;         //记录标识 
    private String orgName;         //组织全称 
    private String orgType;         //组织类型
    private Long fatherOrgId;         //上级组织id 
    private String fatherOrgName;         //上级组织全称 
    private String socialCode;         //统一社会纳税人识别号 
    ... ...
}

然后我们需要定义出,该数据表,继承基本查询的查询,定义SysOrganizationQuery继承PostgreSQLBaseQuery,那么最初的写法为:

public class SysOrganizationQuery extends PostgreSQLBaseQuery<SysOrganization> {

  public SysOrganizationQuery() {
    super();
    this.primaryKey = "id";
  }
  ... ...
}

然后我们可以思考一下,对于一个数据表来说,基础的操作有哪些?

  • 按条件查询单行数据->即返回单个对象
  • 按条件查询分页多行数据->即返回多个对象
  • 按条件查询所有数据
  • 按条件查询总数
  • 按条件更新
  • 按主键更新
  • 单个新增
  • 批量新增
  • 其余根据项目整体需求架构调整的基础方法(这一点正是已有框架无法统一实现和做到的,在应对数据安全里面对行级数据的权限控制这种场景时,如果不在数据库层统一自动生成代码,造成的后果就是大量特判)

那我们根据以上操作,来完善该SysOrganizationQuery,完善的时候,就可以用到我们之前定义的基础查询了。

public class SysOrganizationQuery extends PostgreSQLBaseQuery<SysOrganization> {

    public SysOrganizationQuery() {
        super();
        this.primaryKey = "id";
    }

    public Long count(SysOrganization sysOrganization) {
        return this.countModelBySimpleAnd(Json.toMap(Json.toJson(sysOrganization)));
    }

    public List<SysOrganization> page(SysOrganization sysOrganization, Integer page, Integer size) {
        return this.findListModelBySimpleAnd(Json.toMap(Json.toJson(sysOrganization)), page, size);
    }
 
    public Integer updateByCondition(SysOrganization condition, SysOrganization value) {
        return this.update(Json.toMap(Json.toJson(condition)), Json.toMap(Json.toJson(value)));
    }
    
    public List<SysOrganization> findByCondition(SysOrganization condition) {
        return this.findListModelBySimpleAnd(Json.toMap(Json.toJson(condition)));
    }
}

好了,业务查询的代码就完毕了。你可能发现了,由于这些代码都是统一的形式,仅仅是对象名不一样,所以这个业务Query也是可以用字符串替换的方式实现自动生成的。

现在,我们需要将该抽象融入进Spring项目,所以需要给SysOrganization创建数据仓库对象,那么我们创建一个SysOrganizationRepository如下:

public interface SysOrganizationRepository {

    default SysOrganization findSysOrganizationById(Long id) {
        SysOrganizationQuery query = new SysOrganizationQuery();
        return query.findModelById(id);
    }

    default SysOrganization findSysOrganizationByCondition(SysOrganization condition) {
        String json = Json.toJson(condition);
        Map<String, Object> andCondition = Json.toMap(json);
        SysOrganizationQuery query = new SysOrganizationQuery();
        return query.findModelBySimpleAnd(andCondition);
    }

    default Long save(SysOrganization sysOrganization) {
        SysOrganizationQuery query = new SysOrganizationQuery();
        return query.insert(Json.toMap(Json.toJson(sysOrganization)));
    }


    default Integer saveAll(List<SysOrganization> sysOrganizations) {
        List<Map<String, Object>> sysOrganizationMaps = new ArrayList<>();
        for (SysOrganization sysOrganization : sysOrganizations) {
            sysOrganizationMaps.add(Json.toMap(Json.toJson(sysOrganization)));
        }
        SysOrganizationQuery query = new SysOrganizationQuery();
        return query.batchInsert(sysOrganizationMaps);
    }

    default List<SysOrganization> findAll() {
        SysOrganizationQuery query = new SysOrganizationQuery();
        List<Map<String, Object>> tmp = query.findAll();
        List<SysOrganization> ans = new ArrayList<>();
        for (Map<String, Object> x : tmp) {
            ans.add(Json.toObject(Json.toJson(x), SysOrganization.class));
        }
        return ans;
    }

    default Integer update(SysOrganization condition, SysOrganization value) {
        SysOrganizationQuery query = new SysOrganizationQuery();
        return query.updateByCondition(condition, value);
    }

    default Long count(SysOrganization sysOrganization) {
        SysOrganizationQuery sysOrganizationQuery = new SysOrganizationQuery();
        return sysOrganizationQuery.count(sysOrganization);
    }


    default List<SysOrganization> page(SysOrganization sysOrganization, Integer page, Integer size) {
        SysOrganizationQuery sysOrganizationQuery = new SysOrganizationQuery();
        return sysOrganizationQuery.page(sysOrganization, page, size);
    }

    default Integer updateById(Long id, SysOrganization value) {
        SysOrganization condition = new SysOrganization();
        condition.setId(id);
        SysOrganizationQuery query = new SysOrganizationQuery();
        return query.updateByCondition(condition, value);
    }
}

你可以一一对照我们上面提到的对于一个数据表来说,基础的操作,这里是否都可以通过调用查询类实现了。同时你可能也发现了,以上提到的所有类,都可以自动生成,而且,从下层service层调用来说,也是通过对象调用。不存在使用hashmap作为入参的情况。

最后,如果想要实现特殊需求的查询,只需要实现该接口仓库即可,如下:

@Repository
public class SysOrganizationRepositoryImpl implements SysOrganizationRepository {

    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
}



#### 如何自动生成上面的代码 ​

首先,需要读取并解析创建表的SQL,我这里使用的PGSQL,所以也是针对其语法来做的解析。
需要定义出Table对象和字段对象,如下:

static class Field {
    String name;
    String type;
    String desc;

    public Field(String name, String type, String desc) {
        this.name = name;
        this.type = type;
        this.desc = desc;
    }
}

static class Table {
    String name;
    List<Field> fields;
    String desc;

    public Table(String name, List<Field> fields, String desc) {
        this.name = name;
        this.fields = new ArrayList<>();
        this.fields.addAll(fields);
        this.desc = desc;
    }
}

解析代码如下:

public static List<Table> getTableStrs(String fileName) {
        List<Table> tables = new ArrayList<>();
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(fileName)));
            String line = "";
            String tableName = "";
            List<Field> fieldList = new ArrayList<>();
            String tableDesc = "";
            String tmpDesc = "";
            while ((line = bufferedReader.readLine()) != null) {
                if (line.toUpperCase().startsWith("CREATE TABLE IF NOT EXISTS")) {
                    tableName = line.replace("CREATE TABLE IF NOT EXISTS", "").trim();
                    tableDesc = tmpDesc;
                    if (tableName.length() >= 32) {
                        System.out.println(tableName + "表长度大于32");
                        return new ArrayList<>();
                    }
                    fieldList = new ArrayList<>();
                }
                if (line.startsWith("* 表描述:")) {
                    tmpDesc = line.replace("* 表描述:", "").trim();
                }
                if (line.startsWith("--")) {
                    tmpDesc = line.replace("--", "").trim();
                }
                if (line.startsWith(" ") || line.startsWith("\t")) {
                    line = line.trim();
                    String[] a = line.split(" ");
                    int cnt = 0;
                    if (a.length >= 3) {
                        String name = "";
                        String type = "";
                        String desc = "";
                        for (String b : a) {
                            b = b.trim();
                            if (StringUtils.hasText(b)) {
                                b = b.toLowerCase();
                                if (cnt == 0) {
                                    name = b;
                                    cnt += 1;
                                }
                                if (b.contains("bigint") || b.contains("int8")) {
                                    type = "int8";
                                }
                                if (b.contains("int4") || b.contains("integer")) {
                                    type = "int4";
                                }
                                if (b.contains("varchar") || b.contains("text")) {
                                    type = "varchar";
                                }
                                if (b.contains("timestamp")) {
                                    type = "timestamp";
                                }
                                if (b.contains("date")) {
                                    type = "date";
                                }
                                if (b.contains("uuid")) {
                                    type = "uuid";
                                }
                                if (b.contains("boolean") || b.contains("bool")) {
                                    type = "boolean";
                                }
                                if (b.contains("float4")) {
                                    type = "float4";
                                }
                            }
                            if (b.contains("--")) {
                                b = b.trim();
                                desc = b.replace("--", "");
                            }
                        }
                        if (StringUtils.hasText(name) && StringUtils.hasText(type)) {
                            if (name.length() >= 32) {
                                System.out.println(name + "字段长度大于32");
                                return new ArrayList<>();
                            }
                            fieldList.add(new Field(name, type, desc));
                        }
                    }
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return tables;
    }

一般来说,字段的长度不要大于32,这是为了兼容有些数据库对列名有长度限制的场景。太长了可读性也变差了,所以这里有个特判。

有了解析代码,我们只需要实现生成的代码即可,这一步有些框架可以使用,但是我比较喜欢自己实现,出了问题自己再去迭代的方式。

  • 生成Model
public static String getModelStr(Table table) {
    String name = StringFormatUtils.snake(table.name, true);
    String desc = "//" + table.desc + "\n";
    StringBuilder ans = new StringBuilder().append(String.format("public class %s {\n", name));
    List<String> types = new ArrayList<>();
    for (Field field : table.fields) {
        ans.append(String.format("    private %s %s;         //%s \n", sqlTypeExchange2Java(field.type),
                                 StringFormatUtils.snake(field.name, false),
                                 field.desc));
        types.add(sqlTypeExchange2Java(field.type));
    }
    for (Field field : table.fields) {
        ans.append(String.format("    public %s get%s() {\n" +
                                 "        return %s;\n" +
                                 "    }\n", sqlTypeExchange2Java(field.type),
                                 StringFormatUtils.snake(field.name, true),
                                 StringFormatUtils.snake(field.name, false)));
        ans.append(String.format("    public void set%s(%s %s) {\n" +
                                 "        this.%s = %s;\n" +
                                 "    }\n", StringFormatUtils.snake(field.name, true),
                                 sqlTypeExchange2Java(field.type),
                                 StringFormatUtils.snake(field.name, false),
                                 StringFormatUtils.snake(field.name, false),
                                 StringFormatUtils.snake(field.name, false)));
    }
    String importStr = importStr(types);
    return importStr + "\n" + desc + ans.toString() + "\n" + "}";
}
  • 生成Query
  • 生成Repository
  • 生成RepositoryImpl

这三个的生成,只需要将关键词抠出来,用表名的大小驼峰替换即可。

总结

这样,整个数据库层的抽象就做完了。同时也做到了大量的基础方法进行自动生成,在数据表创建好的时候,执行一条命令,上述基础业务代码都可以自动生成。

当然,为了兼容更多的复杂情况,这个数据库层抽象还是幼小的,需要不断去迭代。