上一篇中,实现了对数据库层的抽象。有些读者朋友提到了,里面使用的是HashMap作为入参,不符合ORM标准的 *** 作。说的没错,不过上一篇是抽象基础查询,弄成hashMap作为入参,只需要业务查询层在继承的时候去实现Object类型的查询。再来看看数据库层抽象的架构图
这一部分,我就以实现业务部分的数据库查询为例,将数据库层进一步封装。
让我们以每个系统中都有的组织架构场景为例,来使用上述抽象查询。
首先,创建组织表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{ public SysOrganizationQuery() { super(); this.primaryKey = "id"; } ... ... }
然后我们可以思考一下,对于一个数据表来说,基础的 *** 作有哪些?
按条件查询单行数据->即返回单个对象按条件查询分页多行数据->即返回多个对象按条件查询所有数据按条件查询总数按条件更新按主键更新单个新增批量新增其余根据项目整体需求架构调整的基础方法(这一点正是已有框架无法统一实现和做到的,在应对数据安全里面对行级数据的权限控制这种场景时,如果不在数据库层统一自动生成代码,造成的后果就是大量特判)
那我们根据以上 *** 作,来完善该SysOrganizationQuery,完善的时候,就可以用到我们之前定义的基础查询了。
public class SysOrganizationQuery extends PostgreSQLbaseQuery{ public SysOrganizationQuery() { super(); this.primaryKey = "id"; } public Long count(SysOrganization sysOrganization) { return this.countModelBySimpleAnd(Json.toMap(Json.toJson(sysOrganization))); } public List 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 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); MapandCondition = 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 sysOrganizations) { List
你可以一一对照我们上面提到的对于一个数据表来说,基础的 *** 作,这里是否都可以通过调用查询类实现了。同时你可能也发现了,以上提到的所有类,都可以自动生成,而且,从下层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; Listfields; String desc; public Table(String name, List fields, String desc) { this.name = name; this.fields = new ArrayList<>(); this.fields.addAll(fields); this.desc = desc; } }
解析代码如下:
public static List getTableStrs(String fileName) { List tables = new ArrayList<>(); try { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(fileName))); String line = ""; String tableName = ""; ListfieldList = 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)); Listtypes = 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
这三个的生成,只需要将关键词抠出来,用表名的大小驼峰替换即可。
这样,整个数据库层的抽象就做完了。同时也做到了大量的基础方法进行自动生成,在数据表创建好的时候,执行一条命令,上述基础业务代码都可以自动生成。
当然,为了兼容更多的复杂情况,这个数据库层抽象还是幼小的,需要不断去迭代。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)