上一篇中,实现了对数据库层的抽象。有些读者朋友提到了,里面使用的是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<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
这三个的生成,只需要将关键词抠出来,用表名的大小驼峰替换即可。
总结
这样,整个数据库层的抽象就做完了。同时也做到了大量的基础方法进行自动生成,在数据表创建好的时候,执行一条命令,上述基础业务代码都可以自动生成。
当然,为了兼容更多的复杂情况,这个数据库层抽象还是幼小的,需要不断去迭代。