使用 MyBatis 映射输入和输出参数
mapping input and output parameters with MyBatis
我正在学习如何使用 MyBatis。老实说,我非常喜欢这个框架。它易于使用,我对它很满意,因为我可以使用我的 sql 命令:) 我使用 MyBatis 3.4.2 和 PostgreSQL 数据库。
例如,我喜欢在使用 @SelectKey
注释插入之前执行查询是多么容易。如果我在接口方法之前添加一些注释,数据映射就像这样:@Results({ @Result(property = "javaField", column = "database_field", javaType = TypeHandler.class)
.
我不喜欢的(希望您能引导我正确的方向)如下:
(问题 1) 我有一些查询允许我使用空值和正常值而无需任何额外的 "if" java 语句来检查变量是否包含空值或非空值。它们看起来像这样:
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 does bind parameters by name, then once, twice, N times, as may time it is referenced, it works."
但这实际上并不能正常工作。如果 userId 不为空,它会起作用。如果它为空,我会得到一个从数据库返回的异常。我猜想 MyBatis 以错误的方式绑定了 null 值。也许这是一个错误。我不知道:(
异常:
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.postgresql.util.PSQLException: ERROR: could not determine data type of parameter
### 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
最后我找到了三种不同的解决方案。
(问题1:解决方案1)
我遵循了 @blackwizard 提到的,我能够将条件 userId = (userId == null) ? -1 : userId
从 java 移动到 MyBatis 级别。而且还不错!正确的语法是:<if test='userId==null'><bind name='userId' value='-1'/></if>
.
(问题1:解决方案2)
我从 postgres 返回 could not determine data type of parameter
错误的原因是因为在 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)
第二个解决方案取决于 PortgreSQL。以下解决方案完全独立于数据库。感谢@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。它包含的附加代码较少。
问题 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 标签,该值必须恰好嵌入字符串开头和结尾的 <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);
你也可以使用typeHandler来设置默认值,只需与参数一起使用:#{userId, typeHandler=CustomDefaultValueTypeHandler}
编辑:回复附加问题:如果你想允许传递空值而不是处理默认值替换那么你必须给 Mybatis 一些关于绑定参数的假定类型的提示因为它无法解析 null 的实际类型,因为它没有 know/see Mapper 接口中的变量声明。所以:#{userId, javaType=int,jdbcType=NUMERIC}
。这两个属性中只有一个就足够了。
Documentation 状态:
Like the rest of MyBatis, the javaType can almost always be determined
from the parameter object, unless that object is a HashMap. Then the
javaType should be specified to ensure the correct TypeHandler is
used.
NOTE The JDBC Type is required by JDBC for all nullable columns, if
null is passed as a value. You can investigate this yourself by
reading the JavaDocs for the PreparedStatement.setNull() method.
问题 2:您绝对不能 reuse/mutualize 您在注释中定义的内容。这不是因为Mybatis,而是注解。您将必须在 XML 中定义结果映射,并使用 @ResultMap 引用它们。 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:正如我已经,@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}.
问题 3:
请尝试使用自 JDK 8 以来提供的 -parameters 编译选项。
您可以省略 @Param 注释。
见https://github.com/mybatis/mybatis-3/issues/549
谢谢。
我正在学习如何使用 MyBatis。老实说,我非常喜欢这个框架。它易于使用,我对它很满意,因为我可以使用我的 sql 命令:) 我使用 MyBatis 3.4.2 和 PostgreSQL 数据库。
例如,我喜欢在使用 @SelectKey
注释插入之前执行查询是多么容易。如果我在接口方法之前添加一些注释,数据映射就像这样:@Results({ @Result(property = "javaField", column = "database_field", javaType = TypeHandler.class)
.
我不喜欢的(希望您能引导我正确的方向)如下:
(问题 1) 我有一些查询允许我使用空值和正常值而无需任何额外的 "if" java 语句来检查变量是否包含空值或非空值。它们看起来像这样:
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 does bind parameters by name, then once, twice, N times, as may time it is referenced, it works."
但这实际上并不能正常工作。如果 userId 不为空,它会起作用。如果它为空,我会得到一个从数据库返回的异常。我猜想 MyBatis 以错误的方式绑定了 null 值。也许这是一个错误。我不知道:(
异常:
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.postgresql.util.PSQLException: ERROR: could not determine data type of parameter
### 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
最后我找到了三种不同的解决方案。
(问题1:解决方案1)
我遵循了 @blackwizard 提到的,我能够将条件 userId = (userId == null) ? -1 : userId
从 java 移动到 MyBatis 级别。而且还不错!正确的语法是:<if test='userId==null'><bind name='userId' value='-1'/></if>
.
(问题1:解决方案2)
我从 postgres 返回 could not determine data type of parameter
错误的原因是因为在 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) 第二个解决方案取决于 PortgreSQL。以下解决方案完全独立于数据库。感谢@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。它包含的附加代码较少。
问题 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 标签,该值必须恰好嵌入字符串开头和结尾的 <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);
你也可以使用typeHandler来设置默认值,只需与参数一起使用:#{userId, typeHandler=CustomDefaultValueTypeHandler}
编辑:回复附加问题:如果你想允许传递空值而不是处理默认值替换那么你必须给 Mybatis 一些关于绑定参数的假定类型的提示因为它无法解析 null 的实际类型,因为它没有 know/see Mapper 接口中的变量声明。所以:#{userId, javaType=int,jdbcType=NUMERIC}
。这两个属性中只有一个就足够了。
Documentation 状态:
Like the rest of MyBatis, the javaType can almost always be determined from the parameter object, unless that object is a HashMap. Then the javaType should be specified to ensure the correct TypeHandler is used.
NOTE The JDBC Type is required by JDBC for all nullable columns, if null is passed as a value. You can investigate this yourself by reading the JavaDocs for the PreparedStatement.setNull() method.
问题 2:您绝对不能 reuse/mutualize 您在注释中定义的内容。这不是因为Mybatis,而是注解。您将必须在 XML 中定义结果映射,并使用 @ResultMap 引用它们。 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:正如我已经
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}.
问题 3:
请尝试使用自 JDK 8 以来提供的 -parameters 编译选项。 您可以省略 @Param 注释。
见https://github.com/mybatis/mybatis-3/issues/549
谢谢。