本章主要内容:核心配置文件、SQL映射文件、级联查询。
前言
1.MyBatis实现查询时,返回的结果集有几种常见的存储方式?请举例说明。
答:可以使用Map存储,也可以使用POJO存储。
<!-- 查询所有用户信息存到Map中 -->
<select id="selectAllUserMap" resultType="map">
select * from user
</select>
<!-- 使用自定义结果集类型 -->
<resultMap type="com.pojo.MapUser" id="myResult">
<!-- property是com.pojo.MapUser类中的属性-->
<!-- column是查询结果的列名,可以来自不同的表 -->
<id property="m_uid" column="uid"/>
<result property="m_uname" column="uname"/>
<result property="m_usex" column="usex"/>
</resultMap>
2.在MyBatis中针对不同的数据库软件,<insert>元素如何将主键回填?
答:MySQL、SQL Server等数据库的表格可以采用自动递增的字段作为主键。自动回填示例如下:
<!-- 添加一个用户,成功后将主键值回填给uid(po类的属性)-->
<insert id="addUser" parameterType="com.po.MyUser"
keyProperty="uid" useGeneratedKeys="true">
insert into user (uname,usex) values(#{uname},#{usex})
</insert>
如果实际工程中使用的数据库不支持主键自动递增(如Oracle),或者取消了主键自动递增的规则时,可以使用MyBatis的<selectKey>元素来自定义生成主键。具体配置示例代码如下:
<insert id="insertUser" parameterType="com.po.MyUser">
<!-- 先使用selectKey元素定义主键,然后再定义SQL语句 -->
<selectKey keyProperty="uid" resultType="Integer" order="BEFORE">
select if(max(uid) is null, 1 , max(uid)+1) as newUid from user
</selectKey>
insert into user (uid,uname,usex) values(#{uid},#{uname},#{usex})
</insert>
3.在MyBatis中,如何给SQL语句传递参数?
答:1)传递一个基本数据参数,如:
<!-- 根据uid查询一个用户信息 -->
<select id="selectUserById" parameterType="Integer"
resultType="com.po.MyUser">
select * from user where uid = #{uid}
</select>
2)使用Map接口传递多个参数,如:
<!-- 查询陈姓男性用户信息 -->
<select id="selectAllUser" resultType="com.po.MyUser" parameterType="map">
select * from user
where uname like concat('%',#{u_name},'%')
and usex = #{u_sex}
</select>
上述SQL文件中参数名u_name和u_sex是Map的key。
3)使用Java Bean传递多个参数,如:
package com.pojo;
public class SeletUserParam {
private String u_name;
private String u_sex;
//此处省略setter和getter方法
}
<select id="selectAllUser" resultType="com.po.MyUser" parameterType="com.pojo.SeletUserParam">
select * from user
where uname like concat('%',#{u_name},'%')
and usex = #{u_sex}
</select>
7.1 MyBatis配置文件概述
MyBatis的核心配置文件配置了很多影响MyBatis行为的信息,这些信息通常只会配置在一个文件中,并且不会轻易改动。另外,与Spring框架整合后,MyBatis的核心配置文件信息将配置到Spring的配置文件中。因此,在实际开发中需要编写或修改MyBatis的核心配置文件的情况不多。
MyBatis配置文件中大标签configuration下子标签包括:
configuration
|--- properties
|--- settings
|--- typeAliases
|--- typeHandlers
|--- objectFactory
|--- plugins
|--- environments
|--- |--- environment
|--- |--- |--- transactionManager
|--- |--- |__ dataSource
|__ mappers
这里不做过多赘述,欲知详情见文末链接文章。
7.2 MyBatis映射器概述
映射器是Mybatis中最复杂最重要的组件,由一个接口(Dao)加上XML(SQL映射文件)组成。映射器也可以使用注解完成,但是实际应用不多。因为注解不适合复杂SQL,可读性也差,并且没有XML文件的上下文互相引用功能。
7.3 <select>元素
在SQL映射文件中<select>元素用于映射SQL的select语句,其示例代码如下:
<!-- 根据uid查询一个用户信息 -->
<select id="selectUserById" parameterType="Integer"
resultType="com.po.MyUser">
select * from user where uid = #{uid}
</select>
上述示例代码中,id的值是唯一标识符,它接收一个Integer类型的参数,返回一个MyUser类型的对象,结果集自动映射到MyUser属性。
下图是<select>元素的常用属性:
7.3.1 使用Map接口传递多个参数
在实际开发中,查询SQL语句经常需要多个参数,比如多条件查询。多个参数传递时,<select>元素的parameterType属性值的类型是什么呢?在MyBatis中允许Map接口通过键值对传递多个参数。
假设数据操作接口中有个实现查询陈姓男性用户信息功能的方法:
public List<MyUser> selectAllUser(Map<String, Object> param);
此时,传递给映射器的是一个Map对象,使用它在SQL中设置对应的参数,对应SQL文件代码如下:
<!-- 查询陈姓男性用户信息 -->
<select id="selectAllUser" resultType="com.po.MyUser" parameterType="map">
select * from user
where uname like concat('%',#{u_name},'%')
and usex = #{u_sex}
</select>
上述SQL文件中参数名u_name和u_sex是Map的key。
对应的UserController代码为:
//查询多个用户
Map<String, Object> map = new HashMap<>();
map.put("u_name", "陈");
map.put("u_sex", "男");
List<MyUser> list = userDao.selectAllUser(map);
7.3.2 使用Java Bean传递多个参数
首先,在ch7应用的src目录下,创建一个名为com.pojo的包,在包中创建一个POJO类SeletUserParam。
其次,将Dao接口中的selectAllUser方法修改为:
public List<MyUser> selectAllUser(SeletUserParam param);
再次,将com.mybatis包中的SQL映射文件UserMapper.xml中的“查询陈姓男性用户信息”代码修改为:
<!-- 查询陈姓男性用户信息 -->
<select id="selectAllUser" resultType="com.po.MyUser" parameterType="com.pojo.SeletUserParam">
select * from user
where uname like concat('%',#{u_name},'%')
and usex = #{u_sex}
</select>
最后,将com.controller包中UserController的“查询多个用户”代码片段修改:
//查询多个用户
SeletUserParam su = new SeletUserParam();
su.setU_name("陈");
su.setU_sex("男");
List<MyUser> list = userDao.selectAllUser(su);
for (MyUser myUser : list) {
System.out.println(myUser);
}
当然,实际应用中要看情况选择使用Map还是JavaBean来传递参数:如果参数较少,用Map比较方便。如果参数较多,建议使用JavaBean。
7.4 <insert>元素
<insert>元素用于映射插入语句,MyBatis执行完一条插入语句后,将返回一个整数表示其影响的行数。它的属性与<select>元素的属性大部分相同,在本节讲解它的几个特有属性。具体如下:
keyProperty:该属性的作用是将插入或更新操作时的返回值赋值给PO类的某个属性,通常会设置为主键对应的属性。如果是联合主键,可以在多个值之间用逗号隔开。
keyColumn:该属性用于设置第几列是主键,当主键列不是表中的第一列时需要设置。如果是联合主键时,可以在多个值之间用逗号隔开。
useGeneratedKeys:该属性将使MyBatis使用JDBC的getGeneratedKeys()方法获取由数据库内部生产的主键,如MySQL、SQL Server等自动递增的字段,其默认值为false。
7.4.1 主键(自动递增)回填
MySQL、SQL Server等数据库的表格可以采用自动递增的字段作为主键。有时可能需要使用这个刚刚产生的主键,用以关联其他业务。
映射文件写法:
<!-- 添加一个用户,成功后将主键值回填给uid(po类的属性),#{uname}为com.po.MyUser的属性值-->
<insert id="addUser" parameterType="com.po.MyUser"
keyProperty="uid" useGeneratedKeys="true">
insert into user (uname,usex) values(#{uname},#{usex})
</insert>
在Controller类中的调用:
//添加一个用户
MyUser addmu = new MyUser();
addmu.setUname("陈恒");
addmu.setUsex("男");
int add = userDao.addUser(addmu);
System.out.println("添加了" + add + "条记录");
System.out.println("添加记录的主键是" + addmu.getUid());
7.4.2 自定义主键
如果实际工程中使用的数据库不支持主键自动递增(如Oracle),或者取消了主键自动递增的规则时,可以使用MyBatis的<selectKey>元素来自定义生成主键。如下Mapping配置:
<insert id="addUser" parameterType="com.po.MyUser">
<!-- 先使用selectKey 元素定义主键,然后在定义SQL语句 -->
<selectKey keyProperty="uid" resultType="Integer" order="BEFORE">
select if(max(uid) is null, 1, max(uid)+1) as newUid from user
</selectKey>
insert into user (uname,usex) values(#{uname},#{usex})
</insert>
这句话是取得user表中id最大值,再+1,实际情况比这个复杂:
select if(max(uid) is null, 1, max(uid)+1) as newUid from user
而 <selectKey> 元素中的 order="BEFORE" 是代表 <selectKey> 元素先于SQL查询语句执行,order="AFTER" 意义则相反。
7.5 <update>与<delete>元素
<update>和<delete>元素比较简单,它们的属性和<insert>元素、<select>元素的属性差不多,执行后也返回一个整数,表示影响了数据库的记录行数。
<!-- 修改一个用户 -->
<update id="updateUser" parameterType="com.po.MyUser">
update user set uname = #{uname},usex = #{usex} where uid = #{uid}
</update>
<!-- 删除一个用户 -->
<delete id="deleteUser" parameterType="Integer">
delete from user where uid = #{uid}
</delete>
7.6 <sql>元素
<sql>元素的作用在于可以定义SQL语句的一部分(代码片段),方便后面的SQL语句引用它,比如反复使用的列名。
<sql id="comColumns">uid,uname,usex</sql>
<select id="selectUser" resultType="com.po.MyUser">
select <include refid="comColumns"/> from user
</select>
7.7 <resultMap>元素
<resultMap>元素表示结果映射集,是MyBatis中最重要也是最强大的元素。主要用来定义映射规则、级联的更新以及定义类型转化器等。
7.7.1 <resultMap>元素结构
<resultMap type="" id="">
<constructor><!-- 类在实例化时,用来注入结果到构造方法 -->
<idArg/><!-- ID参数,结果为ID -->
<arg/><!-- 注入到构造方法的一个普通结果 -->
</constructor>
<id/><!-- 用于表示哪个列是主键 -->
<result/><!-- 注入到字段或JavaBean属性的普通结果 -->
<association property=""/><!-- 用于一对一关联 -->
<collection property=""/><!-- 用于一对多、多对多关联 -->
<discriminator javaType=""><!-- 使用结果值来决定使用哪个结果映射 -->
<case value=""/> <!-- 基于某些值的结果映射 -->
</discriminator>
</resultMap>
<resultMap>元素的type属性表示需要的POJO,id属性是resultMap的唯一标识。子元素<constructor>用于配置构造方法(当POJO未定义无参数的构造方法时使用)。子元素<id>用于表示哪个列是主键。子元素<result>用于表示POJO和数据表普通列的映射关系。子元素<association> 、<collection> 和<discriminator>是用在级联的情况下。关于级联的问题比较复杂,将在7.8节级联那里学习。
7.7.2 使用Map存储结果集
任何select语句可以使用Map存储结果,示例代码如下:
先更改mapping映射文件:
<!-- 查询所有用户信息存到Map中 -->
<select id="selectAllUserMap" resultType="map">
select * from user
</select>
再修改Dao接口:
public List<Map<String, Object>> selectAllUserMap();
然后在Controller中调用Dao接口方法:
//查询所有用户信息存到Map中
List<Map<String, Object>> lmp = userDao.selectAllUserMap();
for (Map<String, Object> map : lmp) {
System.out.println(map);
}
7.7.3 使用POJO存储结果集
使用POJO存储结果集,可以自动映射,如果有比较复杂的映射或者级联查询,那就需要用到resultMap属性配置映射集合了。具体步骤如下:
创建POJO类:
package com.pojo;
public class MapUser {
private Integer m_uid;
private String m_uname;
private String m_usex;
public Integer getM_uid() {
return m_uid;
}
public void setM_uid(Integer m_uid) {
this.m_uid = m_uid;
}
public String getM_uname() {
return m_uname;
}
public void setM_uname(String m_uname) {
this.m_uname = m_uname;
}
public String getM_usex() {
return m_usex;
}
public void setM_usex(String m_usex) {
this.m_usex = m_usex;
}
@Override
public String toString() {
return "User [uid=" + m_uid +",uname=" + m_uname + ",usex=" + m_usex +"]";
}
}
然后配置Mapping文件中的<resultMap>元素:
<!-- 使用自定义结果集类型 -->
<resultMap type="com.pojo.MapUser" id="myResult">
<!-- property是com.pojo.MapUser类中的属性-->
<!-- column是查询结果的列名,可以来自不同的表 -->
<id property="m_uid" column="uid"/>
<result property="m_uname" column="uname"/>
<result property="m_usex" column="usex"/>
</resultMap>
再配置<select>元素:
<!-- 引用刚才配置好的resultMap -->
<select id="selectResultMap" resultMap="myResult">
select * from user
</select>
再去Dao接口中添加接口方法:
public List<MapUser> selectResultMap();
最后Controller中调用方法:
//使用resultMap映射结果集
List<MapUser> listResultMap = userDao.selectResultMap();
for (MapUser myUser : listResultMap) {
System.out.println(myUser);
}
7.8 级联关系
这一小节是本章的重点和难点所在,大家要认真学习哦!
级联关系其实是数据库实体的一个概念,有3种级联关系,分别是一对一级联、一对多级联以及多对多级联。级联的优点是获取数据非常方便,但是过多的级联会增加数据库系统的复杂度,降低系统性能。
如果表A中有一个外键引用了表B的主键,A表就是子表,B表就是父表。当查询表A的数据时,通过表A的外键,也将表B的相关记录返回,这就是级联查询。例如,查询一个人的信息时,同时根据外键(身份证号)也将他的身份证信息返回。
7.8.1 一对一级联查询
生活中一对一级联关系是非常常见的,比如我们的身份证系统,一个人对应一个身份证号,一个身份证号只对应一个人。MyBatis如何处理一对一级联查询呢?在MyBatis中,通过<resultMap>元素的子元素<association>处理这种一对一级联关系。在<association>元素中,通常使用以下属性:
property:指定映射到实体类的对象属性。
column:指定表中对应的字段(即查询返回的列名)。
javaType:指定映射到实体对象属性的类型。
select:指定引入嵌套查询的子SQL语句,该属性用于关联映射中的嵌套查询。
下面以个人与身份证之间的关系为例,讲解一对一级联查询的处理过程,读者只需参考该实例即可学会一对一级联查询的MyBatis实现。
1、创建数据库的表idcard 和表person SQL语句如下:
DROP TABLE IF EXISTS `idcard`;
CREATE TABLE `idcard` (
`id` tinyint(2) NOT NULL AUTO_INCREMENT,
`code` varchar(18) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO idcard VALUES ('1', '123456789123456789');
DROP TABLE IF EXISTS `person`;
CREATE TABLE `person` (
`id` tinyint(2) NOT NULL,
`name` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`idcard_id` tinyint(2) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idcard_id` (`idcard_id`),
CONSTRAINT `idcard_id` FOREIGN KEY (`idcard_id`) REFERENCES `idcard` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO person VALUES ('1', '陈恒', '88', '1');
2、创建ch7项目,com.po包中创建对应的持久化类Idcard和Person,碍于篇幅原因,均省略了setter和getter方法,全部源码见文末Github地址:
package com.po;
/**
* springtest数据库中idcard表的持久化类
*/
public class Idcard {
private Integer id;
private String code;
}
package com.po;
/**
* springtest数据库中person表的持久化类
*/
public class Person {
private Integer id;
private String name;
private Integer age;
private Idcard card;//个人身份证关联
}
3、创建映射文件
首先在Mybatis的核心配置文件mybatis-config.xml中打开延时开关代码,目的是提高查询效率。代码如下:
<!-- 在使用MyBatis嵌套查询方式进行关联查询时,使用MyBatis的延迟加载在一定程度可以提高查询效率 -->
<settings>
<!-- 打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载改为按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
同样的,在com.mybatis包中创建IDcard表和Person表对应的映射文件。
IdCardMapper.xml中注意命名空间com.dao.IdCardDao,绑定了Dao接口,方便使用SQL语句,当你的namespace绑定接口后,你可以不用写接口实现类,mybatis会通过该绑定自动帮你找到对应要执行的SQL语句。其中的SQL语句也很简单,就是查询idcard表的所有数据,返回类型为Idcard类。
<mapper namespace="com.dao.IdCardDao">
<select id="selectCodeById" parameterType="Integer" resultType="com.po.Idcard">
select * from idcard where id=#{id}
</select>
</mapper>
其对应的Dao接口方法IdCardDao为:
@Repository("idCardDao")
@Mapper
public interface IdCardDao {
public Idcard selectCodeById(Integer i);
}
在PersonMapper.xml文件中以3中方式实现“根据id查询个人信息功能”,下面我们一一解释。
第一种方法:嵌套查询,依次执行两个SQL语句。
<mapper namespace="com.dao.PersonDao">
<!-- 一对一 根据id查询个人信息:第一种方法(嵌套查询,依次执行两个SQL语句) -->
<resultMap type="com.po.Person" id="cardAndPerson1">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<!-- 一对一关联查询 -->
<!-- association定义关联对象的封装规则
select:表明当前属性是调用select指定的方法查询出结果
column:指定将那一列的值传递给这个方法
整个流程:使用Select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性
-->
<association property="card" column="idcard_id" javaType="com.po.Idcard"
select="com.dao.IdCardDao.selectCodeById"/>
</resultMap>
<select id="selectPersonById1" parameterType="Integer" resultMap="cardAndPerson1">
select * from person where id=#{id}
</select>
</mapper>
关于第一种方法的个人理解:
先执行 selectPersonById1 的语句,然后将结果idcard_id 的值传给 <association>元素中的<select> 元素。即本表数据由第一个SQL语句查询,被关联的表数据由第二个SQL即<association>元素中的<select> 元素查询,然后整体数据由<resultMap>元素解析并赋值给POJO类Person。
<select id="selectPersonById1"> 这个查询元素接收一个int类型的id值,然后执行这个 select * from person where id=#{id} SQL语句。而后将数据库返回的结果交由 cardAndPerson1 这个resultMap处理。
<resultMap type="com.po.Person" id="cardAndPerson1"> 这个结果集元素返回类型为 com.po.Person 这个POJO类。其中数据库返回的主键列 id 的自动赋值给POJO类 Person 中的属性 id 。其后的 name 和 age 属性皆是如此映射关系。
重点为 <association> 一对一级联元素。它返回的类型为 com.po.Idcard 这个POJO类,映射到 com.po.Person 这个POJO类的 card 属性(读者可以看看Person类的定义)。其中column="idcard_id" 定义要传给其中select="com.dao.IdCardDao.selectCodeById" 这个元素对应的SQL语句的值。
第二种方法:嵌套结果,执行一个SQL语句,本表数据由<resultMap>元素解析,然后被关联的表数据交由<association> 元素解析,所以叫嵌套结果。
<!-- 一对一 根据id查询个人信息:第二种方法(嵌套结果,执行一个SQL语句,由resultMap解析结果) -->
<resultMap type="com.po.Person" id="cardAndPerson2">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<!-- 一对一关联查询 resultMap解析 Person 类中的 card 属性。
card 属性的类型是 Idcard,其中card.id 对应查询结果中的 idcard_id 列名
card.code 对应查询结果中的 code 列名-->
<association property="card" javaType="com.po.Idcard">
<id property="id" column="idcard_id"/>
<result property="code" column="code"/>
</association>
</resultMap>
<select id="selectPersonById2" parameterType="Integer" resultMap="cardAndPerson2">
select p.*,ic.code
from person p, idcard ic
where p.idcard_id = ic.id and p.id=#{id}
</select>
其实<association>元素在这里就相当于一个小的<resultMap>元素,用来解析数据库返回的数据,并赋值给POJO类。
第三种方法:写死SQL语句,数据传给一个自定义的POJO类,其中一个POJO类就是根据SQL结果特制的类。这种方法简洁明了,但是写死了SQL不利于后期程序改动,耦合性太高。
<!-- 一对一 根据id查询个人信息:第三种方法(写死SQL,使用自定义POJO存储结果) -->
<select id="selectPersonById3" parameterType="Integer" resultType="com.pojo.SelectPersonById">
select p.*,ic.code
from person p, idcard ic
where p.idcard_id = ic.id and p.id=#{id}
</select>
三种方法对应的Dao接口:
@Repository("personDao")
@Mapper
public interface PersonDao {
public Person selectPersonById1(Integer id);
public Person selectPersonById2(Integer id);
public SelectPersonById selectPersonById3(Integer id);
}
测试类:
ApplicationContext appCon = new ClassPathXmlApplicationContext("applicationContext.xml");
OneToOneController oto = (OneToOneController)appCon.getBean("oneToOneController");
oto.test();
整体结果截图,注意看SQL语句和传入的值,以及POJO类直接对应的关系其实就是表之间的关联关系:
7.8.2 一对多级联查询
在实际生活中一对多级联关系有许多,例如一个用户可以有多个订单,而一个订单只属于一个用户。
下面以用户和订单之间的关系为例,讲解一对多级联查询(实现“根据用户id查询用户及其关联的订单信息”的功能)的处理过程,读者只需参考该实例即可学会一对多级联查询的MyBatis实现。
1、创建数据表
本例子需要两个数据表,一张是前面已经创建了的user表,一张一订单表orders,其中orders表的外键user_id是user表的主键id。
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`id` tinyint(2) NOT NULL AUTO_INCREMENT,
`ordersn` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
`user_id` tinyint(2) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
CONSTRAINT `user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO orders VALUES ('1', '999999', '1');
INSERT INTO orders VALUES ('2', '88888', '1');
INSERT INTO orders VALUES ('3', '7777777', '31');
INSERT INTO orders VALUES ('4', '666666666', '31');
2、创建持久化类Orders,对应数据表orders
public class Orders {
private Integer id;
private String ordersn;
//多对多用到的商品表
private List<Product> products;
}
user表对应的POJO类MyUser加上这一条属性:
//一对多关联查询,用户关联的订单
private List<Orders> ordersList;
因为是一对多,所以用的List。
3、创建映射文件UserMapper.xml
还是有三种查询方法,嵌套查询和嵌套结果以及写死SQL方法。
其中<collection> 元素用于解析被关联的表数据,ofType="com.po.Orders" 表示MyUser.ordersList集合中的元素类型, column="uid"表示将uid传递给selectOrdersById。其他的都和一对一查询一样。
<!-- 一对多 根据uid查询用户及其关联的订单信息:第一种方法(嵌套查询) -->
<resultMap type="com.po.MyUser" id="userAndOrders1">
<id property="uid" column="uid"/>
<result property="uname" column="uname"/>
<result property="usex" column="usex"/>
<!-- 一对多关联查询,ofType表示集合中的元素类型,将uid传递给selectOrdersById-->
<collection property="ordersList" ofType="com.po.Orders" column="uid"
select="com.dao.OrdersDao.selectOrdersById"/>
</resultMap>
<select id="selectUserOrdersById1" parameterType="Integer" resultMap="userAndOrders1">
select * from user where uid = #{id}
</select>
<!-- 一对多 根据uid查询用户及其关联的订单信息:第二种方法(嵌套结果) -->
<resultMap type="com.po.MyUser" id="userAndOrders2">
<id property="uid" column="uid"/>
<result property="uname" column="uname"/>
<result property="usex" column="usex"/>
<!-- 一对多关联查询,ofType表示集合中的元素类型 -->
<collection property="ordersList" ofType="com.po.Orders" >
<id property="id" column="id"/>
<result property="ordersn" column="ordersn"/>
</collection>
</resultMap>
<select id="selectUserOrdersById2" parameterType="Integer" resultMap="userAndOrders2">
select u.*,o.id,o.ordersn from user u, orders o where u.uid = o.user_id and u.uid=#{id}
</select>
<!-- 一对多 写死SQL,根据uid查询用户及其关联的订单信息:第三种方法(使用POJO存储结果) -->
<select id="selectUserOrdersById3" parameterType="Integer" resultType="com.pojo.SelectUserOrdersById">
select u.*,o.id,o.ordersn from user u, orders o where u.uid = o.user_id and u.uid=#{id}
</select>
其中第三种方法写死SQL,自定义了一个POJO类,但是其对应的Dao接口方法是需要一个List返回对象:
public List<SelectUserOrdersById> selectUserOrdersById3(Integer uid);
其他的没什么,直接上数据和结果截图:
7.8.3 多对多级联查询
其实,MyBatis没有实现多对多级联,这是因为多对多级联可以通过两个一对多级联进行替换。例如,一个订单可以有多种商品,一种商品可以对应多个订单,订单与商品就是多对多的级联关系。使用一个中间表订单记录表,就可以将多对多级联转换成两个一对多的关系(仅体现在数据库表中,方便SQL查询,Mybatis中不体现)。下面以订单和商品(实现“查询所有订单以及每个订单对应的商品信息”的功能)为例,讲解多对多级联查询。
1、创建数据库表orders_detail product
DROP TABLE IF EXISTS `orders_detail`;
CREATE TABLE `orders_detail` (
`id` tinyint(2) NOT NULL AUTO_INCREMENT,
`orders_id` tinyint(2) DEFAULT NULL,
`product_id` tinyint(2) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `orders_id` (`orders_id`),
KEY `product_id` (`product_id`),
CONSTRAINT `orders_id` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`),
CONSTRAINT `product_id` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO orders_detail VALUES ('1', '1', '1');
INSERT INTO orders_detail VALUES ('2', '1', '2');
INSERT INTO orders_detail VALUES ('3', '2', '1');
INSERT INTO orders_detail VALUES ('4', '2', '2');
INSERT INTO orders_detail VALUES ('5', '3', '1');
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
`id` tinyint(2) NOT NULL,
`name` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
`price` double DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO product VALUES ('1', '好书', '88');
INSERT INTO product VALUES ('2', '坏书', '8.8');
2、创建持久化类Product和Orders
public class Product {
private Integer id;
private String name;
private Double price;
//多对多关系中的其中一个一对多
private List<Orders> orders;
}
public class Orders {
private Integer id;
private String ordersn;
//多对多关系中的其中另一个一对多
private List<Product> products;
}
3、创建映射文件
这个多对多和前面的一对多一模一样,看代码注释就行。
<!-- 多对多关联 查询所有订单以及每个订单对应的商品信息(嵌套结果)
一个SQL查询一切,剩下解析交给resultMap和对应的collection
===用Orders类实现多对多查询===
-->
<resultMap type="com.po.Orders" id="allOrdersAndProducts">
<id property="id" column="id"/>
<result property="ordersn" column="ordersn"/>
<!-- 多对多关联 ofType表示Orders.products这个集合中的元素类型Product-->
<collection property="products" ofType="com.po.Product">
<id property="id" column="pid"/>
<result property="name" column="name"/>
<result property="price" column="price"/>
</collection>
</resultMap>
<select id="selectallOrdersAndProducts" resultMap="allOrdersAndProducts">
select o.*,p.id as pid,p.name,p.price
from orders o,orders_detail od,product p
where od.orders_id = o.id
and od.product_id = p.id
</select>
<!-- 多对多关联 查询所有订单以及每个订单对应的商品信息(嵌套结果)
一个SQL查询一切,剩下解析交给resultMap和对应的collection -->
<!-- ===用Product类实现多对多查询=== -->
<resultMap type="com.po.Product" id="allOrdersAndProducts2">
<id property="id" column="pid"/>
<result property="name" column="name"/>
<result property="price" column="price"/>
<!-- 多对多关联 ofType表示Orders.orders这个集合中的元素类型Orders-->
<collection property="orders" ofType="com.po.Orders">
<id property="id" column="id"/>
<result property="ordersn" column="ordersn"/>
</collection>
</resultMap>
<select id="selectallOrdersAndProducts2" resultMap="allOrdersAndProducts2">
select o.*,p.id as pid,p.name,p.price
from orders o,orders_detail od,product p
where od.orders_id = o.id
and od.product_id = p.id
</select>
其中这个SQL就是查询所有订单及其对应的订单详情:
对应的Dao方法:
public List<Orders> selectallOrdersAndProducts();
运行截图:
本章知识点讲解完毕,重点就是后面的级联查询,大家有不懂的可以公众号留言,也可以看看公共号的其他相关文章,最近都是有关Mybatis框架知识的,希望对您有所帮助,本系列教程所有源码见下面地址。
本教程所有源码地址:
https://github.com/jiahaoit/java_ssm_course