@param注解_启用 parameters 编译选项简化 mybatis @Param 注解重复问题

在使用 mybatis 查询的时候, 只需要定义一个查询接口, mybatis 会为我们注入注解实现或是 xml 实现. 但当我们需要传递参数时, 通常需要 @Param 来定义一个名称, 但经常的, 我们也不难发现, 这个名称与参数名称通常是一样的:

User findUser(@Param("username") String username, @Param("password") String password);

如上, username 和 password 都重复了.

而之所以要这么使用, 是因为 xml 中 ${xxx} 所引用的名称就来自于 @Param 里定义的值:

<select id="findUser" resultType="net.xiaogd.demo.mybatis.entity.User">
select * from user where username = #{username} and password = #{password}
select>

这就带来一个重复的问题, 可否简化这个定义, 使得无需重复录入名称, 甚至完全地去掉呢? 比如像下面这样:

User findUser(String username, String password);

答案是可以的, 下面就来说下怎么去做到这一点.

前置条件

  1. 首先项目需要使用 jdk8 或以上;

  2. 其次, 需要增加一个编译时的选项 -parameters.

    也即是这样去编译: javac -parameters

通常, 如果没有加上这个选项, 编译后的方法参数签名会变成这样:

User findUser(String arg0, String arg1);

实际的名称会变成如上所示的 arg0arg1 这样没有太多含义的, 毕竟解析器并不关心实际的名称, 有含义的名称只是给人阅读的而已.

下面就说说怎么去引入这个编译选项以使得可以保留有意义的参数名, 包括 maven 中的设置及 IDE 中的设置(包括 Intellij IDEA 和 Eclipse)

maven 编译时的选项

对于 maven, 可以在编译插件 maven-compiler-plugin 中使用 compilerArgs 增加参数来实现:

<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<compilerArgs>
<arg>-parametersarg>
compilerArgs>
configuration>
plugin>

注: 对于较新的 maven 版本(>= 3.6.2), 也可以直接使用 parameters 配置项:

<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<parameters>trueparameters>
configuration>
plugin>

参见: https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#parameters

另注: 如果你使用 spring boot 2.0 及以后的版本并依赖了 spring-boot-starter-parent, 默认情况下已经启用了这一选项:

参见这里的说明: https://stackoverflow.com/questions/31845676/how-to-compile-spring-boot-applications-with-java-8-parameter-flag/49316086#49316086

你可以通过查看最终生效的 Effective POM 来确认这一点, 对于 Eclipse, 在打开的 pom.xml 文件下方选项卡中选择"Effective POM", 然后搜索 maven-compiler-plugin 关键字可以找到相关配置: e0575346a8195a01c1d75155d3327bdf.png

对于 Intellij IDEA, 在 maven 窗口中 右键--Show Effective POM: 236f991eb9ecee9008253beb40975088.png

或者通过实际是否正常运行来确认这一点, 如果不是很确定, 当然你可以如上所述在自己的 pom.xml 文件中显式地配置上它.

IDE 编译时保留参数名称

说完了 maven 中的配置, 下面再说说在 IDE 中的类似设置.

注: 通常, 如果 maven 中设置了相应选项, 在项目作为 maven 项目导入并构建时, 这些额外的设置也会生效, 无需额外再作设置. 但考虑到 IDE 的版本及可能存在 bug 等各类原因, 如果在 IDE 中运行不正常, 那么则需要额外检查及配置.

Intellij IDEA 中的设置 -parameters

对于 IDEA, 在下述位置 Settings > Build, Execution, Deployment > Compiler > Java Compiler > Additional command line parameters(额外的命令行参数) 的输入框中, 输入-parameters:

469cbda7dec86adb4f3d6530129e13ed.png

参考: https://stackoverflow.com/questions/39217830/how-to-use-parameters-javac-option-in-intellij

如前所述, 如果没有设置也运行正常, 则不必去设置.

Eclipse 中的设置 -parameters

对于 Eclipse, 则是检查 Store information about method parameters 选项, 看看是否已经是勾选上, 如果没有, 则把它勾上:

d4525881a8da48330ae5bba19069f425.png

如前所述, 如果没有勾选也运行正常或者默认已经勾选上了, 则不必再去勾选.

另注: 因为以上设置涉及编译, 所以通常需要重新编译项目(如果设置后没有自动触发 rebuild), 如果还不生效, 甚至可能需要重启 IDE.

没有配置成功时的异常

如果没有加入 -parameters 选项或因其它原因没有启用成功, 则去掉 @Param 注解后可能会遇到下述异常:

exception: org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'username' not found. Available parameters are [arg1, arg0, param1, param2]

完整的异常如下:

exception: 
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'username' not found. Available parameters are [arg1, arg0, param1, param2]
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:77)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)
at com.sun.proxy.$Proxy78.selectList(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:230)
at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:137)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:75)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
at com.sun.proxy.$Proxy86.findUserByUsernameAndPassword(Unknown Source)
at net.xiaogd.demo.mybatis.dao.user.UserDaoTest.testXmlDao(UserDaoTest.java:76)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:542)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:770)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
Caused by: org.apache.ibatis.binding.BindingException: Parameter 'username' not found. Available parameters are [arg1, arg0, param1, param2]
at org.apache.ibatis.binding.MapperMethod$ParamMap.get(MapperMethod.java:202)
at org.apache.ibatis.reflection.wrapper.MapWrapper.get(MapWrapper.java:45)
at org.apache.ibatis.reflection.MetaObject.getValue(MetaObject.java:122)
at org.apache.ibatis.executor.BaseExecutor.createCacheKey(BaseExecutor.java:219)
at org.apache.ibatis.executor.CachingExecutor.createCacheKey(CachingExecutor.java:146)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:82)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)
... 38 more

