Mybatis通過一條SQL查出關聯的對象

Mybatis通過一條SQL查出關聯的對象

以往在做對象的查詢時如果需要把關聯的對象一起查出來是通過resultMap的子查詢來進行的,這種用法可以參考我之前寫的一篇文章http://elim.iteye.com/blog/1337009。通過子查詢來進行的關聯對象的查詢時,Mybatis會重新發起一次數據庫請求,這在有的時候性能方面不是特別的好,我們期望可以用一條SQL語句就把主體對象以及關聯的對象都查出來,Hibernate其實是有對應的實現,Mybatis現在也有對應的支持(筆者以前剛開始接觸Mybatis時Mybatis還沒有這個機制,不知道是從哪個版本開始有了這個功能,挺好的)。

現在假設我們有兩張表,sys_wf_process表和sys_wf_node表。sys_wf_process表是流程實例表,sys_wf_node是流程節點表,流程節點表通過process_id字段關聯sys_wf_process表的主鍵id,一個流程實例會有很多流程節點,二者的表結構如下所示。

sys_wf_process表
字段名
類型
備註
id
integer
主鍵
template_id
integer
模板ID
creator
integer
創建人的ID
create_time
timestamp
創建時間

sys_wf_node表
字段名
類型
備註
id
integer
主鍵
process_id
integer
流程實例ID
node_code
varchar(10)
節點編號
node_name
varchar(100)
節點名稱

針對這兩張表,我們分別建立了對應的實體類與之對應,sys_wf_process表對應的實體對象是SysWfProcess,sys_wf_node表對應的實體類是SysWfNode。二者的代碼如下(對應的set和get方法將被省略)。
public class SysWfProcess {

private Integer id;

private Integer templateId;//模板ID

private Integer creator;

private Date createTime;

private List<SysWfNode> nodes;//包含的流程節點
//…省略get和set方法
}

public class SysWfNode {

private Integer nodeId;//主鍵

private Integer processId;//流程實例ID

private SysWfProcess process;//關聯的流程實例

private String nodeCode;//節點編號

private String nodeName;//節點名稱

//…省略get和set方法
}

SysWfProcess和SysWfNode是一對多的關係,下面將分三種情況來討論對應的配置,通過1拿多,通過多拿1,以及相互拿(即雙向的引用)。

1.1 通過1拿多
在本例中1的一方是SysWfProcess,其通過屬性nodes引用多的一方。如果通過一條SQL在查詢1的一方自動把多的一方也查詢出來,我們通常是通過表的關聯查詢來查詢出所有相關的信息。爲此我們在對應的Mapper.xml文件中加入如下查詢配置。
<!-- 只用一條SQL查出一對多關係 -->
<select id="singleSql1ToN" parameterType="java.lang.Integer" resultMap="SingleSql1ToNResult">
select
a.id,a.template_id,a.creator,a.create_time,b.id node_id,b.node_code, b.node_name
from sys_wf_process a
left join sys_wf_node b
on a.id=b.process_id
where a.id=#{id}
</select>

以及對應的resultMap配置。
<resultMap id="SingleSql1ToNResult" type="com.elim.learn.mybatis.model.SysWfProcess">
<!-- id非常重要,用來區分記錄 -->
<id property="id" column="id"/>
<result property="creator" column="creator"/>
<result property="templateId" column="template_id"/>
<result property="createTime" column="create_time"/>
<!-- 指定關聯的集合屬性的數據映射,ofType屬性指定集合元素對應的數據類型 -->
<collection property="nodes" ofType="com.elim.learn.mybatis.model.SysWfNode">
<id property="nodeId" column="node_id"/>
<result property="nodeCode" column="node_code"/>
<result property="nodeName" column="node_name"/>
<result property="processId" column="id"/>
</collection>
</resultMap>

關聯的集合屬性是通過collection元素來定義的,跟通過resultMap指定子查詢使用的元素是一樣的,只是這裏不指定子查詢,而是直接配置從同一個查詢結果集裏面進行映射。按照上面這種配置Mybatis會把結果集裏面的每一行的node_id、node_code、node_name和id字段取出根據映射關係構造爲一個SysWfNode對象。

然後我們也在對應的Mapper接口裏面加入剛剛定義的查詢方法。
public interface SysWfProcessMapper {
SysWfProcess singleSql1ToN(Integer id);
}

測試一下,看是否能正常拿到SysWfProcess關聯的SysWfNode,代碼如下:
public class BasicTest {

private SqlSessionFactory sessionFactory = SqlSessionFactoryUtil.getSqlSessionFactory();
private SqlSession session = null;

@Before
public void before() {
session = sessionFactory.openSession();
}

@After
public void after() {
session.commit();
session.close();
}
@Test
public void test3() {
SysWfProcessMapper mapper = session.getMapper(SysWfProcessMapper.class);
SysWfProcess process = mapper.singleSql1ToN(1);
List<SysWfNode> nodes = process.getNodes();
System.out.println(nodes);//這裏可以輸出獲取到的SysWfNode信息
}

}

