【问题标题】:mapping input and output parameters with MyBatis使用 MyBatis 映射输入和输出参数
【发布时间】:2017-02-20 17:36:19
【问题描述】:

我正在学习如何使用MyBatis。老实说,我非常喜欢这个框架。它易于使用,我很满意,因为我可以使用我的 sql 命令:) 我使用 MyBatis 3.4.2 和 PostgreSQL 数据库。

例如,我喜欢在使用 @SelectKey 注释插入之前执行查询是多么容易。如果我在接口方法之前添加一些注释,数据映射就像这样:@Results({ @Result(property = "javaField", column = "database_field", javaType = TypeHandler.class)

我不喜欢的(希望你能把我引向正确的方向)如下:


(问题 1) 我有一些查询允许我使用 null 和正常值,而无需任何额外的“if”java 语句来检查变量是否包含 null 值或不包含 null 值。它们看起来像这样:

SELECT * FROM table
WHERE key_name = ? AND ((? IS NULL AND user_id IS NULL) OR User_id = ?) 

使用 JDBC 我需要执行以下操作:

stmt = connection.prepareStatement(query);
stmt.setString(1, "key");
stmt.setString(2, userId);
stmt.setString(3, userId);

如您所见,我需要传递两次 userId,因为这是 JDBC 的工作方式。老实说,我的期望是下面的代码可以与 MyBatis 一起使用,但不幸的是它不起作用。第三个参数还需要定义。

我想知道是否可以将这个功能添加到 MyBatis 中。如果 MyBatis 可以自动绑定两次 userId 应该没问题,像这样:

@Select("SELECT * FROM table key_name = #{key} and ((#{userId} is null and user_id is null) OR user_id = #{userId})
SomeClass findByKeyAndUserId(String key, Long userId);

实际上我所做的解决方法如下。我讨厌它,因为它很棘手并且需要额外的 java "if" 语句:

@Select("SELECT * FROM table WHERE key_name = #{key} AND COALESCE(user_id, -1) = #{userId}")
SomeClass findByKeyAndUserId(String key, Long userId);

userId = (userId == null) ? -1 : userId;
SomeClass abc = mapper.findByKeyAndUserId(key, userId);

我不知道用 MyBatis 管理这种情况的最佳做法是什么。请指导我。


(问题 2) 映射@Select。有什么办法可以避免在映射相同结果类型的查询结果时重复代码?

第一个查询:

@Select("SELECT * FROM table WHERE ...")
@Results({
        @Result(property = "key", column = "key_name", javaType = String.class),
        @Result(property = "value", column = "key_value", javaType = String.class),
        @Result(property = "userId", column = "user_id", javaType = Long.class),
        @Result(property = "interval", column = "interval", javaType = Long.class),
        @Result(property = "description", column = "description", javaType = String.class),
        @Result(property = "status", column = "status", typeHandler = StatusTypeHandler.class)
})
SomeClass findByKeyAndUserId(@Param("key") String key, @Param("userId") Long userId);

第二次查询:

@Select("SELECT * FROM table WHERE <different conditions then before>")
@Results({
        <I need to add here the exact same code then before in query 1>
})
SomeClass findByKeyAndUserId(@Param("key") String key, @Param("userId") Long userId);

我可以以某种方式重用与映射相关的代码吗?我需要添加映射,因为我对状态字段使用特殊类型的处理程序。我使用基于注释的配置。


(问题 3) @Param 注释 我在文档中看不到有关 @Param 注释的任何内容。这很难弄清楚为什么我的 java 参数没有正确限制。最后我意识到我的代码中缺少@Param 注释。为什么官方文档中没有提到这一点?我做错了事,@Param 没必要用?

该代码运行良好:

SomeClass findByKeyAndUserId(@Param("key") String key, @Param("userId") Long userId);

这不起作用:

SomeClass findByKeyAndUserId(String key, Long userId);

更新(问题 1)

我的第一个想法与 @blackwizard 提到的类似:“Mybatis 确实按名称绑定参数,然后一次、两次、N 次,根据引用的时间,它可以工作。 "

但这实际上不能正常工作。如果 userId 不为空,它会起作用。如果它为空,我会得到一个从数据库返回的很好的异常。我猜 MyBatis 以错误的方式绑定 null 值。也许这是一个错误。我不知道:(

例外:

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.postgresql.util.PSQLException: ERROR: could not determine data type of parameter $2
### The error may exist in com/.../dao/TableDao.java (best guess)
### The error may involve ....dao.Table.findByKeyAndUserId-Inline
### The error occurred while setting parameters
### SQL: SELECT * FROM table WHERE key_name = ? AND (? IS NULL AND user_id IS NULL) OR user_id = ? AND status = 1
### Cause: org.postgresql.util.PSQLException: ERROR: could not determine data type of parameter $2

最后我找到了三种不同的解决方案。

(问题1:解决方案1)

我遵循 @blackwizard 提到的内容,我能够将条件 userId = (userId == null) ? -1 : userId 从 java 移动到 MyBatis 级别。而且还不错!正确的语法是:&lt;if test='userId==null'&gt;&lt;bind name='userId' value='-1'/&gt;&lt;/if&gt;

(问题1:解决方案2) 我从 postgres 中返回 could not determine data type of parameter $2 错误的原因是,在 null 值的情况下,JDBC 驱动程序无法确定参数的类型。所以让我们手动定义它。

@Select("SELECT * FROM table "
        + "WHERE key_name = #{key} AND ((#{userId}::BIGINT IS NULL AND user_id IS NULL) OR user_id = #{userId})")
})
SomeClass findByKeyAndUserId(@Param("key") String key, @Param("userId") Long userId);

(问题1:解决方案3) 第二种解决方案依赖于 porstgreSQL。以下解决方案完全独立于数据库。感谢@blackwizard 的精彩评论。

@Select("SELECT * FROM table "
        + "WHERE key_name = #{key} AND ((#{userId, jdbcType=BIGINT} IS NULL AND user_id IS NULL) OR user_id = #{userId})")
})
SomeClass findByKeyAndUserId(@Param("key") String key, @Param("userId") Long userId);

