JSR-308和Checker框架为jOOQ 3.9添加了更多类型安全性

Java 8引入了JSR-308,它为Java语言添加了新的注释功能。 最重要的是:键入注释。 现在可以像下面这样设计怪物了:

该推文中显示的代码确实可以编译。 现在可以注释每种类型,以便以任何自定义方式增强类型系统。 为什么,你可能会问? 这种语言增强的主要驱动用例之一是checker框架 ,这是一个开放源代码库,可让您轻松实现任意编译器插件以进行复杂的类型检查。 最无聊和琐碎的例子是可空性。 考虑以下代码:

import org.checkerframework.checker.nullness.qual.Nullable;

class YourClassNameHere {
    void foo(Object nn, @Nullable Object nbl) {
        nn.toString(); // OK
        nbl.toString(); // Fail
        if (nbl != null)
            nbl.toString(); // OK again
    }
}

上面的示例可以直接在checker框架实时演示控制台中运行 。 使用以下注释处理器编译以上代码:

javac -processor org.checkerframework.checker.nullness.NullnessChecker afile.java

产量:

错误:[dereference.of.nullable]取消引用可能为空的引用nbl:5:9

太棒了! 例如,它的工作方式与在CeylonKotlin中 实现流敏感类型非常相似,不同之处在于它更为冗长。 但是它也要强大得多,因为可以使用注释处理器直接在Java中实现实现增强的和带注释的Java类型系统的规则! 通过某种方式使注解图灵完整。��

这对jOOQ有什么帮助?

jOOQ已经提供了两种类型的API文档注释。 这些注释是:

  • @PlainSQL –表示DSL方法接受“纯SQL”字符串,这可能会带来SQL注入风险
  • @Support –表示DSL方法可以本机工作,或者可以针对给定的SQLDialect集进行仿真

这种方法的一个示例是CONNECT BY子句 ,该子句得到Cubrid,Informix和Oracle的支持,为了方便起见,它也被重载为也接受“普通SQL”谓词:

@Support({ CUBRID, INFORMIX, ORACLE })
@PlainSQL
SelectConnectByConditionStep<R> connectBy(String sql);

到目前为止,这些注释仅用于文档目的。 使用jOOQ 3.9后,不再可用。 现在,我们向jOOQ API引入了两个新的注释:

  • org.jooq.Allow –允许在给定范围内使用一组方言(或@PlainSQL批注)
  • org.jooq.Require –在给定范围内要求通过@Support注释支持一组方言

最好通过示例来解释。 让我们先看看@PlainSQL

限制对

使用jOOQ API的最大优点之一就是SQL注入已经成为过去。 由于jOOQ是内部特定于域的语言,因此用户确实可以直接在Java代码中直接定义SQL表达式树,而不是像JDBC那样使用声明的字符串化版本。 表达式树是用Java编译的,因此不可能通过用户输入注入任何不需要的或无法预见的表达式。

但是有一个例外。 jOOQ并不支持每个数据库中的所有SQL功能。 这就是jOOQ附带丰富的“普通SQL” API的原因,在该API中,可以将自定义SQL字符串嵌入SQL表达式树中的任何位置。 例如,上面的CONNECT BY子句:

DSL.using(configuration)
   .select(level())
   .connectBy("level < ?", bindValue)
   .fetch();

上面的jOOQ查询转换为以下SQL查询:

SELECT level
FROM dual
CONNECT BY level < ?

如您所见,完全有可能“做错”并产生SQL注入风险,就像在JDBC中一样:

DSL.using(configuration)
   .select(level())
   .connectBy("level < " + bindValue)
   .fetch();

区别非常细微。 使用jOOQ 3.9和checker框架,现在可以指定以下Maven编译器配置:

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.3</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <fork>true</fork>
        <annotationProcessors>
            <annotationProcessor>org.jooq.checker.PlainSQLChecker</annotationProcessor>
        </annotationProcessors>
        <compilerArgs>
            <arg>-Xbootclasspath/p:1.8</arg>
        </compilerArgs>
    </configuration>
</plugin>

org.jooq.checker.PlainSQLChecker将确保不会编译使用带有@PlainSQL注释的API的客户端代码。 我们收到的错误消息是这样的:

C:\ Users \ lukas \ workspace \ jOOQ \ jOOQ-examples \ jOOQ-checker-framework-example \ src \ main \ java \ org \ jooq \ example \ checker \ PlainSQLCheckerTests.java:[17,17]错误:[普通]当前范围不允许使用SQL。 使用@ Allow.PlainSQL。]

如果您知道自己在做什么,并且绝对必须在非常特定的位置(范围)使用jOOQ的@PlainSQL API,则可以使用@Allow.PlainSQL对该位置(范围)进行注释,并且代码可以再次正常编译:

// Scope: Single method.
@Allow.PlainSQL
public List<Integer> iKnowWhatImDoing() {
    return DSL.using(configuration)
              .select(level())
              .connectBy("level < ?", bindValue)
              .fetch(0, int.class);
}

甚至:

// Scope: Entire class.
@Allow.PlainSQL
public class IKnowWhatImDoing {
    public List<Integer> iKnowWhatImDoing() {
        return DSL.using(configuration)
                  .select(level())
                  .connectBy("level < ?", bindValue)
                  .fetch(0, int.class);
    }
}

甚至(但是您可能只是关闭检查器):

// Scope: entire package (put in package-info.java)
@Allow.PlainSQL
package org.jooq.example.checker;

好处是显而易见的。 如果安全性对您非常重要(应该如此),则只需在每个开发人员版本或至少在CI版本中启用org.jooq.checker.PlainSQLChecker ,并在“偶然”使用@PlainSQL API时获得编译错误遇到。

