Nop渠道供给了相似MyBatis的动态SQL管理才能,可是功用特性远比MyBatis丰厚、强壮。一起它的完成反而愈加简略,在NopORM的根底上完成SqlLibManager只需要300多行的代码。

解说视频: www.bilibili.com/video/BV1Xi…

一. 运用说明

1.1 添加一个sql-lib.xml文件

<!-- /nop/demo/sql/demo.sql-lib.xml -->
<sql-lib x:schema="/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>
  • 假如没有指定sqlMethod,则会依据SQL句子和是否传入range参数来主动推定。sqlMethod的可选值有findFirst/findPage/findAll/execute等
  • 经过${expr}形式引进的表达式会被主动替换为SQL参数,并不是直接作为字符串拼接在一起。假如表达式回来的是调集目标,还会主动展开成一组参数。

1.2 添加一个Mapper类

经过@SqlLibMapper注解指定相关的sql-lib.xml文件。

@SqlLibMapper("/nop/demo/sql/demo.sql-lib.xml")
public interface DemoMapper {
  DemoEntity findFirstByName(@Name("name") String name);
}

1.3 在beans.xml注册Mapper Bean

<bean id="io.nop.demo.biz.DemoMapper"
      class="io.nop.orm.sql_lib.proxy.SqlLibProxyFactoryBean"
      ioc:type="@bean:id" ioc:bean-method="build">
  <property name="mapperClass" value="@bean:type"/>
</bean>
  • 经过Excel模型生成代码时,假如数据表具有mapper标签,则会主动生成Mapper接口类以及上述Mapper Bean的界说。
MyBatis Nop渠道
经过XML装备动态SQL 经过统一的Delta定制完成装备批改
经过Mapper接口封装SQL的履行 Nop渠道运用统一的@Name注解界说参数名,经过IEvalContext来传递上下文目标
经过固定的几个标签函数生成动态SQL Nop渠道中经过Xpl标签库引进自界说标签
经过表达式生成SQL参数 表达式运用通用的表达式引擎,利用Xpl模板言语的SQL输出模式将输出的表达式成果转换为SQL参数
支撑事务、成果数据缓存等 利用Dao层的JdbcTemplate,主动支撑事务和成果缓存
管理SQL句子 一起管理EQL、SQL、DQL等各类查询言语

二. 为什么运用XML文件是一种优点

目前JPA和MyBatisPlus这种结构现已基本不运用XML装备格局,全部经过Java注解来完成装备。这导致许多人误以为XML格局现已完全过期,乃至看到运用XML格局的结构就会激烈对立。
可是这实际上是一种刻板印象和不正确的认知。

运用XML文件比较于注解存在许多好处。

2.1 调试时不必停机就能够调整SQL句子

Nop渠道中的一切模型文件都支撑动态加载,只要修正模型文件,或许它所依靠的文件,模型解析缓存会主动更新。所以在调试时远比运用Java注解便捷

2.2 经过Delta定制调整SQL句子

当sql-lib模型文件现已打包到Jar包中之后,无需修正的原始xml文件,在独立的delta模块的delta目录下添加一个同名文件即可覆盖根底模块中的文件。

而运用Java注解的情况下,咱们无法经过简略、通用的方法来定制jar包中的SQL句子。

<sql-lib x:extends="super">
  <sqls>
    <sql name="findUserRoles">
      <source>
        ...
      </source>
    </sql>
  </sqls>
</sql-lib>

咱们能够只定制指定的某个SQL句子,或许只定制某个指定特点。而在MyBatis中定制mapper文件的时分只能全体替换。

关于Delta定制的具体介绍,拜见如何在不修正根底产品源码的情况下完成定制化开发

2.3 无代码开发能够在线调整SQL句子

Nop渠道中一切的模型文件统一运用虚拟文件体系进行管理,而虚拟文件体系能够将数据库中的某个装备表也当作虚拟文件来处理。经过这种方法,咱们能够完成在界面上装备SQL句子,
而编程时能够将它看作是一个普通的sql-lib模型文件,并且复用Nop渠道内置的模型缓存、模型依靠关系追踪等才能。

2.4 二次笼统才能

Nop渠道的模型文件加载时支撑元编程处理,并且生成SQL句子时运用的XPL模板言语也支撑自界说标签支撑机制。这使得咱们能够轻松发现SQL构造中的通用模式,并供给自界说的笼统。
比方说咱们发现一个SQL片段经常出现,能够用自界说标签库将它笼统为一个函数,而运用Java注解,一般咱们只能运用结构内置的笼统,没有进一步简化装备的可能性。

2.5 依据元模型生成IDE提示和可视化规划器

MyBatis的IDE插件需要单独去编写。而在Nop渠道中,任何DSL文件只要经过x:schema引进对应的元模型,经过通用的nop-idea-plugin
插件即可主动推导得到语法提示、断点调试等功用。
相似的,能够依据元模型主动推导得到在线可视化规划器,直接规划对应的DSL文件。

三. 强壮的XPL模板言语

MyBatis的一个根本性规划问题在于它只供给了少数内置的标签,在实际运用过程中明显能够感觉到笼统才能不足。
在Nop渠道中,咱们运用XPL模板言语来生成SQL句子,能够经过XPL标签库来引进无限多的自界说笼统。

<sql-lib>
  <x:config>
    <c:import from="/nop/orm/xlib/sql.xlib"/>
  </x:config>
  <sqls>
    <sql name="findWithDialect">
      <arg name="product"/>
      <source>
        select
        <sql:fragment id="colList"/>
        from my_entity
        where 1=1
        <sql:when-dialect name="h2">
          and a = 1
        </sql:when-dialect>
        <sql:filter>and o.classId in (:ids)</sql:filter>
        <c:if test="${product.main}">
          <c:script>
            import app.MyHelper;
          </c:script>
          and b > ${MyHelper.getXXX(product)}
        </c:if>
      </source>
    </sql>
  </sqls>
