Mybatis
-
ORM:对象关系映射。每张表要和一个JavaBean对应
- 关系->对象:指从表里查询数据,把结果封装成对应的JavaBean对象
- 对象->关系:指把JavaBean里的数据,保存到表里对应的字段上
- Hibernate是一个全ORM框架
- Mybatis是一个半ORM框架:只实现了 关系->对象
-
搭建Mybatis开发环境
-
创建项目,导入jar包,准备JavaBean
-
使用Mybatis实现功能
-
先创建dao接口(映射器Mapper):不需要实现类
-
再给每个接口创建一个xml文件(映射配置文件),主要配置每个方法的SQL语句:
映射文件和映射器接口,要求:同名同位置
-
还需要创建一个全局配置文件:主要配置数据源、映射器
全局配置文件名称随意,位置建议放到src下
-
可以再配置一个日志配置文件
-
-
功能测试
//1. 加载全局配置文件 InputStream is = Resources.getResourceAsStream("全局配置文件"); //2. 得到一个SqlSession对象 SqlSessionFactory factory = new SqlSesionFactoryBuilder().build(is); SqlSession session = factory.openSession(); //3. 得到dao接口的代理对象 UserDao userDao = session.getMapper(UserDao.class); //4. 释放资源 session.close(); is.close();
-
-
代理方式的CURD功能实现
- 在dao接口里增加方法
- 在接口配置文件里配置SQL语句
<select id="方法名" parameterType="参数类型" resultType="结果集封装的类型"> select语句 </select> <insert id="方法名" parameterType="参数类型"> <selectKey resultType="主键值类型" keyProperty="主键值存储到哪个属性" order="AFTER"> select last_insert_id() </selectKey> insert语句 </insert> <update id="方法名" parameterType="参数类型"> update语句 </update> <delete id="方法名" parameterType="参数类型"> delete语句 </delete>
-
参数说明
-
单参数
- 如果参数是简单类型,SQL语句里取参数值:
#{随便}
- 如果参数是JavaBean,SQL语句里取参数值:
#{JavaBean的属性名}
- 如果参数是复杂JavaBean,SQL语句里取参数值:
#{JavaBean属性名.属性名}
- 如果参数是简单类型,SQL语句里取参数值:
-
多参数
- SQL取值方案一:
#{arg0}, #{arg1}, ....
- SQL取值方案二:
#{param1}, #{param2}, ...
- 给参数命名,根据名称取参数值
List<User> search(@Param("username")String username, @Param("sex")String sex);
<select> select * from user where username like "%"#{username}"%" and sex = #{sex} </select>
- SQL取值方案一:
-
-
结果集封装
- resultType:自动映射。要求JavaBean属性名 和 表字段名/字段别名 相同
- resultMap:手动映射。手动配置一下 JavaBean属性名 和 表字段名的对应关系
<select id="" resultMap="map"> </select> <resultMap id="map" type="JavaBean的全限定类名/别名"> <id property="JavaBean属性名" column="主键字段名"/> <result property="JavaBean属性名" column="表字段名"/> </resultMap>
-
全局配置文件
<typeAliases> <package name="com.is.fwis.domain"/> </typeAliases> <environments default=""> .... </environments> <mappers> <package name="com.is.fwis.dao"/> </mappers>
一、动态sql拼接
目标
- 能够使用mybatis的标签实现动态SQL拼接
分析
我们在前边的学习过程中,使用的SQL语句都非常简单。而在实际业务开发中,我们的SQL语句通常是动态拼接而成的,比如:条件搜索功能的SQL语句。
# 提供了一个功能:用户可以在页面上根据username、sex、address进行搜索
# 用户输入的搜索条件:可以是一个条件,也可能是两个、三个
# 只输入一个条件:姓名是"王"
SELECT * FROM USER WHERE username LIKE '%王%'
# 只输入一个条件:性别是“男”
SELECT * FROM USER WHERE sex = '男'
# 输入两个条件:姓名“王”,性别“男”
SELECT * FROM USER WHERE username LIKE '%王%' AND sex = '男'
# 输入三个条件:姓名“王”,性别“男”,地址“北京”
SELECT * FROM USER WHERE username LIKE '%王%' AND sex = '男' AND address LIKE '%北京%';
在Mybatis中,SQL语句是写在映射配置的XML文件中的。Mybatis提供了一些XML的标签,用来实现动态SQL的拼接。
常用的标签有:
<if></if>
:用来进行判断,相当于Java里的if判断<where></where>
:通常和if配合,用来代替SQL语句中的where 1=1
<foreach></foreach>
:用来遍历一个集合,把集合里的内容拼接到SQL语句中。例如拼接:in (value1, value2, ...)
<sql></sql>
:用于定义sql片段,达到重复使用的目的
讲解
1. 准备Mybatis环境
- 创建java项目,导入jar包;准备JavaBean
- 创建映射器接口UserDao
- 创建映射配置文件UserDao.xml
- 创建全局配置文件SqlMapConfig.xml
- 创建日志配置文件log4j.properties
2. <if>
标签:
语法介绍
<if test="判断条件,使用OGNL表达式进行判断">
SQL语句内容, 如果判断为true,这里的SQL语句就会进行拼接
</if>
使用示例
- 根据用户的名称和性别搜索用户信息。把搜索条件放到User对象里,传递给SQL语句
- 映射器接口UserDao上加方法
package com.is.fwis.dao;
import com.is.fwis.domain.User;
import java.util.List;
/**
* @author lcz
* @date 2021/12/7
*/
public interface UserDao {
/**
* 根据username和sex搜索用户
* @param user 封装了搜索条件的User对象
* @return 搜索的结果
*/
List<User> search1(User user);
}
- 映射文件UserDao.xml里配置statement
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.is.fwis.dao.UserDao">
<!--
if标签:用于条件判断
语法:<if test="用OGNL表达式判断"> 如果判断为true,这里的内容会拼接上去 </if>
注意:标签里写OGNL表达式,不要再加#{}、${}
常用的OGNL表达式:
比较:>, <, >=, <=, ==, != 或者 gt, lt, gte, lte, eq, neq
逻辑:&&,||,! 或者 and, or, not
调用方法:username.length(), list.size()
-->
<select id="search1" resultType="User">
select * from user where 1=1
<if test="username != null and username.length()>0">
and username like "%"#{username}"%"
</if>
<if test="sex != null and sex.length()>0">
and sex = #{sex}
</if>
</select>
</mapper>
- 功能测试,在测试类里加测试方法
package com.is.fwis;
import com.is.fwis.dao.UserDao;
import com.is.fwis.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* @author lcz
* @date 2021/12/07
*/
public class SqlTest {
private UserDao userDao;
private SqlSession session;
private InputStream is;
/**
* 要求:根据username和sex搜索用户
* 搜索条件放到user对象里
*/
@Test
public void testSearch(){
User user = new User();
// user.setUsername("王");
// user.setSex("男");
List<User> userList = userDao.search1(user);
userList.forEach(System.out::println);
}
@Before
public void init() throws IOException {
//1. 读取全局配置文件
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 得到一个SqlSession对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
session = factory.openSession();
userDao = session.getMapper(UserDao.class);
}
@After
public void destroy() throws IOException {
session.close();
is.close();
}
}
3. <where>
标签
语法介绍
在刚刚的练习的SQL语句中,我们写了where 1=1
。如果不写的话,SQL语句会出现语法错误。Mybatis提供了一种代替where 1=1
的技术:<where></where>
标签。
代码示例
把上一章节的实现代码进行优化,使用<where></where>
标签代替where 1=1
- 映射器UserDao的search1方法:已有,不用修改
/**
* 根据username和sex搜索用户
* @param user 封装了搜索条件的User对象
* @return 搜索的结果
*/
List<User> search1(User user);
- 在映射文件UserDao.xml里修改SQL语句
<!--
where标签:让Mybatis帮我们生成一个where关键字
Mybatis会智能判断:
如果一个条件都没有,就不生成where关键字
如果有条件,会判断是否有多余的and关键字,把多余的and去掉
注意:建议把所有的where条件都放到where标签里边
-->
<select id="search1" resultType="User">
select * from user
<where>
<if test="username != null and username.length()>0">
and username like "%"#{username}"%"
</if>
<if test="sex != null and sex.length()>0">
and sex = #{sex}
</if>
</where>
</select>
- 在测试类里进行功能测试:测试方法不需要修改
@Test
public void testSearch(){
User user = new User();
// user.setUsername("王");
// user.setSex("男");
List<User> userList = userDao.search1(user);
userList.forEach(System.out::println);
}
4. <foreach>
标签
语法介绍
foreach标签,通常用于循环遍历一个集合,把集合的内容拼接到SQL语句中。例如,我们要根据多个id查询用户信息,SQL语句:
select * from user where id = 1 or id = 2 or id = 3;
select * from user where id in (1, 2, 3);
假如我们传参了id的集合,那么在映射文件中,如何遍历集合拼接SQL语句呢?可以使用foreach
标签实现。
<!--
foreach标签:
属性:
collection:被循环遍历的对象,使用OGNL表达式获取,注意不要加#{}
open:循环之前,拼接的SQL语句的开始部分
item:定义变量名,代表被循环遍历中每个元素,生成的变量名
separator:分隔符
close:循环之后,拼接SQL语句的结束部分
标签体:
使用#{OGNL}表达式,获取到被循环遍历对象中的每个元素
-->
<foreach collection="" open="id in(" item="id" separator="," close=")">
#{id}
</foreach>
使用示例
- 有搜索条件类QueryVO如下:
package com.is.fwis.domain;
public class QueryVO {
private Integer[] ids;
public Integer[] getIds() {
return ids;
}
public void setIds(Integer[] ids) {
this.ids = ids;
}
}
- 在映射器UserDao里加方法
/**
* QueryVO里有一个Integer[] ids
* 要求:根据ids查询对应的用户列表
*/
List<User> search2(QueryVO vo);
- 在映射文件UserDao.xml里配置statement
<!--
foreach标签:用于循环遍历
collection:被循环的集合/数组
item:定义一个变量
separator:定义拼接时的分隔符
open:拼接字符串时的开始部分
close:拼接字符串时的结束部分
相当于 for(Integer id: ids){}
select * from user where id in(41, 42, 45)
-->
<select id="search2" resultType="User">
<!--select * from user where id in(41, 42, 45)-->
select * from user where
<foreach collection="ids" open="id in(" item="id" separator="," close=")">
#{id}
</foreach>
</select>
- 功能测试
@Test
public void testSearch2(){
QueryVO vo = new QueryVO();
vo.setIds(new Integer[]{41,42,43,44,45});
List<User> userList = userDao.search2(vo);
userList.forEach(System.out::println);
}
5. <sql>
标签
在映射文件中,我们发现有很多SQL片段是重复的,比如:select * from user
。Mybatis提供了一个<sql>
标签,把重复的SQL片段抽取出来,可以重复使用。
语法介绍
在映射文件中定义SQL片段:
<sql id="唯一标识">sql语句片段</sql>
在映射文件中引用SQL片段:
<include refid="sql片段的id"></include>
使用示例
在查询用户的SQL中,需要重复编写:select * from user
。把这部分SQL提取成SQL片段以重复使用
- 要求:QueryVO里有ids,user对象。根据条件进行搜索
- 修改QueryVO,增加成员变量user
package com.is.fwis.domain;
/**
* @author lcz
* @date 2021/12/07
*/
public class QueryVO {
private Integer[] ids;
private User user;
//get/set方法……
}
- 在映射器UserDao里加方法
/**
* 动态SQL拼接的综合应用:if、where、foreach
* 要求:QueryVo里有ids、username、sex值,根据这些值进行搜索
*/
List<User> search3(QueryVO vo);
- 在映射文件UserDao.xml里配置statement
<select id="search3" resultType="User">
<!--select * from user-->
<include refid="selUser"/>
<where>
<if test="ids != null and ids.length > 0">
<foreach collection="ids" open="and id in(" item="id" separator="," close=")">
#{id}
</foreach>
</if>
<!--<if test="user != null">
<if test="user.username != null and user.username.length() > 0">
and username like "%"#{user.username}"%"
</if>
<if test="user.sex != null and user.sex.length() > 0">
and sex = #{user.sex}
</if>
</if>-->
<include refid="userCondition"/>
</where>
</select>
<!--
sql标签:用于定义一个sql片段
include标签:什么时候要引用某个SQL片段,就使用include标签
注意:引入SQL片段之后,最终的SQL语句必须要完全符合语法
-->
<sql id="selUser">select * from user</sql>
<sql id="userCondition">
<if test="user != null">
<if test="user.username != null and user.username.length() > 0">
and username like "%"#{user.username}"%"
</if>
<if test="user.sex != null and user.sex.length() > 0">
and sex = #{user.sex}
</if>
</if>
</sql>
- 在测试类里加测试方法
@Test
public void testSearch3(){
QueryVO vo = new QueryVO();
vo.setIds(new Integer[]{41,42,43,44,45});
// User user = new User();
// user.setUsername("王");
// user.setSex("男");
// vo.setUser(user);
List<User> userList = userDao.search3(vo);
userList.forEach(System.out::println);
}
小结
<select>
select * from user
<where>
<if test="ids != null and ids.length > 0">
<foreach collection="ids" item="id" separator="," open="and id in(" close=")">
#{id}
</foreach>
</if>
<if test="user != null">
<if test="username != null and username.length()>0">
and username like "%"#{username}"%"
</if>
<if test="sex != null and sex.length()>0">
and sex = #{sex}
</if>
</if>
</where>
</select>
<select>
select * from user
<include refid="condition"/>
</select>
<sql id="condition">
<where>
<if test="ids != null and ids.length > 0">
<foreach collection="ids" item="id" separator="," open="and id in(" close=")">
#{id}
</foreach>
</if>
<if test="user != null">
<if test="username != null and username.length()>0">
and username like "%"#{username}"%"
</if>
<if test="sex != null and sex.length()>0">
and sex = #{sex}
</if>
</if>
</where>
</sql>
拓展:<set>
标签
语法介绍
set标签用于代替update语句 的set关键字,实现动态拼接update语句,它可以用来动态包含需要更新的列,帮我们去掉多余的逗号。
使用示例
- 在映射器接口UserDao里添加方法
void edit(User user);
- 在映射文件UserDao.xml里配置SQL语句
<update id="edit" parameterType="com.is.fwis.domain.User">
update user
<set>
<if test="username!=null and username.length()>0">username = #{username},</if>
<if test="sex != null and sex.length()>0">sex = #{sex},</if>
<if test="birthday != null">birthday = #{birthday},</if>
</set>
where id = #{id}
</update>
- 功能测试
@Test
public void testEdit(){
User user = new User();
user.setId(51);
user.setUsername("张三");
userDao.edit(user);
session.commit();
}
二、Mybatis的缓存
缓存:高速的临时数据存储
1. 准备环境
略。按步骤准备好User和UserDao、UserDao.xml、SqlMapConfig.xml、log4j.properties
2. 一级缓存
目标
- 了解Mybatis的一级缓存
讲解
一级缓存介绍
- 一级缓存,是SqlSession对象提供的缓存
- 执行一次查询之后,查询的结果(JavaBean对象)会被缓存到SqlSession中。
- 再次查询同样的数据,Mybatis会优先从缓存中查找;如果找到了,就不再查询数据库。
一级缓存的清除
- 当调用了SqlSession对象的修改、添加、删除、commit()、close()、clearCache()等方法时,一级缓存会被清空。
效果演示
package com.is.fwis;
import com.is.fwis.dao.UserDao;
import com.is.fwis.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
/**
* Mybatis本身支持缓存:
*
* 一级缓存:缓存的有效范围较小,在同一个SqlSession里有效。
* 缓存本质是一个HashMap,而这个Map在SqlSession对象内部
* 使用一个SqlSession,第一次查询id为41的用户:没有缓存,要从数据库里查询;查询完成,结果会被缓存起来
* 使用同一SqlSession,再一次查询id为41的用户:有缓存了,就从缓存里直接得到结果,不会再执行SQL语句了
*
* 二级缓存:缓存的有效范围较大,多个SqlSession可以共享二级缓存的数据
*
*
* @author lcz
* @date 2021/12/07
*/
public class CacheTest {
private UserDao userDao;
private SqlSession session;
private InputStream is;
private SqlSessionFactory factory;
/**
* 第一次查询:
* 使用一个SqlSession,查询id为41的用户,得到:user1
* 第二次查询:
* 使用同一SqlSession,查询id为41的用户,得到:user2
* 验证:
* 1. 如果一级缓存有效,SQL语句会执行几次?一次
* 2. 如果一级缓存有效,两次得到的user对象是否是同一个?是
*
* 当我们执行了增、删、改、关闭session、提交事务、clearCache方法时,一级缓存会被清除掉
*/
@Test
public void testLevel1Cache(){
User user1 = userDao.findById(41);
//如果在这里使用SqlSession执行了增、删、改、关闭session、提交事务、clearCache方法时,Mybatis会把缓存清除掉
session.clearCache();
User user2 = userDao.findById(41);
System.out.println(user1==user2);
}
@Before
public void init() throws IOException {
//1. 读取全局配置文件
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 得到一个SqlSession对象
factory = new SqlSessionFactoryBuilder().build(is);
session = factory.openSession();
userDao = session.getMapper(UserDao.class);
}
@After
public void destroy() throws IOException {
session.close();
is.close();
}
}
3. 二级缓存
目标
- 了解Mybatis二级缓存的效果
讲解
3.1 什么是二级缓存
- 二级缓存是Mapper级别的缓存(映射器级别的缓存)
- 同样的映射器Mapper共享二级缓存。UserDao
- 注意:
- 二级缓存,缓存的是序列化之后的数据;
- 当从缓存里取数据时,要进行反序列化还原成JavaBean对象
- 要求JavaBean必须实现序列化接口
Serializable
- 二级缓存需要手动开启
3.2 开启二级缓存
- 修改全局配置文件,开启二级缓存
<settings>
<!--开启全局的二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
-
哪个映射器要使用二级缓存,就在哪个映射文件里开启二级缓存支持
-
哪个方法要使用二级缓存,就在方法的配置statement上加
useCache="true"
3.3 效果演示
/**
* 二级缓存:全局缓存。即使把session关闭了,重新得到一个session查询相同数据时,二级缓存依然有效
* 二级缓存默认是关闭的,需要手动开启
*
* 开启二级缓存
* 1. 在全局配置文件里开启二级缓存
* 2. 哪个映射器需要用二级缓存,就在映射配置文件里开启
* 3. 哪个方法需要用二级缓存,就在方法的配置上,增加属性useCache="true"
* 注意:二级缓存,存储的是序列化之后的字节;获取缓存数据时会反序列化恢复成JavaBean对象
* 给User类实现Serializable接口
*
* 第一次:
* 使用factory得到一个SqlSession:session1
* 使用session1得到UserDao代理对象:userDao1
* 使用userDao1查询id为41的用户,得到:user1
* 关闭session1
*
* 第二次:
* 使用factory得到一个SqlSession:session2
* 使用session2得到UserDao代理对象:userDao2
* 使用userDao2查询id为41的用户,得到:user2
* 关闭session2
*
* 验证:
* 如果二级缓存有效,SQL语句会执行几次?一次
* 如果二级缓存有效,两次得到的user对象地址是否相同?不同
*/
@Test
public void testLevel2Cache(){
//第一次:
SqlSession session1 = factory.openSession();
UserDao userDao1 = session1.getMapper(UserDao.class);
User user1 = userDao1.findById(41);
session1.close();
//第二次:
SqlSession session2 = factory.openSession();
UserDao userDao2 = session2.getMapper(UserDao.class);
User user2 = userDao2.findById(41);
session2.close();
System.out.println(user1==user2);
}
三、嵌套查询(级联查询)
如果要查询一条数据,有很多关联的其它数据,该如何实现呢?可以使用Mybatis的查询嵌套方式实现需求
如下图所示:
- 要查询一条用户数据,封装成User
- 但是User里还要有:
- 当前User关联的订单列表
- 当前User关联的帐号列表
- 当前User关联的角色列表
- 当前User关联的收货地址列表
- 当前User关联的用户详情
1. 准备开发环境
- 创建项目,导入jar包,创建JavaBean
- 创建映射器接口UserDao和AccountDao
- 创建映射文件UserDao.xml 和AccountDao.xml
- 创建Mybatis的全局配置文件。 注意,要配置好别名和映射器
- 准备好log4j日志配置文件
- 编写单元测试类备用
2. 关联一项数据
目标
- 查询所有帐号,及关联的用户。一个帐号Account只关联一个用户User
分析
- 修改实体类Account
- 在映射器里加方法
- 在映射文件配置statement:
- 编写SQL语句,只查询所有的帐号
select * from account
- 编写SQL语句,只查询所有的帐号
- 使用resultMap封装结果集,使用association封装关联的那个User
实现
1. 修改实体类Account
public class Account {
private Integer id;
private Integer uid;
private Double money;
private User user;
//get/set...
}
2. 在AccountDao里增加方法
/**
* 查询所有帐号,及每个帐号关联的一个User。一个Account关联一个User
*/
List<Account> queryAllAccount();
3. 在AccountDao.xml里配置statement
<select id="queryAllAccount" resultMap="accountMap">
select * from account
</select>
<resultMap id="accountMap" type="Account">
<id property="id" column="id"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!--
一个Account关联一个User:使用association标签
property:属性名
javaType:这个属性的类型,写全限定类名或者别名
select:让Mybatis调用哪个方法,可以得到这个Account关联的User对象
需要一个方法:查询某帐号关联的用户=>根据帐号里的uid查询对应的User
column:让Mybatis调用方法时,方法需要的参数,从当前结果集哪个字段里取
-->
<association property="user" javaType="User"
select="com.is.fwis.dao.UserDao.findById"
column="uid"/>
</resultMap>
4. 编写“查询一个用户”的方法
1. 在UserDao里增加方法
/**
* 根据uid查询对应的User
*/
User findById(Integer uid);
2. 在UserDao.xml配置statement
<select id="findById" resultType="User">
select * from user where id = #{uid}
</select>
5. 测试
package com.is.fwis;
import com.is.fwis.dao.AccountDao;
import com.is.fwis.dao.UserDao;
import com.is.fwis.domain.Account;
import com.is.fwis.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* 嵌套查询(级联查询)测试
* @author lcz
* @date 2021/12/07
*/
public class NestedQueryTest {
private InputStream is;
private SqlSession session;
private UserDao userDao;
private AccountDao accountDao;
/**
* 查询帐号,及关联的用户。一个帐号关联一个用户
*/
@Test
public void testQueryAllAccount(){
List<Account> accounts = accountDao.queryAllAccount();
accounts.forEach(System.out::println);
}
@Before
public void init() throws IOException {
//1. 读取全局配置文件
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 得到一个SqlSession对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
session = factory.openSession();
userDao = session.getMapper(UserDao.class);
accountDao = session.getMapper(AccountDao.class);
}
@After
public void destroy() throws IOException {
session.close();
is.close();
}
}
3. 关联一个集合
目标
- 查询用户信息,及其关联的帐号信息集合。
分析
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
/**因为一个用户有多个帐号,所以这里要有Account的集合*/
private List<Account> accounts;
//get/set...
}
- 在UserDao里增加方法,查询所有用户。执行的语句
select * from user
- 在UserDao.xml里配置statement:
- 要使用resultMap封装结果集
- 当封装User里accounts时,使用
<collection property="accounts" ofType="Account" select="" column=""/>
实现
1. 在UserDao里增加方法
/**
* 查询所有用户,及关联的帐号集合。一个User关联多个Account
*/
List<User> queryAllUser();
2. 在UserDao.xml里配置statement
<select id="queryAllUser" resultMap="userMap">
select * from user
</select>
<resultMap id="userMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<!--
关联一个account集合:使用collection标签
property:属性名称
ofType:集合里的每个成员的类型,写全限定类名或别名
select:让Mybatis调用哪个方法,可以得到这个集合
需要有一个方法:查询当前这个用户的帐号集合
column:让Mybatis调用方法时,方法的参数从哪个字段取
-->
<collection property="accounts" ofType="Account"
select="com.is.fwis.dao.AccountDao.findAccountsByUid"
column="id"/>
</resultMap>
3. 准备根据用户id查询帐号集合的功能
3.1 在AccountDao里增加方法
/**
* 查询某一用户的帐号集合
* @param uid 用户id
* @return uid对应的帐号集合
*/
List<Account> findAccountsByUid(Integer uid);
3.2 在AccountDao.xml里配置statement
<select id="findAccountsByUid" resultType="Account">
SELECT * FROM account WHERE uid = #{uid}
</select>
4. 测试
/**
* 查询用户,及关联的帐号。一个用户关联多个帐号
*/
@Test
public void testQueryAllUser(){
List<User> userList = userDao.queryAllUser();
userList.forEach(System.out::println);
}
四、延迟加载
在多表关联查询时,比如查询用户信息,及其关联的帐号信息,在查询用户时就直接把帐号信息也一并查询出来了。但是在实际开发中,并不是每次都需要立即使用帐号信息,这时候,就可以使用延迟加载策略了。
需要在查询嵌套方式的基础上,才能开启懒加载。
1. 什么是延迟加载
立即加载
- 不管数据是否需要使用,只要调用了方法,就立即发起查询。
- 比如:查询帐号,得到关联的用户;查询用户,得到关联的帐号
延迟加载
-
延迟加载,也叫按需加载,或者叫懒加载。
- 只有当真正使用到数据的时候,才发起查询。不使用不发起查询
- 比如:查询用户信息,不使用accounts的时候,不查询帐号的数据;只有当使用了用户的accounts,Mybatis再发起查询帐号的信息
-
先从单表查询,需要使用关联数据时,才进行关联数据的查询。单表查询的速度要比多表关联查询速度快,性能高;
-
用不上的数据,暂时不查询,内存占用小
延迟加载的使用场景
- 一对一(多对一),通常不使用延迟加载(建议)。比如:查询帐号,关联加载用户信息
- 一对多(多对多),通常使用延迟加载(建议)。比如:查询用户,关联加载帐号信息
2. 实现延迟加载
只需要在查询嵌套方式的基础上,开启懒加载即可。 开启懒加载方式:
方式一:在全局配置文件中增加配置,开启全局的懒加载
<settings>
<!--开启 懒加载的全局开关-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--关闭 积极加载-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
方式二:局部配置懒加载
- 在
association
标签上、或collection
标签上增加属性fetchType
,把当前关联加载设置为懒加载 - 局部配置,会覆盖掉全局配置
<association fetchType="lazy"></association>
<collection fetchType="lazy"></collection>
注意:
- 在配置了延迟加载策略后,即使没有调用关联对象的任何方法,当你调用当前对象的
equals、clone、hashCode、toString方法时也会触发关联对象的查询。 - 在配置文件中可以使用lazyLoadTriggerMethods配置项覆盖掉mybatis的默认行为。
<!-- 指定对象的哪些方法触发加载 -->
<setting name="lazyLoadTriggerMethods" value="getUid,toString"/>
总结
- 懒加载:延迟加载,按需加载。当用不到关联的数据时,就不查询;只要用到时,才会查询
- 开启懒加载:
- 全局开启
- 局部开启:在association标签或者collection标签上加属性
fetchType="lazy"
五、多表关联查询
1. 搭环境
搭建Mybatis开发环境:略
针对user表和account表进行关联查询。两张表的关系是:一个user对多个account
分别准备好两张表对应的实体类User
, Account
2. 一对一(多对一)关联查询
目标
- 需求:查询所有帐户表信息,及其关联的用户信息
分析
-
编写多表查询的SQL语句。
注意:查询结果集里一定不能有重名列;如果有重名列,就起别名,保证所有列名不重复
#查询所有帐户表信息,及其关联的用户信息 SELECT a.id aid, a.uid, a.money, u.* FROM account a LEFT JOIN USER u ON a.uid = u.id
-
调整的JavaBean实体类
要查询帐号,就封装到Account对象里
每个帐号关联一个用户数据:在Account里加一个成员变量
private User user
-
使用Mybatis执行SQL,使用resultMap封装结果
当封装关联的那个User时,使用association标签
实现
1) 修改Account类
Account中要有User的引用
public class Account {
private Integer id;
private Integer uid;
private Double money;
private User user;
//get/set...
}
2) 在映射器接口AccountDao中增加方法
/**
* @author lcz
* @date 2021/12/07
*/
public interface AccountDao {
/**
* 查询所有帐号,及每个帐号关联的一个User。一个Account关联一个User
*/
List<Account> queryAllAccount();
}
3) 在映射文件AccountDao.xml中增加statement
- 使用resultMap手动映射封装结果集
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.is.fwis.dao.AccountDao">
<select id="queryAllAccount" resultMap="accountMap">
SELECT a.id aid, a.uid, a.money, u.* FROM account a LEFT JOIN USER u ON a.uid = u.id
</select>
<resultMap id="accountMap" type="Account">
<id property="id" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<association property="user" javaType="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
</association>
</resultMap>
</mapper>
4) 在单元测试类中编写测试代码
package com.is.fwis;
import com.is.fwis.dao.AccountDao;
import com.is.fwis.dao.UserDao;
import com.is.fwis.domain.Account;
import com.is.fwis.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* 多表查询测试
* @author lcz
* @date 2021/12/07
*/
public class DuobiaoQueryTest {
private InputStream is;
private SqlSession session;
private UserDao userDao;
private AccountDao accountDao;
@Test
public void testQueryAllAccount(){
List<Account> accounts = accountDao.queryAllAccount();
accounts.forEach(System.out::println);
}
@Before
public void init() throws IOException {
//1. 读取全局配置文件
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 得到一个SqlSession对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
session = factory.openSession();
userDao = session.getMapper(UserDao.class);
accountDao = session.getMapper(AccountDao.class);
}
@After
public void destroy() throws IOException {
session.close();
is.close();
}
}
3. 一对多(多对多)关联查询
- 查询所有用户(user)信息,以及每个用户拥有的所有帐号(account)信息
分析
-
编写多表查询SQL语句
注意:查询结果集里不能有重名列。如果有,就起别名,保证所有列名不重复
#查询所有用户(user)信息,以及每个用户拥有的所有帐号(account)信息 SELECT u.*, a.id aid, a.uid, a.money FROM USER u LEFT JOIN account a ON u.id = a.uid
-
改造JavaBean
把查询结果集里user表数据,封装到User对象里
一个用户关联多个帐号,所以在User里应该有一个
private List<Account> accounts;
-
执行SQL语句,用resultMap封装结果;
当封装到其中关联的集合
List<Account>
时,使用collection标签封装
实现
1) 修改User类
User类中要有List<Account>
,用于保存用户拥有的帐号信息集合
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
/**因为一个用户有多个帐号,所以这里要有Account的集合*/
private List<Account> accounts;
//get/set...
}
2) 在映射器接口UserDao中增加方法
package com.is.fwis.dao;
import com.is.fwis.domain.User;
import java.util.List;
/**
* @author lcz
* @date 2021/12/07
*/
public interface UserDao {
List<User> queryAllUser();
}
3) 在配置文件UserDao.xml中增加statement
- 封装结果集时,要使用resultMap手动映射
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.is.fwis.dao.UserDao">
<select id="queryAllUser" resultMap="userMap">
SELECT u.*, a.id aid, a.uid, a.money FROM USER u LEFT JOIN account a ON u.id = a.uid
</select>
<resultMap id="userMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<collection property="accounts" ofType="Account">
<id property="id" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
</collection>
</resultMap>
</mapper>
4) 在单元测试类中编写测试代码
/**
* 查询所有用户,及关联的帐号列表。
* 要求:使用一条 多表查询语句 实现
*/
@Test
public void testQueryAllUser(){
List<User> userList = userDao.queryAllUser();
userList.forEach(System.out::println);
}