Mybatis通过一条SQL查出关联的对象

Mybatis通过一条SQL查出关联的对象

以往在做对象的查询时如果需要把关联的对象一起查出来是通过resultMap的子查询来进行的。通过子查询来进行的关联对象的查询时,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;//节点名称

//…省略getset方法

}

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

}

1.4     columnPrefix

有的时候我们的查询里面可能需要多次关联同一张表,因为查询出来的不同信息都是基于同一张表的。打个比方,我们有一张部门表,其有一个字段存的是部门经理ID,有一个字段存的是副经理ID,我们需要查一个部门的信息的时候把这个部门的经理和副经理的信息一起查出来,部门经理和副经理的信息都是存在人员信息表中的。部门表的结构是t_dept(id,name,manager_id,vice_manager_id),人员表的结果是t_person(id,no,name),那么基于上面的场景,我们的查询语句大概是这样的。

<select id="findById" parameterType="java.lang.Long"resultMap="BaseResultMap">

SELECT

id,

name,

manager_id,

vice_manager_id,

b.no manager_no,

b.name manager_name,

c.no vice_manager_no,

c.name vice_manager_name

FROM

t_dept a

LEFT JOIN

t_person b ON a.manager_id = b.id

LEFT JOIN

t_person c ON a.vice_manager_id = c.id

WHERE

a.id=#{id}

</select>

对应的映射结果大概是这样的。

<resultMap type="com.elim.learn.mybatis.model.Department"id="BaseResultMap">

<id column="id" property="id"/>

<result column="name" property="name"/>

<association property="manager"javaType="com.elim.learn.mybatis.model.Person">

<id column="manager_id" property="id"/>

<result column="manager_no" property="no"/>

<result column="manager_name" property="name"/>

</association>

<association property="viceManager"javaType="com.elim.learn.mybatis.model.Person">

<id column="vice_manager_id" property="id"/>

<result column="vice_manager_no" property="no"/>

<result column="vice_manager_name" property="name"/>

</association>

</resultMap>

我们关联了两个Person对象,就写了两遍<association>,只是映射的column不一样而已,如果我们需呀关联5个Person对象,按照这样的方式就得配置5遍。Mybatis为我们提供了一个columnPrefix可以简化这种配置。它的大概意思是先配置一个通用的ResultMap,在配置<association>的时候指定resultMap为我们配置的通用的ResultMap,同时指定在匹配通用的resultMap时需要使用的列的前缀,columnPrefix。文字描述可能不是很好理解,我们来看一个示例。基于columnPrefix我们的上述ResultMap应该配置成如下这样。

<resultMap type="com.elim.learn.mybatis.model.Department"id="BaseResultMap">

<id column="id" property="id"/>

<result column="name" property="name"/>

<association property="manager" resultMap="PersonResultMap"columnPrefix="manager_"/>

<association property="viceManager"resultMap="PersonResultMap" columnPrefix="vice_manager_"/>

</resultMap>

<resultMap type="com.elim.learn.mybatis.model.Person"id="PersonResultMap">

<id column="id" property="id"/>

<result column="no" property="no"/>

<result column="name" property="name"/>

</resultMap>

PersonResultMap是最基础的映射,然后对于manager属性,我们指定了其columnPrefix=”manager_”,这样在使用PersonResultMap映射结果集时,都会把对应的column加上指定的前缀“manager_”,即会把列manager_id与属性id映射,而不是原来的列id。对于集合类型的属性的映射也是一样的,我们也可以在对应的<collection>标签上指定columnPrefix。

参考文档

http://www.mybatis.org/mybatis-3/sqlmap-xml.html

(注:本文是基于Mybatis3.3.1所写)

(注:该博客第一次于2016年12月20日发表于本人的iteye博客,http://elim.iteye.com/blog/2346389)

时间: 2024-08-09 06:24:12

Mybatis通过一条SQL查出关联的对象的相关文章

MyBatis一次执行多条SQL语句

MyBatis一次执行多条SQL语句 有个常见的场景:删除用户的时候需要先删除用户的外键关联数据,否则会触发规则报错. 解决办法不外乎有三个:1.多条sql分批执行:2.存储过程或函数调用:3.sql批量执行. 今天我要说的是MyBatis中如何一次执行多条语句(使用mysql数据库). 1.修改数据库连接参数加上allowMultiQueries=true,如: hikariConfig.security.jdbcUrl=jdbc:mysql://xx.xx.xx:3306/xxxxx?cha

MyBatis插件及示例----打印每条SQL语句及其执行时间

Plugins 摘一段来自MyBatis官方文档的文字. MyBatis允许你在某一点拦截已映射语句执行的调用.默认情况下,MyBatis允许使用插件来拦截方法调用 Executor(update.query.flushStatements.commint.rollback.getTransaction.close.isClosed) ParameterHandler(getParameterObject.setParameters) ResultSetHandler(handleResultS

使用mybatis时,sql查出来的结果映射不进逆向工程生成的该模型

为了逆向工程生成驼峰效果,我们会在两个单词中间加_,但是在mybatis中,写sql语句时,要加别名,使别名和你逆向工程生成的该数据模型里面的属性名一致,否则查出来的结果映射不进去.

MyBatis7:MyBatis插件及示例----打印每条SQL语句及其执行时间

Plugins 摘一段来自MyBatis官方文档的文字. MyBatis允许你在某一点拦截已映射语句执行的调用.默认情况下,MyBatis允许使用插件来拦截方法调用 Executor(update.query.flushStatements.commint.rollback.getTransaction.close.isClosed) ParameterHandler(getParameterObject.setParameters) ResultSetHandler(handleResultS

MyBatis一个映射发送多条SQL遇到的问题

在做批量更新的时候,我们可以知道经常是用mybatis动态sql拼接多条sql语句,如: <update id="alterStudentDormBatch" parameterType="Map"> <foreach collection="stuDormMap" index="stuId" item="newDorm" separator=";"> UPDAT

mysql+mybatis 在mybatis一个标签中,执行多条sql语句

然后在mybatis映射文件中的标签中,一般不能执行多条sql,如下: <insert id="addUser" parameterType="User" > insert into t_users (name,password,phone) values (#{name}, #{password},#{phone}); insert into t_dep (depname) values (#{depname}) </insert> MyS

用一条SQL语句查出每门课都大于80分的学生的姓名

用一条sql语句查询出所有课程都大于80分的学生名单: name cource score 张三 语文 81 张三 数学 75 李四 语文 76 李四 数学 90 王五 语文 81 王五 数学 100 王五 英语 90 1 SET FOREIGN_KEY_CHECKS=0; 2 3 -- ---------------------------- 4 -- Table structure for grade 5 -- ---------------------------- 6 DROP TABL

Mybatis+MySql 一个标签中执行多条sql语句 这个坑 ,我心中有一句MMP

解决办法 转自网友: 亲测 解决了问题@ MySql默认是不支持这种骚操作的,但是并不代表不能实现,只需要在jdbc的配置文件中稍做配置: driver=com.mysql.jdbc.Driverurl=jdbc:mysql://127.0.0.1:3306/airipo?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=trueusername=

一条SQL语句查询两表中两个字段

首先描述问题,student表中有字段startID,endID.garde表中的ID需要对应student表中的startID或者student表中的endID才能查出grade表中的name字段,这时候问题就来了,如果需要一条sql一句同时查出garde表中的两条数据怎么办?(两表的关联字段为 SID) sql="select b.name,c.name as name2 from student a,garde b,grade c where a.SID=b.SID and a.SID=c