</sql-lib>
  • <sql:fragment><sql:when-dialect>这样的标签都是sql.xlib标签库中自界说的标签,并不是引擎中内置的功用。
  • 咱们添加更多事务相关的标签,比方 <app:FilterTopProduct/>等。
  • XPL模板言语内置了<c:if><c:for>等很多语法结构,支撑相似于JavaScript的表达式语法。能够直接经过import导入java类

三. 面向OLAP的主子表查询

润乾公司开源了一个前端BI体系,它在技术层面提出了一个特别的DQL(Dimentinal Query
Language)言语。具体介绍能够参阅乾学院的文章

离别宽表,用 DQL 成就新一代 BI – 乾学院

润乾的观念是终端用户难以理解杂乱的SQL JOIN,为了便于多维分析,只能运用大宽表,这为数据准备带来一系列困难。而DQL则是简化了对终端用户而言JOIN操作的心智模型,并且在功能上比较于SQL更有优势。

例如,运用DQL能够简化主子表相关的汇总查询

-- SQL
SELECT T1.订单编号,T1.客户,SUM(T2.价格)
FROM 订单表T1
JOIN 订单明细表T2 ON T1.订单编号=T2.订单编号
GROUP BY T1.订单编号,T1.客户
-- DQL
SELECT 订单编号,客户,订单明细表.SUM(价格)
FROM 订单表

Nop渠道经过QueryBean笼统完成了相似于DQL的这种组合查询才能

<sql-lib>
  <sqls>
    <query name="findCustomStats">
      <source>
        <fields>
          <field name="orderNo"/>
          <field name="customer"/>
          <field owner="orderDetails" name="price" aggFunc="sum"/>
        </fields>
        <sourceName>Order</sourceName>
      </source>
    </query>
  </sqls>
</sql-lib>

能够在前端供给一个可视化规划器直接规划query目标。

在Java代码中

QueryBean query = new QueryBean();
query.addFeld(mainField("orderNo"), mainField("customer"),
   subField("orderDetials","price").sum());
query.setSourceName("Order");

四. 更多MyBatis和JPA不具备的高级功用

4.1 批量加载相关特点

支撑相关调集的ORM引擎很容易发生N+1问题,在sql-lib文件中能够装备batchLoadSelection完成相似GraphQL的批量加载机制,削减数据库拜访次数。

<eql name="findBySqlFilter">
  <batchLoadSelection>
    simsCollege { simsClasses }
  </batchLoadSelection>
  <source>
    select o
    from SimsClass o
    where 1=1
    <sql:filter>and o.classId in (:ids)</sql:filter>
  </source>
</eql>

4.2 启用数据权限过滤

敞开enableFilter特点为true之后,会针对每个实体目标主动追加对应的数据权限过滤条件。假如结合NopORM引擎内置的IEqlAstTransformer机制,能够对EQL进行严厉的格局查看和权限限制。

<eql name="findFirstByName" enableFilter="true" sqlMethod="findFirst">
    <source>
        select u from NopAuthUser u where u.userName like ${'%' + name + '%'}
    </source>
</eql>

一般情况下因为考虑到安全性问题,咱们并不会把EQL言语的修正权限开放给用户,可是借助于enableFilter和astTransformer机制,咱们能够有效的限制用户运用EQL时拜访的数据范围,杜绝SQL注入攻击。

4.3 多数据源支撑

经过querySpace属功能够指定运用不同的DataSource,然后拜访不同的数据库

<sql querySpace="report">
  ...
</sql>

beans.xml中需要注册对应的DataSource, bean的id的格局为nopDataSource_{querySpace}

<bean id="nopDataSource_report"  class="com.zaxxer.hikari.HikariDataSource">
  ...
</bean>

4.4 运用Native SQL查询得到实体目标

一般情况下咱们运用<eql>节点来加载实体数据。可是假如设置rowType为实体类型,则也能够运用<sql>节点来加载实体数据。

回来成果包装为实体目标后,会主动供给相关特点延迟加载功用。

  <sql name="testOrmEntityRowMapper" rowType="io.nop.app.SimsClass" sqlMethod="findFirst"
       colNameCamelCase="true" >
      <source>
          select o.class_id, o.class_name, o.college_id
          from sims_class o
      </source>
  </sql>
  • 设置了colNameCamelCase会主动将class_id这样的回来字段名转换为classId这样的实体特点名
  • 假如SQL句子回来的成果中没有包括主键字段,则会新建实体目标,否则会依据id加载当时OrmSession中的实体,并更新实体上的特点。
  • 假如履行SQL之前对应的实体数据现已加载到内存中,且现已被修正,则履行SQL会抛出异常nop.err.orm.entity-prop-is-dirty。假如没有被修正,则会更新实体特点。
  • 能够经过ormEntityRefreshBehavior来改变上面的行为。errorWhenDirty是缺省行为。useFirst将保存第一次加载的实体数据,忽略当时SQL查询得到的数据。useLast则运用最后一次查询得到的数据。

更具体的介绍拜见sql-lib.md

4.5 直接作为数据字典

假如sql的称号以_dict为后缀,则能够经过DictProvider来调用它,获取到的成果被包装为DictBean目标。

 DictBean dict = DictProvider.instance().getDict(null, "sql/test.demo_dict", null, scope);

SQL句子要求有必要包括value和label字段

<eql name="demo_dict">
    <source>
        select o.collegeId as value, o.collegeName as label
        from SimsCollege o
    </source>
</eql>

依据可逆核算理论规划的低代码渠道NopPlatform已开源: