使用MyBatis映射输入和输出参数

zap*_*pee 2 java postgresql mybatis mybatis-generator spring-mybatis

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

例如,我喜欢在插入@SelectKey注释之前执行查询的难易程度。和数据映射就像一个魅力,如果我的接口方法之前添加一些注释,像这样:@Results({ @Result(property = "javaField", column = "database_field", javaType = TypeHandler.class)

以下是我不喜欢的(希望您能将我带往正确的方向):


(问题1)我有一些查询,这些查询使我可以使用null和正常值,而无需任何其他“ if” java语句来检查变量是否包含null值。他们看起来像这样:

SELECT * FROM table
WHERE key_name = ? AND ((? IS NULL AND user_id IS NULL) OR User_id = ?) 
Run Code Online (Sandbox Code Playgroud)

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

stmt = connection.prepareStatement(query);
stmt.setString(1, "key");
stmt.setString(2, userId);
stmt.setString(3, userId);
Run Code Online (Sandbox Code Playgroud)

如您所见,我需要传递两次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);
Run Code Online (Sandbox Code Playgroud)

我实际上所做的解决方法如下。我讨厌它,因为它很棘手,并且需要附加的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);
Run Code Online (Sandbox Code Playgroud)

我不知道用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);
Run Code Online (Sandbox Code Playgroud)

第二查询:

@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);
Run Code Online (Sandbox Code Playgroud)

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


(问题3) @Param注释在注释文档中我什么都看不到@Param。很难弄清楚为什么我的java参数没有正确绑定。最终,我意识到@Param代码中缺少注释。为什么官方文档中未提及此内容?我以错误的方式做了某件事,@Param不需要使用吗?

该代码可以正常工作:

SomeClass findByKeyAndUserId(@Param("key") String key, @Param("userId") Long userId);
Run Code Online (Sandbox Code Playgroud)

这不起作用:

SomeClass findByKeyAndUserId(String key, Long userId);
Run Code Online (Sandbox Code Playgroud)

更新(问题1)

我的第一个想法与@blackwizard提到的想法类似:Mybatis 确实按名称绑定参数,然后一次,两次,N次(如所引用的时间一样)起作用。”

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

例外:

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
Run Code Online (Sandbox Code Playgroud)

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

(问题1:解决方案1)

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

(问题1:解决方案2) 之所以could not determine data type of parameter $2从postgres中获取错误是因为,如果使用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);
Run Code Online (Sandbox Code Playgroud)

(问题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);
Run Code Online (Sandbox Code Playgroud)

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

bla*_*ard 5

问题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);
Run Code Online (Sandbox Code Playgroud)

Mybatis确实按名称绑定参数,然后按引用的时间绑定一次,两次,N次。

您可以在Mybatis中使用if标记,如果使用XML标记,虽然效果并不好,但总有一天您可以将其重用于其他用途,这是一个好技巧。要在注释值中使用XML标签,该值必须<script>准确地嵌入在字符串的开头和结尾的标签中。

@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);
Run Code Online (Sandbox Code Playgroud)

您还可以使用typeHandler设置默认值,只需将其与参数一起使用: #{userId, typeHandler=CustomDefaultValueTypeHandler}

编辑:答复其他问题:如果要允许传递空值而不是处理默认值的替换,则必须给Mybatis一些有关假定的绑定参数类型的提示,因为它不能解析实际的空值类型,因为它不知道/请参阅Mapper界面中的变量声明。因此:#{userId, javaType=int,jdbcType=NUMERIC}。这两个属性中只有一个就足够了。

文档状态:

像MyBatis的其余部分一样,javaType几乎总是可以从参数对象确定的,除非该对象是HashMap。然后 java类型应规定以确保正确的类型处理器使用。

注意如果将null作为值传递,则JDBC对于所有可为空的列都需要JDBC类型。您可以通过阅读PreparedStatement.setNull()方法的JavaDocs自己进行调查。

问题2:您绝对不能重用/相互化注释中定义的内容。这不是因为Mybatis,而是因为注释。您将必须使用@ResultMap在XML中定义引用结果的映射。文档状态:

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.
Run Code Online (Sandbox Code Playgroud)

问题3:正如我在您的其他问题中已经回答的那样,@ Param批注可以将便捷的参数列表转换为映射,例如:

Map<String, Object> params = new HashMap<String, Object>();
params.put("key", key);
params.put("userId", userId);
Run Code Online (Sandbox Code Playgroud)

我同意Mybatis文档可能会更好,并且您会在此处找到更多资源。

但是,Mybatis文档指出有关@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}.
Run Code Online (Sandbox Code Playgroud)