本章主要内容:核心配置文件、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