1.2 通過多拿1
通過一條SQL語句在查詢一的一方時把多的一方也查出來,我們需要在查詢時把所有關聯的信息都查詢出來,在對應的Mapper.xml文件中添加如下配置。
<!-- 只用一條SQL查出多對一關係 -->
<select id="singleSqlNTo1" parameterType="java.lang.Integer" resultMap="SingleSqlNTo1Result">
select
a.id,a.node_code,a.node_name,a.process_id, b.template_id,b.creator,b.create_time
from
sys_wf_node a, sys_wf_process b
where
a.process_id=b.id and a.id=#{id}
</select>

<resultMap type="com.elim.learn.mybatis.model.SysWfNode" id="SingleSqlNTo1Result">
<id property="nodeId" column="id"/>
<result property="nodeCode" column="node_code"/>
<result property="nodeName" column="node_name"/>
<result property="processId" column="process_id"/>
<!-- 單個對象的關聯是通過association元素來定義的 -->
<association property="process" javaType="com.elim.learn.mybatis.model.SysWfProcess">
<id property="id" column="process_id"/>
<result property="templateId" column="template_id"/>
<result property="creator" column="creator"/>
<result property="createTime" column="create_time"/>
</association>
</resultMap>

然後我們在對應的Mapper接口中定義與查詢id相同名稱的方法。
public interface SysWfNodeMapper {
SysWfNode singleSqlNTo1(Integer id);
}

測試如下:
@Test
public void test4() {
SysWfNodeMapper mapper = session.getMapper(SysWfNodeMapper.class);
SysWfNode node = mapper.singleSqlNTo1(2);
SysWfProcess process = node.getProcess();
System.out.println(process);//這裏能拿到對應的SysWfProcess
}

1.3 雙向引用
當需要兩邊都持有對應的引用,引用裏面又持有對應的引用時,基於單條查詢SQL的方式好像配置不出來。筆者將SingleSql1ToNResult中的SysWfProcess引用的SysWfNode再引用對應的SysWfProcess,指定其解析的resultMap爲SingleSql1ToNResult時將拋出java.lang.StackOverflowError,因爲它們在進行循環引用,循環的新建對象、賦值。配置如下:
<resultMap id="SingleSql1ToNResult" type="com.elim.learn.mybatis.model.SysWfProcess">
<!-- id非常重要,用來區分記錄 -->
<id property="id" column="id"/>
<result property="creator" column="creator"/>
<result property="templateId" column="template_id"/>
<result property="createTime" column="create_time"/>
<!-- 指定關聯的集合屬性的數據映射,ofType屬性指定集合元素對應的數據類型 -->
<collection property="nodes" ofType="com.elim.learn.mybatis.model.SysWfNode">
<id property="nodeId" column="node_id"/>
<result property="nodeCode" column="node_code"/>
<result property="nodeName" column="node_name"/>
<result property="processId" column="id"/>
<association property="process" javaType="com.elim.learn.mybatis.model.SysWfProcess" resultMap="SingleSql1ToNResult"/>
</collection>
</resultMap>

所以這種循環相互擁有對方引用的通過配置是不OK的,但是如果我們僅僅是對其某些屬性感興趣,我們可以在裏面的assocation時再通過result指定一層映射關係,這個時候我們就可以拿到SysWfNode對應的SysWfProcess對象了,但是它跟我們的擁有SysWfNode的SysWfProcess對象已經不是同一個對象了。基於這種情況的配置如下:
<resultMap id="SingleSql1ToNResult" type="com.elim.learn.mybatis.model.SysWfProcess">
<!-- id非常重要,用來區分記錄 -->
<id property="id" column="id"/>
<result property="creator" column="creator"/>
<result property="templateId" column="template_id"/>
<result property="createTime" column="create_time"/>
<!-- 指定關聯的集合屬性的數據映射,ofType屬性指定集合元素對應的數據類型 -->
<collection property="nodes" ofType="com.elim.learn.mybatis.model.SysWfNode">
<id property="nodeId" column="node_id"/>
<result property="nodeCode" column="node_code"/>
<result property="nodeName" column="node_name"/>
<result property="processId" column="id"/>
<association property="process" javaType="com.elim.learn.mybatis.model.SysWfProcess" resultMap="SysWfProcess"/>
</collection>
</resultMap>

<resultMap id="SysWfProcess" type="com.elim.learn.mybatis.model.SysWfProcess">
<id property="id" column="id"/>
<result property="creator" column="creator"/>
<result property="templateId" column="template_id"/>
<result property="createTime" column="create_time"/>
</resultMap>

對應的測試代碼如下:
@Test
public void test5() {
SysWfProcessMapper mapper = session.getMapper(SysWfProcessMapper.class);
SysWfProcess process = mapper.singleSql1ToN(1);
List<SysWfNode> nodes = process.getNodes();
SysWfNode node = nodes.get(0);
System.out.println(node.getProcess());//不爲null
System.out.println(process == node.getProcess());//false
}

參考文檔
http://www.mybatis.org/mybatis-3/sqlmap-xml.html

(注:本文是基於Mybatis3.3.1所寫)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章