Mybatis 关联为 one-one 而不是 one-many
Mybatis Association as one-one instead of one-many
为清楚起见进行了编辑 02/07/17
我在工作中使用 Mybatis 3.3,但我 运行 遇到了障碍。我很确定这是我的 resultMapper 的问题,但我很难找到相关的 tutorials/info.
我有一个现有的 Java 模型、Mybatis 映射器和 tables;我正在尝试编写一个尽可能重用的新模块。我现有的模型如下所示:
class Document {
Header header;
List<Detail> details;
}
我想用不同的 Mybatis 映射器重用模型以在 Details 和 Headers 之间产生 1-1 关系(即 details.size() 始终为 1)。
我目前只能获取 1 个文档。它提取 table 中的第一个 header,并将每个文档的每个细节附加到它。这是我的结果图和我正在处理的查询。查询 returns 正确的结果,但 Mybatis 将它们包装错误。
<resultMap id="header" type="Header">
<result property="id" column="ID" />
<result property="title" column="TITLE" />
</resultMap>
<resultMap id="detail" type="Detail">
<result property="id" column="ID" />
<result property="title" column="INFO" />
</resultMap>
<resultMap id="document" type="Document">
<association property="header" resultMap="header" />
<association property="details" resultMap="detail" />
</resultMap>
SELECT
HEADER.ID,
DETAIL.ID
FROM HEADER
JOIN DETAIL ON HEADER.ID = DETAIL.HEADER_ID
在文档 resultMap 中使用 collection 而不是 association for 属性 details .
编辑 02-08-17:
根据您的编辑,事情变得更清楚了:轻微的模型更改会更自然地满足您的需求:
class Document {
Header header;
Detail detail;
}
但是您想保留 1 个元素的 List<Detail>
。
我想过。它只需要添加一个 "virtual property" => getter 和 setter 来包装列表。
public void setSingleDetail(Detail detail) {
this.details = Arrays.asList(detail);
}
public Detail getSingleDetail() {
return null == this.details ? null : this.details.iterator().next();
}
但这还不够,因为它只是覆盖了详细列表。
如果看看方法org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForNestedResultMap
。你会发现Mybatis不允许你想要什么,它总是"group by"容器实体(如果你不指定id属性,它会使用hash)。这也是为什么需要 getter 的原因:与新行中的值进行比较。
你想要做的事情需要一个与 Header 关联的 resultMap Detail(当然还有一个 class 具有匹配结构),然后你会得到一个结果/Detail。
要将行集映射到 object 的树,关键是 object 的身份。考虑这个例子:
header_id | detail_id
---------------------
1 | 1
1 | 2
有两种方法可以将其解析为以 Document
作为根实体的 object 树。第一种方法是让一个实体具有 header 和两个 Detail
的 collection。另一种方法是有两个 Document
,每个有一个 Detail
。这里的区别在于 Document
object 的身份。你需要告诉 mybatis 什么列是 Document
实体的标识符。
为了您的目的,这应该是 Detail
.
的 ID
您需要更改查询和映射才能实现这一点。
主要目的是在这个映射中指定 detail id 为文档 id:
<resultMap id="document" type="Document">
<id column="detail_id"/>
<association property="header" resultMap="header" />
<association property="details" resultMap="detail"/>
</resultMap> modify the query
您需要更改查询以使详细信息 ID 列具有唯一名称,例如
SELECT
HEADER.ID,
DETAIL.ID DETAIL_ID
FROM HEADER
JOIN DETAIL ON HEADER.ID = DETAIL.HEADER_ID
但这会破坏 Detail
关联中其他列的映射。
为了将 resultMap
用于 Detail
,您需要为 detail table 中的所有列添加相同的前缀,并在映射中指定:
<resultMap id="document" type="Document">
<id column="detail_id"/>
<association property="header" resultMap="header" />
<association property="details" resultMap="detail" prefix="DETAIL_"/>
</resultMap> modify the query
请注意,由于 mybatis 错误,前缀应为大写。
为清楚起见进行了编辑 02/07/17
我在工作中使用 Mybatis 3.3,但我 运行 遇到了障碍。我很确定这是我的 resultMapper 的问题,但我很难找到相关的 tutorials/info.
我有一个现有的 Java 模型、Mybatis 映射器和 tables;我正在尝试编写一个尽可能重用的新模块。我现有的模型如下所示:
class Document {
Header header;
List<Detail> details;
}
我想用不同的 Mybatis 映射器重用模型以在 Details 和 Headers 之间产生 1-1 关系(即 details.size() 始终为 1)。
我目前只能获取 1 个文档。它提取 table 中的第一个 header,并将每个文档的每个细节附加到它。这是我的结果图和我正在处理的查询。查询 returns 正确的结果,但 Mybatis 将它们包装错误。
<resultMap id="header" type="Header">
<result property="id" column="ID" />
<result property="title" column="TITLE" />
</resultMap>
<resultMap id="detail" type="Detail">
<result property="id" column="ID" />
<result property="title" column="INFO" />
</resultMap>
<resultMap id="document" type="Document">
<association property="header" resultMap="header" />
<association property="details" resultMap="detail" />
</resultMap>
SELECT
HEADER.ID,
DETAIL.ID
FROM HEADER
JOIN DETAIL ON HEADER.ID = DETAIL.HEADER_ID
在文档 resultMap 中使用 collection 而不是 association for 属性 details .
编辑 02-08-17:
根据您的编辑,事情变得更清楚了:轻微的模型更改会更自然地满足您的需求:
class Document {
Header header;
Detail detail;
}
但是您想保留 1 个元素的 List<Detail>
。
我想过。它只需要添加一个 "virtual property" => getter 和 setter 来包装列表。
public void setSingleDetail(Detail detail) {
this.details = Arrays.asList(detail);
}
public Detail getSingleDetail() {
return null == this.details ? null : this.details.iterator().next();
}
但这还不够,因为它只是覆盖了详细列表。
如果看看方法org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForNestedResultMap
。你会发现Mybatis不允许你想要什么,它总是"group by"容器实体(如果你不指定id属性,它会使用hash)。这也是为什么需要 getter 的原因:与新行中的值进行比较。
你想要做的事情需要一个与 Header 关联的 resultMap Detail(当然还有一个 class 具有匹配结构),然后你会得到一个结果/Detail。
要将行集映射到 object 的树,关键是 object 的身份。考虑这个例子:
header_id | detail_id
---------------------
1 | 1
1 | 2
有两种方法可以将其解析为以 Document
作为根实体的 object 树。第一种方法是让一个实体具有 header 和两个 Detail
的 collection。另一种方法是有两个 Document
,每个有一个 Detail
。这里的区别在于 Document
object 的身份。你需要告诉 mybatis 什么列是 Document
实体的标识符。
为了您的目的,这应该是 Detail
.
您需要更改查询和映射才能实现这一点。 主要目的是在这个映射中指定 detail id 为文档 id:
<resultMap id="document" type="Document">
<id column="detail_id"/>
<association property="header" resultMap="header" />
<association property="details" resultMap="detail"/>
</resultMap> modify the query
您需要更改查询以使详细信息 ID 列具有唯一名称,例如
SELECT
HEADER.ID,
DETAIL.ID DETAIL_ID
FROM HEADER
JOIN DETAIL ON HEADER.ID = DETAIL.HEADER_ID
但这会破坏 Detail
关联中其他列的映射。
为了将 resultMap
用于 Detail
,您需要为 detail table 中的所有列添加相同的前缀,并在映射中指定:
<resultMap id="document" type="Document">
<id column="detail_id"/>
<association property="header" resultMap="header" />
<association property="details" resultMap="detail" prefix="DETAIL_"/>
</resultMap> modify the query
请注意,由于 mybatis 错误,前缀应为大写。