那么, 按照其提示, 可以将相应的 xml 语句调整为:

<select id="findUser" resultType="net.xiaogd.demo.mybatis.entity.User">
select * from user where username = #{arg0} and password = #{arg1}
select>

或者是使用 param1, param2 这样的名称(注意, 与 arg 从 0 编号不同, 这里是从 1 开始编号)

<select id="findUser" resultType="net.xiaogd.demo.mybatis.entity.User">
select * from user where username = #{param1} and password = #{param2}
select>

自然, 使用这些没有太多含义的编号参数名, 代码的可读性就差了很多, 参数是否正确对上了也不容易看出来.

mybatis 版本及 useActualParamName(use-actual-param-name) 的问题

最后, 还有一个配置 useActualParamName(使用实际的参数名称) 可能导致一些异常, 这点与 mybatis 不同版本的缺省配置不同有关, 也与项目本身是否显式配置了这一参数值有关.

在没有启用 -parameters 以保留方法参数名并且没有用 @Param 设置一个有效的名称时, 有时你可能会发现使用 arg0 也还是提示找不到参数:

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'arg0' not found. Available parameters are [0, 1, param1, param2]

完整的异常如下:

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'arg0' not found. Available parameters are [0, 1, param1, param2]
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:77)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)
at com.sun.proxy.$Proxy78.selectList(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:230)
at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:137)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:75)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
at com.sun.proxy.$Proxy86.findUserByUsernameAndPassword(Unknown Source)
at net.xiaogd.demo.mybatis.dao.user.UserDaoTest.testXmlDao(UserDaoTest.java:76)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:542)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:770)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
Caused by: org.apache.ibatis.binding.BindingException: Parameter 'arg0' not found. Available parameters are [0, 1, param1, param2]
at org.apache.ibatis.binding.MapperMethod$ParamMap.get(MapperMethod.java:202)
at org.apache.ibatis.reflection.wrapper.MapWrapper.get(MapWrapper.java:45)
at org.apache.ibatis.reflection.MetaObject.getValue(MetaObject.java:122)
at org.apache.ibatis.executor.BaseExecutor.createCacheKey(BaseExecutor.java:219)
at org.apache.ibatis.executor.CachingExecutor.createCacheKey(CachingExecutor.java:146)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:82)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)
... 38 more

而这是由于 mybatis 版本及 useActualParamName(use-actual-param-name) 选项设置的原因.

在早期的版本中(< 3.4.1), useActualParamName 选项的默认值是 false, 而之后的版本(>= 3.4.1), 这个选项的默认值则是 true.

只有设置为 true, 才能利用 -parameters 配置带来的好处.

或者就是项目本身配置了其它不同于缺省的值, 如果使用了 spring boot 项目, 可以检查如下选项:

mybatis.configuration.use-actual-param-name=false

或是在 xml 配置文件中设置:

<setting name="useActualParamName" value="false" />

如果设置了 false, 那么就要写成 #{0}, #{1} 这样:

<select id="findUser" resultType="net.xiaogd.demo.mybatis.entity.User">
select * from user where username = #{0} and password = #{1}
select>

如果以前有大量这样的写法, 而你为了兼容它们不想去调整, 那你就无法利用 -parameters 配置带来的好处.

无论是使用 {0}, {1}, 还是使用 {arg0}, {arg1}, 可读性都不是很好, 而且在后续如果需要增加参数, 还容易引入错误, 因此并不推荐这样的写法.

关于启用 -parameters 编译选项简化 mybatis @Param 注解重复问题就介绍到这里.

注:下文中的 *** 代表文件名中的组件名称。 # 包含: 中文-英文对照文档:【***-javadoc-API文档-中文(简体)-英语-对照版.zip】 jar包下载地址:【***.jar下载地址(官方地址+国内镜像地址).txt】 Maven依赖:【***.jar Maven依赖信息(可用于项目pom.xml).txt】 Gradle依赖:【***.jar Gradle依赖信息(可用于项目build.gradle).txt】 源代码下载地址:【***-sources.jar下载地址(官方地址+国内镜像地址).txt】 # 本文件关键字: 中文-英文对照文档,中英对照文档,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压 【***.jar中文文档.zip】,再解压其中的 【***-javadoc-API文档-中文(简体)版.zip】,双击 【index.html】 文件,即可用浏览器打开、进行查看。 # 特殊说明: ·本文档为人性化翻译,精心制作,请放心使用。 ·本文档为双语同时展示,一行原文、一行译文,可逐行对照,避免了原文/译文来回切换的麻烦; ·有原文可参照,不再担心翻译偏差误导; ·边学技术、边学英语。 ·只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; ·不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 # 温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值