限制对

现在,对于大多数用户而言,更有趣的是能够检查客户端代码中使用的jOOQ API是否确实支持您的数据库。 例如,上面的CONNECT BY子句仅在Oracle中受支持(如果我们忽略不太流行的Cubrid和Informix数据库)。 假设您仅使用Oracle。 您要确保您使用的所有jOOQ API都与Oracle兼容。 现在,您可以将以下注释添加到所有使用jOOQ API的软件包中:

// Scope: entire package (put in package-info.java)
@Allow(ORACLE)
package org.jooq.example.checker;

现在,只需激活org.jooq.checker.SQLDialectChecker来键入代码以检查@Allow符合性,即可完成:

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.3</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <fork>true</fork>
        <annotationProcessors>
            <annotationProcessor>org.jooq.checker.SQLDialectChecker</annotationProcessor>
        </annotationProcessors>
        <compilerArgs>
            <arg>-Xbootclasspath/p:1.8</arg>
        </compilerArgs>
    </configuration>
</plugin>

从现在开始,每当您使用任何jOOQ API时,上述检查器都将验证以下三个值是否为true:

  • 正在使用的jOOQ API未使用@Support注释
  • 使用的jOOQ API带有@Support注释,但没有任何显式的SQLDialect (即“可在所有数据库上工作”),例如DSLContext.select()
  • 使用的jOOQ API带有@Support注释,并带有SQLDialects引用的至少一个@Allow

因此,在这样标注的包装中……

// Scope: entire package (put in package-info.java)
@Allow(ORACLE)
package org.jooq.example.checker;

……使用这样注释的方法就可以了:

@Support({ CUBRID, INFORMIX, ORACLE })
@PlainSQL
SelectConnectByConditionStep<R> connectBy(String sql);

…但是使用这样注释的方法不是:

@Support({ MARIADB, MYSQL, POSTGRES })
SelectOptionStep<R> forShare();

为了允许使用此方法,例如,客户端代码除了可以使用ORACLE语言外,还可以使用MYSQL语言:

// Scope: entire package (put in package-info.java)
@Allow({ MYSQL, ORACLE })
package org.jooq.example.checker;

从现在开始,此程序包中的所有代码都可能引用支持MySQL和/或Oracle的方法。

@Allow批注有助于在全局级别上访问API。 多个@Allow注释(范围可能不同)创建了允许的方言的@Allow取关系,如下所示:

// Scope: class
@Allow(MYSQL)
class MySQLAllowed {

    @Allow(ORACLE)
	void mySQLAndOracleAllowed() {
	    DSL.using(configuration)
		   .select()
		   
		   // Works, because Oracle is allowed
		   .connectBy("...")
		   
		   // Works, because MySQL is allowed
		   .forShare();
	}
}

从上面可以看出,析取两个方言不能确保给定的语句在两个数据库中都可以使用。 所以…

如果我希望同时支持两个数据库怎么办?

在这种情况下,我们将使用新的@Require注释。 多个@Require注释(范围可能不同)创建所需方言的合集,如下所示:

// Scope: class
@Allow
@Require({ MYSQL, ORACLE })
class MySQLAndOracleRequired {

    @Require(ORACLE)
	void onlyOracleRequired() {
	    DSL.using(configuration)
		   .select()
		   
		   // Works, because only Oracle is required
		   .connectBy("...")
		   
		   // Doesn't work because Oracle is required
		   .forShare();
	}
}

如何使用

假设您的应用程序仅需要与Oracle一起使用。 现在,您可以在软件包上添加以下注释,例如,由于在您的代码中不允许将MySQL作为方言,因此您将无法使用任何仅MySQL的API:

@Allow(ORACLE)
package org.jooq.example.checker;

现在,随着需求的变化,您还希望从应用程序中也开始支持MySQL。 只需将软件包规格更改为以下内容,然后开始修复jOOQ使用中的所有编译错误。

// Both dialects are allowed, no others are
@Allow({ MYSQL, ORACLE })

// Both dialects are also required on each clause
@Require({ MYSQL, ORACLE })
package org.jooq.example.checker;

默认值

默认情况下,对于任何范围, org.jooq.checker.SQLDialectChecker都采用以下注释:

  • 什么都不允许。 每个@Allow批注都会添加到允许的方言集中。
  • 一切都是必需的。 每个@Require批注将从必填方言集中删除。

实际观看

这些功能将是jOOQ 3.9的组成部分。 只需添加以下依赖项即可使用它们:

<dependency>
    <!-- Use org.jooq            for the Open Source edition
             org.jooq.pro        for commercial editions, 
             org.jooq.pro-java-6 for commercial editions with Java 6 support,
             org.jooq.trial      for the free trial edition -->
	
    <groupId>org.jooq</groupId>
    <artifactId>jooq-checker</artifactId>
    <version>${org.jooq.version}</version>
</dependency>

…,然后为您的编译器插件选择适当的注释处理器。

不能等到jOOQ 3.9吗? 不用了 只需从GitHub上检查3.9.0-SNAPSHOT版本,然后按照此处给出的示例项目进行操作:

做完了! 从现在开始,使用jOOQ时,您可以确保编写的任何代码都可以在计划支持的所有数据库上运行!

我认为,今年的Annotatiomaniac冠军头衔应该交给检查框架的制定者:

有关检查器框架的更多信息:

翻译自: https://www.javacodegeeks.com/2016/05/jsr-308-checker-framework-add-even-typesafety-jooq-3-9.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值