Nop 平台的数据访问层使用 NopORM 引擎,它的功能相当于 JPA + MyBatis + SpringData,并且内置了多租户、逻辑删除、动态扩展字段、字段加密等业务常用功能。NopGraphQL 服务框架会自动自动识别 ORM 的实体对象,自动使用 ORM 引擎去实现实体关联属性的批量加载。
Nop 平台的标准开发模式是先设计数据模型,然后再根据数据模型生成 Java 实体代码,但是这只是简化开发的一种方式。NopORM 支持动态数据模型,我们可以跳过代码生成的步骤直接手工编写数据模型文件,从而实现数据库访问层。
讲解视频:https://www.bilibili.com/video/BV1yC4y1r716/
最简单的数据访问层开发步骤如下:
一. 编写 app.orm.xml 文件
Nop 平台启动时会自动加载所有模块的 orm 目录下的 app.orm.xml 文件。
例如/_vfs/nop/demo/orm/app.orm.xml
。 nop/orm 目录下具有文件_module,表示它是一个 Nop 模块。
<orm x:schema="/nop/schema/orm/orm.xdef" xmlns:x="/nop/schema/xdsl.xdef">
<entities>
<entity name="app.demo.DemoEntity" tableName="demo_entity"
className="io.nop.orm.support.DynamicOrmEntity" registerShortName="true">
<columns>
<column name="sid" code="SID" propId="1" stdSqlType="VARCHAR" precision="32" tagSet="seq" mandatory="true"
primary="true"/>
<column name="name" code="NAME" propId="2" stdSqlType="VARCHAR" precision="100" mandatory="true"/>
<column name="status" code="STATUS" propId="3" stdSqlType="INTEGER"/>
</columns>
</entity>
</entities>
</orm>
复制代码
如果不生成特定的 Java 实体类,可以使用系统内置的动态实体类 DynamicEntity
每个字段都必须指定 propId 属性,不要求连续,但是不能重复。
主键字段需要标注 primary=true。指定 tagSet=seq 表示为它增加 seq 标签,从而在保存的时候自动生成随机值
如果 application.yaml 中配置了nop.orm.init-database-schema: true
,则系统启动的时候会自动根据模型配置创建数据库表
二. 通过 IDaoProvider 获取 IEntityDao
我们可以增加一个 DemoEntityBizModel,在其中通过@Inject
自动注入 IDaoProvider。一般情况下实现增删改查的 BizModel 会从 CrudBizModel 继承,它已经实现了大量标准的 CRUD 操作。这里为了演示功能,我们选择不继承已有的 CrudBizModel,完全手工编写。
@BizModel("DemoEntity")
public class DemoEntityBizModel {
// 注意,字段不能声明为private。NopIoC无法注入私有成员变量
@Inject
IDaoProvider daoProvider;
@BizQuery
@GraphQLReturn(bizObjName = "DemoEntity")
public IOrmEntity getEntity(@Name("id") String id) {
IEntityDao<IOrmEntity> dao = daoProvider.dao("app.demo.DemoEntity");
return dao.getEntityById(id);
}
@BizMutation
@GraphQLReturn(bizObjName = "DemoEntity")
public IOrmEntity saveEntity(@Name("data") Map<String, Object> data) {
IEntityDao<IOrmEntity> dao = daoProvider.dao("app.demo.DemoEntity");
OrmEntity entity = dao.newEntity();
BeanTool.instance().setProperties(entity, data);
dao.saveEntity(entity);
return entity;
}
@BizQuery
@GraphQLReturn(bizObjName = "DemoEntity")
public List<IOrmEntity> findByName(@Name("name") String name) {
IEntityDao<IOrmEntity> dao = daoProvider.dao("app.demo.DemoEntity");
QueryBean query = new QueryBean();
query.addFilter(FilterBeans.contains("name", name));
return dao.findAllByQuery(query);
}
}
复制代码
一般情况下@BizModel
注解指定的对象名与实体对象名相同,便于代码定位。
通过daoProvider.dao(entityName)
可以获取到指定实体类对应的 Dao 对象。在 Nop 平台中我们只会使用平台内置的 IEntityDao 接口,它已经提供了足够丰富的方法,不需要业务开发人员再去扩展 Dao 接口。如果有些功能 IEntityDao 接口无法满足需求,可以使用IOrmTemplate
或者SqlLibMapper
机制。
服务函数可以返回实体对象。这一点与 SpringMVC 的 Controller 不同。Controller 一般只能返回可以自动序列化为 JSON 的 DTO 对象,否则无法控制哪些字段可以返回到前台。当我们不是直接返回字段,而是返回某种动态处理结果的时候,在 Spring 框架中也需要通过 DTO 进行适配。但是在使用 NopGraphQL 框架时,我们可以直接返回实体,然后通过 xmeta 元数据来控制返回字段,并且增加额外的转换逻辑。需要注意的是,我们现在使用的是动态实体对象,因此无法根据类名来确定是哪个实体类型,所以需要通过@GraphQLReturn
注解来指明返回的对象类型是什么。
在/_vfs/nop/demo/model/
目录下需要增加一个DemoEntity/DemoEntity.xmeta
元数据文件。当 GraphQL 服务函数返回的类型为指定对象类型时,会加载这里的元数据文件来获取对象信息。在这个文件中我们也可以增加实体上没有的字段,通过getter
等配置实现动态计算。
三. 通过 XMeta 模型增加自定义字段
NopGraphQL 框架实际返回的业务对象的属性可以由 xmeta 模型来控制。通过它还可以控制访问权限、转换逻辑等。通过 getter 属性我们可以为业务对象增加自定义字段。
<meta x:schema="/nop/schema/xmeta.xdef" xmlns:x="/nop/schema/xdsl.xdef">
<props>
<prop name="sid" displayName="SID" queryable="true">
<schema type="String"/>
</prop>
<prop name="name" displayName="名称" queryable="true" insertable="true" updatable="true">
<schema type="String"/>
</prop>
<prop name="status" displayName="状态" queryable="true" insertable="true" updatable="true">
<schema type="Integer"/>
</prop>
<prop name="status_label" displayName="状态文本">
<schema type="String"/>
<getter>
<c:script><![CDATA[
if(entity.status == 1)
return "ACTIVE";
return "INACTIVE";
]]></c:script>
</getter>
</prop>
</props>
</meta>
复制代码
可以看出 xmeta 中的信息与 orm 模型中的信息有一定的重叠之处,但是它们用于不同的目的,一般并不会完全一致。Nop 平台中的标准做法是使用编译期元编程自动实现两者之间的信息同步,并利用 Delta 合并来引入差异信息。在本文中我们不会涉及这些细节,感兴趣的读者可以参考Nop平台元编程
四. 通过 SqlLibMapper 接口调用 SQL 语句
1. 声明接口 DemoMapper, 通过@SqlLibMapper
注解与 sql 文件关联
@SqlLibMapper("/nop/demo/sql/demo.sql-lib.xml")
public interface DemoMapper {
IOrmEntity findFirstByName(@Name("name") String name);
}
复制代码
2. 在 beans.xml 中注册 Mapper 接口类
因为 NopIoC 并不使用类扫描机制,所以我们需要手动在 app-simple-demo.beans.xml 中增加 bean 的定义。
<bean id="io.nop.auth.dao.mapper.NopAuthRoleMapper" class="io.nop.orm.sql_lib.proxy.SqlLibProxyFactoryBean"
ioc:type="@bean:id" ioc:bean-method="build">
<property name="mapperClass" value="@bean:type"/>
</bean>
复制代码
3. 在 demo.sql-lib.xml 增加 SQL 语句或者 EQL 对象查询语句
<sql-lib x:scheme="/nop/schema/orm/sql-lib.xdef" xmlns:x="/nop/schema/xdsl.xdef">
<sqls>
<eql name="findFirstByName" sqlMethod="findFirst">
<source>
select o from DemoEntity o where o.name like ${'%' + name + '%'}
</source>
</eql>
</sqls>
</sql-lib>
复制代码
4. 在 BizModel 中调用 SqlLibMapper
class DemoEntityBizModel{
@Inject
DemoMapper demoMapper;
@BizQuery
@GraphQLReturn(bizObjName = "DemoEntity")
public IOrmEntity findBySql(@Name("name") String name) {
return demoMapper.findFirstByName(name);
}
}
复制代码
评论