我个人更喜欢解决方案 3。它包含的额外代码更少。

【问题讨论】:

    标签: java postgresql mybatis spring-mybatis mybatis-generator


    【解决方案1】:

    问题3:

    请尝试自 JDK 8 以来提供的 -parameters 编译选项。 您可以省略 @Param 注释。

    https://github.com/mybatis/mybatis-3/issues/549

    谢谢。

    【讨论】:

      【解决方案2】:

      问题 1:

      为参数命名:

      @Select("SELECT * FROM table key_name = #{key} and ((#{userId} is null and user_id is null) OR user_id = #{userId}")
      SomeClass findByKeyAndUserId(@Param("key") String key, @Param("userId") Long userId);
      

      Mybatis 确实是按名称绑定参数,然后一次,两次,N次,不管是什么时候被引用,都有效。

      你可以在 Mybatis 中做 if: 使用 XML 标签,虽然它并不是真的更好......无论如何,这是一个很好的技巧,你有一天可以将其用于另一个目的。要在注释值中使用 XML 标记,该值必须嵌入到 &lt;script&gt; 标记中,正好位于字符串的开头和结尾。

      @Select({"<script>",
               "<if 'userId==null'><bind name='userId' value='1'/></if>",
               "SELECT * FROM table WHERE key_name = #{key} ", 
               "AND COALESCE(user_id, -1) = #{userId}", 
               "</script>"})
      SomeClass findByKeyAndUserId(@Param("key") String key, @Param("userId") Long userId);
      

      你也可以使用typeHandler来设置默认值,只要和参数一起使用:#{userId, typeHandler=CustomDefaultValueTypeHandler}

      编辑:回复其他问题:如果你想允许传递空值而不是处理默认值替换,那么你必须给 Mybatis 一些关于假定类型绑定参数的提示,因为它无法解析null 的实际类型,因为它不知道/查看 Mapper 接口中的变量声明。所以:#{userId, javaType=int,jdbcType=NUMERIC}。两个属性中的一个可能就足够了。

      Documentation 状态:

      像 MyBatis 的其余部分一样,javaType 几乎总是可以确定的 来自参数对象,除非该对象是 HashMap。然后 应指定 javaType 以确保正确的 TypeHandler 用过。

      注意 JDBC 要求所有可空列的 JDBC 类型,如果 null 作为值传递。您可以通过以下方式自行调查 阅读 PreparedStatement.setNull() 方法的 JavaDocs。

      问题 2:您绝对不能重用/共同化您在注释中定义的内容。这不是因为Mybatis,而是注解。您必须在使用 @ResultMap 引用它们的 XML 中定义结果映射。 Documentation 状态:

      ResultMap   Method  N/A     This annotation is used to provide the id of a <resultMap> element in an XML mapper to a @Select or @SelectProvider annotation. This allows annotated selects to reuse resultmaps that are defined in XML. This annotation will override any @Results or @ConstructorArgs annotation if both are specified on an annotated select.
      

      问题3:正如我已经answered in your other question,@Param 注释具有将方便的参数列表转换为映射的效果,例如:

      Map<String, Object> params = new HashMap<String, Object>();
      params.put("key", key);
      params.put("userId", userId);
      

      我同意 Mybatis 文档可以更好,您会发现更多资源,例如 here

      但是,Mybatis documentation 声明了以下关于 @Param 注释

      @Param  Parameter   N/A     If your mapper method takes multiple parameters, this annotation can be applied to a mapper method parameter to give each of them a name. Otherwise, multiple parameters will be named by their position prefixed with "param" (not including any RowBounds parameters). For example #{param1}, #{param2} etc. is the default. With @Param("person"), the parameter would be named
      #{person}.
      

      【讨论】:

      • 非常感谢您的回复。我为(问题 1)更新了我的原始帖子。你能检查一下吗?明天我会检查你的另外两个答案。
      • 第三期现在很清楚了。十分感谢!这就是为什么我有时会在异常日志中看到 paramX 绑定不正确的原因;)
      • 我更新了我的帖子。第一个问题完全解决了。感谢您的帮助!
      猜你喜欢
      • 1970-01-01
      • 2017-01-20
      • 1970-01-01
      • 2016-03-20
      • 1970-01-01
      • 1970-01-01
      • 2021-09-13
      • 2016-12-07
      • 2021-02-28
      相关资源
      最近更新 更多