程序地带

Mybatis2


一、SQL深入-动态sql【重点】

​ 我们在前边的学习过程中,使用的SQL语句都非常简单。而在实际业务开发中,我们的SQL语句通常是动态拼接而成的,比如:条件搜索功能的SQL语句。


​ 在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片段,达到重复使用的目的
准备环境

​ 我们以对user表的操作为例,演示这些标签的用法。先准备Mybatis的环境如下


创建java项目,导入jar包

创建JavaBean实体类:User

创建映射器接口UserDao

创建映射文件UserDao.xml

创建Mybatis的核心配置文件,配置好类型别名和映射器

准备log4j日志配置文件

准备好单元测试类

public class MybatisSqlTest {
private InputStream is;
private SqlSession session;
private UserDao dao;
@Before
public void init() throws IOException {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
session = factory.openSession();
dao = session.getMapper(UserDao.class);
}
@After
public void destory() throws IOException {
session.close();
is.close();
}
}
<if>标签:
<if test="判断条件,使用OGNL表达式进行判断">
SQL语句内容, 如果判断为true,这里的SQL语句就会进行拼接
</if>
需求描述

根据用户的名称和性别搜索用户信息。

把搜索条件放到User对象里,传递给SQL语句


需求实现
1) 在映射器UserDao中增加方法
//根据user搜索用户信息
List<User> search(User user);
2) 在映射文件UserDao.xml中配置
在if标签的test属性中,直接写OGNL表达式,从parameterType中取值进行判断,不需要加#{}或者${}
<select id="search" parameterType="user" 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 username.length() > 0">
and sex = #{sex}
</if>
</select>
3) 在单元测试类中编写测试代码
@Test
public void testSearch(){
User user = new User();
user.setUsername("%王%");
user.setSex("男");
List<User> users = dao.search(user);
for (User u : users) {
System.out.println(u);
}
}
<where>标签

在刚刚的练习的SQL语句中,我们写了where 1=1。如果不写的话,SQL语句会出现语法错误。Mybatis提供了一种代替where 1=1的技术:<where></where>标签。


<where>标签代替了where 1=1

<where>标签内拼接的SQL没有变化,每个if的SQL中都有and

<where>标签会自动处理掉第一条件里前边的and,以保证SQL语法正确


需求描述

​ 使用==<where></where>标签代替where 1=1==


需求实现
只需要修改一下配置文件:
<select id="search" parameterType="user" resultType="user">
select * from user
<where>
<if test="username != null and username.length() > 0">
and username like #{username}
</if>
<if test="sex != null and username.length() > 0">
and sex = #{sex}
</if>
</where>
</select>
再次运行测试代码,查看结果仍然正常
小结
<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>
id in(41,42,44,45)
需求描述

QueryVO中有一个属性ids, 是id值的集合。根据QueryVO中的ids查询用户列表。


​ QueryVO类如下:


public class QueryVO {
private Integer[] ids;
public Integer[] getIds() {
return ids;
}
public void setIds(Integer[] ids) {
this.ids = ids;
}
}
需求实现
1) 在映射器接口UserDao中增加方法
List<User> findByIds(QueryVO vo);
2) 在映射文件UserDao.xml中添加statement
<!--在核心配置文件中已经使用package配置了类型别名-->
<select id="findByIds" resultType="user" parameterType="queryvo">
select * from user
<where>
<foreach collection="ids" open="and id in (" item="id" separator="," close=")">
#{id}
</foreach>
</where>
</select>
3) 在单元测试类中编写测试代码
@Test
public void testFindUserByIdsQueryVO(){
QueryVO vo = new QueryVO();
vo.setIds(new Integer[]{41, 42});
List<User> userList = dao.findByIds(vo);
for (User user : userList) {
System.out.println(user);
}
}
小结
<foreach collection="被循环遍历的集合" item="每一个元素的变量名" separator="分隔符" open="前缀" close="后缀">
#{每一个元素的变量名}
</foreach>
拼接的结果是:前缀 + 用指定分隔符拼接的字符串 + 后缀
<sql>标签

在映射文件中,我们发现有很多SQL片段是重复的。Mybatis提供了一个<sql>标签,把重复的SQL片段抽取出来,可以重复使用。


定义SQL片段:


<sql id="唯一标识">sql语句片段</sql>

引用SQL片段:


<include refid="sql片段的id"></include>

扩展:


​ 如果想要引入其它映射文件中的sql片段,那么<include>标签的refid的值,需要在sql片段的id前指定namespace。例如:


​ <include refid="com.itheima.dao.RoleDao.selectRole"></include>


​ 表示引入了namespace为com.itheima.dao.RoleDao的映射文件中id为selectRole的sql片段


需求描述

在查询用户的SQL中,需要重复编写:select * from user。把这部分SQL提取成SQL片段以重复使用


需求实现
1) 在映射文件UserDao.xml中定义SQL片段
<sql id="selectUser">
select * from user
</sql>
2) 在映射文件UserDao.xml中使用SQL片段
<select id="findByIds" resultType="user" parameterType="queryvo">
<!-- refid属性:要引用的sql片段的id -->
<include refid="selectUser"></include>
<where>
<foreach collection="ids" open="and id in (" item="id" separator="," close=")">
#{id}
</foreach>
</where>
</select>
小结
<select id="xxx" parameterType="QueryVO" resultType="user">
<include refid="yyy"/>
<where>
<!-- 循环集合ids -->
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" item="id" separator="," open="and id in(" close=")">
#{id}
</foreach>
</if>
<!-- user里的username和sex -->
<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>
</where>
</select>
<sql id="yyy">select * from user</sql>
二、Mybatis的缓存

我们以 根据主键查询User为例,演示缓存的效果


注意:User类不要重写toString()方法,我们需要打印User对象的地址


public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
//get/set方法......
}
一级缓存
什么是一级缓存

什么是一级缓存?


一级缓存,也叫本地缓存(localCache),是SqlSession对象提供的缓存,默认是开启的执行一次查询之后,查询的结果会被缓存到SqlSession中。再次查询同样的数据,Mybatis会优先从缓存中查找;如果找到了,就不再查询数据库。

什么情况下一级缓存会失效?


当sqlSession执行了修改、添加、删除时当sqlSession提交了事务commit()或回滚rollback()时当手动清理缓存sqlSession.clearCache()时当SqlSession对象关闭sqlSession.close()时

拓展:一级缓存能否关闭?可以,但通常不需要


在全局配置文件中添加设置项 localCacheScope的值为 STATEMENT即可
一级缓存效果演示
/**
* Mybatis缓存效果演示
*/
public class MybatisCacheTest {
private InputStream is;
private SqlSession session;
private UserDao dao;
/**
* 测试 Mybatis的一级缓存:
* SQL语句执行了一次、输出user1和user2的地址是相同的
* 说明Mybatis使用了缓存
*/
@Test
public void testLevel1Cache(){
User user1 = dao.findUserById(41);
System.out.println(user1);
User user2 = dao.findUserById(41);
System.out.println(user2);
}
/**
* 测试 清除Mybatis的一级缓存
* 两次打印的User地址不同,执行了两次SQL语句
* SqlSession的修改、添加、删除、commit()、clearCache()、close()都会清除一级缓存
*/
@Test
public void testClearLevel1Cache(){
User user1 = dao.findUserById(41);
session.clearCache();
User user2 = dao.findUserById(41);
System.out.println(user1 == user2);//false
}
@Before
public void init() throws IOException {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
session = factory.openSession();
dao = session.getMapper(UserDao.class);
}
@After
public void destory() throws IOException {
session.close();
is.close();
}
}
一级缓存的源码分析
SqlSession接口:面向用户的接口,提供了让用户调用的方法:selectList, selectOne, insert, update, delete…。 实现类 DefaultSqlSessionExecutor接口:每次操作数据库,本质都是由这个Executor来执行的。每个SqlSession里都有自己的一个Executor对象Cache接口:缓存接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k22nVOVr-1610904937778)(img/image-20200813203742345.png)]


二级缓存
什么是二级缓存

什么是二级缓存?


是映射器级别的缓存,同一映射器Mapper共享缓存。

当SqlSession查询了一条数据后,会把数据存储到SqlSession的一级缓存中

当这个SqlSession关闭或提交时,Mybatis可以把一级缓存中的数据,转存到二级缓存中

当有新的SqlSession对象要获取 数据时,优先从二级缓存中查找


什么情况下二级缓存会失效?


默认情况下,执行增删改之后,一级缓存和二级缓存都会清空默认情况下,查询数据,不会清除缓存

注意事项:


如果要使用二级缓存,要求JavaBean实现序列化接口Serializable二级缓存需要手动开启
二级缓存效果演示
1) 修改全局配置文件,开启全局的二级缓存开关
<settings>
<!-- 增加此配置项,启动二级缓存(默认值就是true,但是仍然建议配置上) -->
<setting name="cacheEnabled" value="true"/>
</settings>
2) 修改映射文件UserDao.xml,让映射器支持二级缓存
<mapper namespace="com.itheima.dao.UserDao">
<!-- 把cache标签加到映射文件 mapper标签里 -->
<cache/>
......
</mapper>
3) 修改映射文件UserDao中的findById,让此方法(statement)支持二级缓存
<!-- 如果statement的标签上,设置有的useCache="true",表示此方法要使用二级缓存 -->
<select id="findById" parameterType="int" resultType="user" useCache="true">
select * from user where id = #{id}
</select>
4) 修改JavaBean:User

注意:如果要使用二级缓存,那么==JavaBean需要实现Serializable接口==


public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
//get/set......
}
5) 编写测试代码
/**
* Mybatis二级缓存效果演示
*/
public class MybatisLevel2CacheTest {
private InputStream is;
private SqlSessionFactory factory;
/**
* 测试二级缓存。
* 测试结果:
* 虽然输出的user1和user2地址不同,但是SQL语句只执行了一次,说明第二次用了缓存。
* Mybatis的二级缓存,保存的不是JavaBean对象,而是散列的数据。
* 当要获取缓存时,把这些数据重新组装成一个JavaBean对象,所以地址不同
*/
@Test
public void testLevel2Cache(){
SqlSession session1 = factory.openSession();
UserDao dao1 = session1.getMapper(UserDao.class);
User user1 = dao1.findUserById(41);
session1.close();
SqlSession session2 = factory.openSession();
UserDao dao2 = session2.getMapper(UserDao.class);
User user2 = dao2.findUserById(41);
session2.close();
System.out.println(user1==user2);
}
@Before
public void init() throws IOException {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(is);
}
@After
public void destory() throws IOException {
is.close();
}
}
二级缓存原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iRfRlM73-1610904937787)(img/image-20200813210828010.png)]


三、多表关联查询【重点】

多表关系:


一对一:一对一通常会合并成一张表,但是:
如果一张表太大,把常用字段放在一张表里,不常用字段放在另外一张表 一对多:用户和订单, 部门和员工
使用外键维护数据的完整性和一致性 多对多:学生和课程,订单和商品,老师和学生
通过中间关系表,来维护多对多的关系

多表查询语法:


内连接:查询表之间必定有关联的数据,无关数据是会被剔除的 # 显式内连接
select * from table1 inner join table2 on 表关联条件
# 隐式内连接
select * from table1, table2 where 表关联条件
外连接:查询一张表的全部数据,及另一张表的关联的数据 # 左外连接:查左表的全部数据,及右表的关联数据
select * from 左表 left join 右表 on 表关联条件
# 右外连接:查右表的全部数据,及左表的关联数据
select * from 左表 right join 右表 on 表关联条件
子查询:查询嵌套的技巧 # 子查询是一个值
# 查询帐号1所属的用户
select * from user where id = (select uid from account where id = 1)
# 子查询是一个集合
# 查询帐号余额大于1000的用户
select * from user where id in(select uid from account where money > 1000)
# 子查询是一张虚拟表。拿虚拟表和其它表关联查询
# 查询帐号余额大于1000的用户和帐号信息
select * from user u right join (select * from account where money > 1000) t on u.id = t.uid
select * from user u right join account a on u.id = a.uid where a.money > 1000
准备工作

创建java项目,导入依赖

分别创建好user表和account表的实体类:User和Account

在dao中创建映射器接口AccountDao和UserDao

创建映射器的配置文件AccountDao.xml和UserDao.xml

创建Mybatis核心配置文件,配置好类型别名和映射器

准备日志配置文件log4j.properties

编写好单元测试类


public class MybatisMultipleTest {
private InputStream is;
private SqlSession session;
private AccountDao accountDao;
private UserDao userDao;
@Before
public void init() throws IOException {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
session = factory.openSession();
accountDao = session.getMapper(AccountDao.class);
userDao = session.getMapper(UserDao.class);
}
@After
public void destory() throws IOException {
session.close();
is.close();
}
}
一对一(多对一)关联查询
查询所有帐户表信息,及其关联的用户信息
方案一:类继承方式(不推荐)

创建新的JavaBean用于封装查询结果,定义与所有字段对应的属性,可以使用继承的方式来减少代码量。


例如:


创建UserAccount类,定义user表对应的属性,然后继承Account类

创建UserAccount类,定义account表对应的属性,然后继承User类


1) 创建JavaBean:UserAccount
public class UserAccount extends Account {
private String username;
private String address;
//get/set...
//toString...
}
2) 在映射器AccountDao中增加方法
//查询所有帐号,及其关联的用户信息--类继承的方式
List<UserAccount> queryAllAccounts1();
3) 在映射文件AccountDao.xml中增加配置
注意:SQL语句查询结果集中,不能有重名列。如果有,给列起别名保证没有重名列
<select id="queryAllAccounts1" resultType="userAccount">
select a.*, u.username, u.address from account a left join user u on a.uid = u.id
</select>
4) 编写测试代码
@Test
public void testQueryAllAccounts(){
List<UserAccount> userAccounts = accountDao.queryAllAccounts1();
for (UserAccount userAccount : userAccounts) {
System.out.println(userAccount);
}
}
方案二:类引用方式(推荐)

JavaBean中要有 关联JavaBean的引用。例如:


在Account中增加一个属性user,指向User对象。
把查询结果集中,帐号信息封装到Account中把查询结果集中,用户信息封装到Account的User中
1) 修改JavaBean:Account类
注意:Account中要有User的引用
public class Account {
private Integer id;
private Integer uid;
private Double money;
private User user;
//get/set...
//toString...
}
2) 在映射器AccountDao中增加方法
//查询所有帐号,及其关联的用户信息-类引用方式
List<Account> queryAllAccounts2();
3) 在映射文件AccountDao.xml中增加配置
注意:SQL语句查询结果集中,不能有重名列。如果有,给列起别名保证没有重名列
<select id="queryAllAccounts2" resultMap="AccountUserMap">
SELECT a.id aid, a.uid uid, a.money money, u.* FROM account a LEFT JOIN USER u ON a.uid = u.id
</select>
<resultMap id="AccountUserMap" type="account">
<id property="id" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!--
association:用于把结果集中某些列的数据,封装到JavaBean中关联的一个对象上。用于一对一情形
property:把数据封装到哪个属性关联的对象上
javaType:关联的对象是什么类型的。是com.itheima.domain.User,这里使用了别名
-->
<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>
4) 编写测试代码
@Test
public void testQueryAllAccounts2(){
List<Account> accounts = accountDao.queryAllAccounts2();
for (Account account : accounts) {
System.out.println(account);
}
}
方案三:查询嵌套方式(推荐)

在这里插入图片描述


我们可以把多表关联查询,拆分成两步:


先查询帐号的信息,封装到Account里再查询每个帐号关联的用户信息,封装到Account内的User对象里(我们配置好,Mybatis自动调用)
1) 修改JavaBean:Account类
Account类里要有一个成员变量:User(同方案二的JavaBean)
2) 在映射器AccountDao中增加方法
List<Account> queryAllAccounts3();
3) 在映射文件AccountDao.xml中增加配置
<select id="queryAllAccounts3" 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"/>
<!--
根据用户的id查询用户对象
调用方法com.itheima.dao.UserDao.findById,把uid字段值传递过去,得到对应User对象
-->
<association property="user" javaType="User"
select="com.itheima.dao.UserDao.findById"
column="uid"/>
</resultMap>
4) 在映射器UserDao中增加方法
User findById(Integer id);
5) 在映射文件UserDao.xml中增加配置
<select id="findById" resultType="User">
select * from user where id = #{uid}
</select>
6) 编写测试代码
@Test
public void testOne() throws IOException {
List<Account> accounts = accountDao.queryAllAccounts3();
for (Account account : accounts) {
System.out.println(account);
}
}
小结

对一关联查询注意事项:


JavaBean:Account关联一个User,所以Account里应该有User类型的成员变量sql语句:多表查询语句,注意不能有重名列,可以起别名保证所有列名不重复结果集要使用resultMap进行手动映射

使用多表查询语句,手动配置映射关系


<select id="queryAllAccounts" resultMap="accountMap">
select a.id aid, a.uid, a.money, u.* from account a left join user u on u.id = a.uid
</select>
<resultMap id="accountMap" type="Account">
<id property="id" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!--<result property="user.username" column="username"/>-->
<!--
association标签:用于把数据封装到关联的一个JavaBean对象里
property:属性名。哪个属性是关联的JavaBean
javaType:关联的JavaBean是什么类型的,可以写全限定类名或别名
-->
<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>

把多表查询拆分成多步查询


查询所有帐号。把帐号的数据封装到Account对象里Account里需要的那个User:调用另外一个方法,查询当前帐号关联的那个User对象
<select id="queryAllAccounts2" resultMap="accountMap2">
select * from account
</select>
<resultMap id="accountMap2" type="Account">
<id property="id" column="id"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!-- 调用另外一个方法,得到关联的User对象。被调用的方法,必须提供好 -->
<association property="user" javaType="User" select="com.itheima.dao.UserDao.findById" column="uid"/>
</resultMap>
被调用的方法
public interface UserDao {
User findById(Integer id);
}
<select id="findById" resultType="User">
select * from user where id = #{id}
</select>
一对多(多对多)关联查询
查询所有用户(user)信息,以及每个用户拥有的所有帐号(account)信息
类引用方式
JavaBean中要有关联JavaBean的集合。例如:在User中增加一个属性accounts,类型是List<Account>
把查询结果集里,用户的信息封装到User中把查询结果集里,帐号的信息封装到User的accounts中
1) 修改JavaBean:User类
User类中要有List<Account>,用于保存用户拥有的帐号信息集合
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
/**增加属性:account的集合*/
private List<Account> accounts;
//get/set...
//toString...
}
2) 在映射器UserDao中增加方法
List<User> queryAllUsers();
3) 在映射文件UserDao.xml中增加配置
注意:SQL语句查询结果集中,不能有重名列。如果有,给列起别名保证没有重名列
<select id="queryAllUsers" resultMap="userAccountsMap">
SELECT a.id aid, a.uid uid, a.money money, u.* FROM USER u LEFT JOIN account a
ON u.id = a.uid
</select>
<resultMap id="userAccountsMap" 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:用于封装JavaBean中某一属性关联的集合,用于一对多情形
property:封装哪个属性关联的集合
ofType:集合中的数据类型是什么。这里是com.itheima.domain.Account,使用了别名
-->
<collection property="accounts" ofType="account">
<id property="id" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
</collection>
</resultMap>
4) 编写测试代码
@Test
public void testQueryAllUsers(){
List<User> users = userDao.queryAllUsers();
for (User user : users) {
System.out.println(user);
}
}
查询嵌套方式
1) 修改JavaBean:User类
同类引用方式里的JavaBean相同,略
2) 在映射器UserDao中增加方法
List<User> queryAllUser2();
3) 在映射文件UserDao.xml中增加配置
<select id="queryAllUser2" resultMap="userMap">
select * from user
</select>
<resultMap id="userMap" type="User">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<collection property="accounts" ofType="Account"
select="com.itheima.dao.AccountDao.findByUid" column="id"/>
</resultMap>
4) 在映射器AccountDao中增加方法
List<Account> findByUid(Integer uid);
5) 在映射文件AccountDao.xml中增加配置
<select id="findByUid" resultType="Account">
select * from account where uid = #{uid}
</select>
6) 编写测试代码
@Test
public void testMany() throws IOException {
UserDao userDao = session.getMapper(UserDao.class);
List<User> userList = userDao.queryAllUser2();
for (User user : userList) {
System.out.println(user);
}
小结
练习-多对多关联查询

现有用户表(user)和角色表(role),是多对多关系。有中间关系表user_role

练习:


查询所有用户,及关联的角色集合

查询所有角色,及关联的用户集合


准备工作

创建Maven的java项目,配置好坐标,引入Mybatis的依赖(略)

创建用户信息和角色信息的实体类

注意:User中要有Role的集合; Role中要有User的集合

public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
private List<Role> roles;
//get/set方法......
//toString方法......
}
public class Role {
private Integer id;
private String roleName;
private String roleDesc;
private List<User> users;
//get/set方法......
//toString方法......
}

创建映射器接口UserDao和RoleDao(准备好备用,暂时不需要加方法)

创建映射文件UserDao.xml和RoleDao.xml

创建Mybatis的核心配置文件,配置好别名和映射器

准备单元测试类(准备好备用)

public class MybatisMany2ManyTest {
private InputStream is;
private SqlSession session;
private UserDao userDao;
private RoleDao roleDao;
@Before
public void init() throws IOException {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
session = factory.openSession();
userDao = session.getMapper(UserDao.class);
roleDao = session.getMapper(RoleDao.class);
}
@After
public void destory() throws IOException {
session.close();
is.close();
}
}
练习1:查询所有用户,及关联的角色集合
1) 在映射器接口UserDao中增加方法
//查询所有用户信息,及其关联的角色集合
List<User> queryAllUsers();
2) 在映射文件UserDao.xml中增加statement
<select id="queryAllUsers" resultMap="userRolesMap">
SELECT u.*, r.id rid, r.role_name roleName, r.role_desc roleDesc
FROM USER u LEFT JOIN user_role ur ON u.id = ur.uid
LEFT JOIN role r ON ur.rid = r.id
</select>
<resultMap id="userRolesMap" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<!--
collection:用于封装JavaBean里关联的角色集合
-->
<collection property="roles" ofType="role">
<id property="id" column="rid"/>
<result property="roleName" column="roleName"/>
<result property="roleDesc" column="roleDesc"/>
</collection>
</resultMap>
3) 在单元测试类中编写测试代码
/**
* 查询所有用户信息,及其关联的角色信息集合
*/
@Test
public void testQueryAllUsers(){
List<User> users = userDao.queryAllUsers();
for (User user : users) {
System.out.println(user);
}
}
练习2:查询所有角色,及关联的用户集合
1) 在映射器接口RoleDao中增加方法
//查询所有角色信息,及其关联的用户集合
List<Role> queryAllRoles();
2) 在映射文件RoleDao.xml中增加statement
<select id="queryAllRoles" resultMap="roleUsersMap">
SELECT r.id rid, r.role_name roleName, r.role_desc roleDesc, u.*
FROM role r LEFT JOIN user_role ur ON r.id = ur.rid
LEFT JOIN USER u ON ur.uid = u.id
</select>
<resultMap id="roleUsersMap" type="role">
<id property="id" column="rid"/>
<result property="roleName" column="roleName"/>
<result property="roleDesc" column="roleDesc"/>
<collection property="users" ofType="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>
</resultMap>
3) 在单元测试类中编写测试代码
/**
* 查询所有角色信息,及其关联的用户信息集合
*/
@Test
public void testQueryAllRoles(){
List<Role> roles = roleDao.queryAllRoles();
for (Role role : roles) {
System.out.println(role);
}
}
小结
只要是多表关联,就需要修改JavaBean
如果是关联一个,Account里要有一个User如果是关联多个,User里要有一个List<Account> sql语句:查询结果集一定不能有重名列,通过起别名保证所有列不重复在映射文件里,使用resultMap手动映射
<select id="" resultMap="yyy">
xxxsqlxxxx
</select>
<resultMap id="yyy" type="">
<!-- 如果关联一个 -->
<association property="" javaType="">
</association>
<association property="" javaType="" select="" column/>
<!-- 如果关联多个 -->
<collection property="" ofType="">
</collection>
<collection property="" ofType="" select="" column/>
</resultMap>
四、Mybatis的延迟加载【重点】

在多表关联查询时,比如查询用户信息,及其关联的帐号信息,在查询用户时就直接把帐号信息也一并查询出来了。但是在实际开发中,并不是每次都需要立即使用帐号信息,这时候,就可以使用延迟加载策略了 。


什么是延迟加载
立即加载
不管数据是否需要使用,只要调用了方法,就立即发起查询。比如:查询帐号,得到关联的用户;查询用户,得到关联的帐号
延迟加载
延迟加载,也叫按需加载,或者叫==懒加载==。
只有当真正使用到数据的时候,才发起查询。不使用不发起查询比如:查询用户信息,不使用accounts的时候,不查询帐号的数据;只有当使用了用户的accounts,Mybatis再发起查询帐号的信息
好处:先从单表查询,需要使用关联数据时,才进行关联数据的查询。
单表查询语句简单,查询速度比多表关联查询快内存占用小 坏处:当需要使用数据时才会执行SQL。这样大批量的SQL执行的情况下,会造成查询等待时间比较长
延迟加载的使用场景
一对一(多对一),通常不使用延迟加载(建议)。比如:查询帐号,关联加载用户信息一对多(多对多),通常使用延迟加载(建议)。比如:查询用户,关联加载帐号信息
延迟加载的实现

无论是对一,还是对多,如果想要实现延迟加载,只要==在查询嵌套的基础==上,再多一步:


在全局配置文件中,开启懒加载
对一的延迟加载(association)

查询帐号信息,及其关联的用户信息。使用懒加载的方式实现。


步骤:


使用查询嵌套的方式,查询帐号及关联的用户信息

在核心配置文件中,开启懒加载


1. 用查询嵌套方式,查询帐号及关联的用户
1) 在映射器AccountDao中增加方法
List<Account> queryAllAccounts();
2) 在映射文件AccountDao.xml中增加配置
<select id="queryAllAccounts" resultMap="accountLazyUser">
select * from account
</select>
<resultMap id="accountLazyUser" type="account">
<id property="id" column="id"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!--
association标签:用于封装关联的JavaBean对象 (查询账号关联的用户信息)
select:调用哪个statement,懒加载 得到关联的JavaBean对象
column:调用statement时,需要传递的参数值,从哪个字段中取出
-->
<association property="user" javaType="user"
column="uid" select="com.itheima.dao.UserDao.findById"/>
</resultMap>
3) 在映射器UserDao中增加方法
User findById(Integer id);
4) 在映射文件UserDao.xml中增加配置
<select id="findById" parameterType="int" resultType="user">
select * from user where id = #{id}
</select>
2. 在核心配置文件中开启懒加载(启动延迟加载,禁用积极加载)
<!-- 把settings标签放到typeAliases之前 -->
<settings>
<!-- 启动延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 禁用积极加载:使用按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
3. 编写测试代码
/**
* 测试一对一实现懒加载:查询帐号,及其关联的一个用户。
*/
@Test
public void testQueryAllAccounts(){
List<Account> accounts = accountDao.queryAllAccounts();
for (Account account : accounts) {
System.out.println(account.getId()+", " + account.getUid() + ", " +account.getMoney());
//执行下面这行代码,才会发起查询user的SQL语句
System.out.println(account.getUser());
}
}
对多的延迟加载(collection)

查询用户信息,及其关联的帐号信息集合。使用延迟加载实现。


实现步骤:


使用查询嵌套的方式,查询用户信息collection,及关联的帐号集合

在核心配置文件中,开启懒加载


1. 用查询嵌套方式,查询用户及关联的帐号
1) 在映射器UserDao中增加方法
List<User> queryAllUsers();
2) 在映射文件UserDao.xml中增加statement
<select id="queryAllUsers" resultMap="userLazyAccounts">
select * from user
</select>
<resultMap id="userLazyAccounts" 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"
column="id" select="com.itheima.dao.AccountDao.findByUid"/>
</resultMap>
3) 在映射器AccountDao中增加方法
List<Account> findByUid(Integer uid);
4) 在映射文件AccountDao.xml中增加statement
<select id="findByUid" parameterType="int" resultType="account">
select * from account where uid = #{id}
</select>
2. 在核心配置文件中开启懒加载
<!-- 把settings标签放到typeAliases之前 -->
<settings>
<!-- 启动延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 禁用积极加载:使用按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
3. 编写测试代码
/**
* 测试一对多实现懒加载:查询用户,及其关联的帐号集合
*/
@Test
public void testQueryAllUsers(){
List<User> users = userDao.queryAllUsers();
for (User user : users) {
System.out.println(user.getUsername()+", " + user.getSex());
//执行页面这行代码,才会发起查询account的SQL语句
System.out.println(user.getAccounts());
}
}
小结
在查询嵌套方式的基础上,开启懒加载的全局开关
<settings>
<!--开启懒加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--关闭积极加载-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
五、Mybatis的注解开发

Mybatis也支持注解开发。但是需要明确的是,Mybatis仅仅是把映射文件 使用注解代替了;而Mybatis的核心配置文件,仍然是xml配置(加载映射器的xml配置文件改成自动注册指定包下的映射器)。


1 准备环境

创建Java项目,导入jar包

创建JavaBean:User和Account

public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
private List<Account> accounts;
//get/set...
//toString...
}
public class Account {
private Integer id;
private Integer uid;
private Double money;
private User user;
//get/set...
//toString...
}

创建映射器接口UserDao和AccountDao,备用

准备Mybatis的核心配置文件,配置好别名和映射器

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.itheima.domain"/>
</typeAliases>
<environments default="mysql_mybatis">
<environment id="mysql_mybatis">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<--自动注册指定包下所有的映射器接口-->
<mappers>
<package name="com.itheima.dao"/>
</mappers>
</configuration>

准备好单元测试类备用

/**
* Mybatis的注解开发功能测试--简单的CURD操作
*/
public class MybatisAnnotationTest {
private InputStream is;
private SqlSession session;
private UserDao userDao;
private AccountDao accountDao;
@Before
public void init() throws IOException {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
session = factory.openSession();
userDao = session.getMapper(UserDao.class);
accountDao = session.getMapper(AccountDao.class);
}
@After
public void destory() throws IOException {
session.close();
is.close();
}
}
2. 常用注解介绍
@Select:相当于映射文件里的select标签:用于配置查询方法的语句@Insert:相当于映射文件里的insert标签@SelectKey:相当于映射文件里的selectKey标签,用于添加数据后获取最新的主键值@Update:相当于映射文件里的update标签@Delete:相当于映射文件里的delete标签@Results:相当于映射文件里的resultMap标签@Result:相当于映射文件里的result标签,和@Results配合使用,封装结果集的@One:相当于映射文件里的association,用于封装关联的一个JavaBean对象@Many:相当于映射文件里的collection标签,用于封装关联的一个JavaBean对象集合
3. 简单CURD操作【掌握】
查询全部用户

在映射器接口UserDao中增加方法

@Select("select * from user")
List<User> queryAll();

在测试类MybatisAnnotationTest中编写测试代码

@Test
public void testQueryAll(){
List<User> users = dao.queryAll();
for (User user : users) {
System.out.println(user);
}
}
根据主键查询一个用户

在映射器接口UserDao中增加方法

@Select("select * from user where id = #{id}")
User findById(Integer id);

在测试类MybatisAnnotationTest中编写测试代码

@Test
public void testFindById(){
User user = dao.findById(41);
System.out.println(user);
}
添加用户

在映射器接口UserDao中增加方法

@Insert("insert into user (id,username,birthday,sex,address) values (#{id},#{username},#{birthday},#{sex},#{address})")
@SelectKey(
statement = "select last_insert_id()", //查询最新主键值的SQL语句
resultType = Integer.class, //得到最新主键值的类型
keyProperty = "id", //得到最新主键值,保存到哪个属性里
before = false //是否在insert操作之前查询最新主键值
)
void save(User user);

在测试类MybatisAnnotationTest中编写测试代码

@Test
public void testSave(){
User user = new User();
user.setUsername("小红");
user.setSex("女");
user.setAddress("中粮商务公园");
user.setBirthday(new Date());
System.out.println("保存之前:" + user);
dao.save(user);
session.commit();
System.out.println("保存之后:" + user);
}
修改用户

在映射器接口UserDao中增加方法

@Update("update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}")
void edit(User user);

在测试类MybatisAnnotationTest中编写测试代码

@Test
public void testEdit(){
User user = dao.findById(57);
user.setAddress("广州");
dao.edit(user);
session.commit();
}
删除用户

在映射器接口UserDao中增加方法

@Delete("delete from user where id = #{id}")
void delete(Integer id);

在测试类MybatisAnnotationTest中编写测试代码

@Test
public void testDelete(){
dao.delete(57);
session.commit();
}
JavaBean属性名和字段名不一致的情况处理

创建JavaBean: User2

public class User2 {
private Integer userId;
private String username;
private Date userBirthday;
private String userSex;
private String userAddress;
//get/set...
//toString...
}

在映射器接口UserDao中增加方法

@Select("select * from user")
@Results({
@Result(property = "userId", column = "id", id = true),
@Result(property = "username", column = "username"),
@Result(property = "userBirthday", column = "birthday"),
@Result(property = "userSex", column = "sex"),
@Result(property = "userAddress", column = "address")
})
List<User2> queryAllUser2();

在测试类MybatisAnnotationTest中编写测试代码

@Test
public void testQueryAllUser2(){
List<User2> user2List = dao.queryAllUser2();
for (User2 user2 : user2List) {
System.out.println(user2);
}
}
4. 多表关联查询
一对一(多对一)关联查询,实现懒加载(fetchType = FetchType.LAZY)
需求描述
需求:查询帐号信息,及其关联的用户信息
需求实现

修改映射器接口UserDao,增加方法findById(供关联查询时使用)

@Select("select * from user where id = #{id}")
User findById(Integer id);

创建JavaBean:Account

注意:Account中要有User的引用,前边已经准备好

修改映射器接口AccountDao,增加方法

@Select("select * from account where id = #{id}")
@Results({
@Result(property = "id", column = "id", id = true),
@Result(property = "uid", column = "uid"),
@Result(property = "money", column = "money"),
@Result(
property = "user",
javaType = User.class,
column = "uid",
one = @One(
//一对一关联查询,调用select配置的statement,得到关联的User对象
select = "com.itheima.dao.UserDao.findById",
//FetchType.LAZY 表示要使用延迟加载
fetchType = FetchType.LAZY
)
)
})
Account findById(Integer id);

编写测试代码

@Test
public void testOne2One(){
Account account = accountDao.findById(1);
System.out.println(account.getId() + ", "+ account.getMoney());
//如果不执行下面这行代码,Mybatis不会发起查询用户的SQL
System.out.println(account.getUser());
}
一对多(多对多)关联查询,实现懒加载
需求描述
需求:查询用户信息,及其关联的帐号集合信息
需求实现

修改映射器AccountDao,增加方法findAccountsByUid(供关联查询时使用)

@Select("select * from account where uid = #{uid}")
List<Account> findAccountsByUid(Integer uid);

修改JavaBean:User

注意:User中需要有Account的集合,前边已经准备好

修改映射器接口UserDao,增加方法

/**
* 查询用户信息,及其关联的帐号信息集合
* @param id
* @return
*/
@Select("select * from user where id = #{id}")
@Results({
@Result(property = "id",column = "id",id = true),
@Result(property = "username",column = "username"),
@Result(property = "birthday",column = "birthday"),
@Result(property = "sex",column = "sex"),
@Result(property = "address",column = "address"),
@Result(
property = "accounts",
javaType = List.class, //注意,这里是List.class,而不是Account.class
column = "id",
many = @Many(
//一对多关联查询,调用select对应的statement,得到帐号集合
select = "com.itheima.dao.AccountDao.findAccountsByUid",
//FetchType.LAZY 表示要使用延迟加载
fetchType = FetchType.LAZY
)
)
})
User findUserAccountsById(Integer id);

编写测试代码

@Test
public void testOne2Many(){
User user = userDao.findUserAccountsById(41);
System.out.println(user.getUsername()+", " + user.getAddress());
//如果不执行下面这行代码,Mybatis不会发起查询帐号的SQL语句
System.out.println(user.getAccounts());
}
上午内容复习
动态sql拼接
<select>
<include refif="selectUser"/>
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" item="id" separator="," open="and id in(" 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>
</where>
</select>
<sql id="selectUser"></sql>

缓存


一级缓存:本地缓存,是SqlSession对象的缓存
使用SqlSession对象第一次查询一个数据,这个数据会被缓存起来使用相同SqlSession,再次查询相同的数据,会优先从缓存里查找当执行增删改,提交/回滚,手动清除缓存,关闭SqlSession时,会清除缓存 二级缓存:全局缓存,是Mapper级别的缓存,即:相同的Mapper之间可以进行数据共享
清除:当执行增删改时数据会清除

多表查询


对一查询


JavaBean要求:一个Account关联一个User,所以Account里要有一个User对象sql语句要求:查询结果集一定不能有重名列。可以给列起别名保证列名不重复在映射文件里,要使用resultMap手动设置映射 <select id="xxx" resultMap="yyy">
多表关联查询sql
</select>
<resultMap id="yyy" type="映射的JavaBean的全限定类名或别名">
<association property="关联JavaBean的那个属性名" javaType="关联的JavaBean的类型">
<id property="" column=""/>
<result property="" column=""/>
</association>
</resultMap>
查询嵌套的方式:查询所有的帐号,及关联的用户 <select id="xxx" resultMap="yyy">
select * from account
</select>
<resultMap>
<id property="id" column="id"/>
<result property="uid" column="uid"/>
....
<!-- 配置 让Mybatis调用select指定方法,并传参uid的值,查询得到User对象 -->
<association property="user" javaType="User"
select="com.itheima.dao.UserDao.findById" column="uid"/>
</resultMap>

对多查询


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_41942838/article/details/112760940

随机推荐

pythonista 文档_Pythonista中文文档:contacts

contacts—访问iOS上的联系人数据库contacts模块允许你读取和修改iOS联系人(地址簿)数据库。注意:首次contacts导入模块时,将显示系统提供的权限对话框。如果你拒绝...

weixin_39648297 阅读(417)

python fmod函数_Python内置函数详解——总结篇

2个多月来,将3.5版本中的68个内置函数,按顺序逐个进行了自认为详细的解析,现在是时候进行个总结了。为了方便记忆,将这些内置函数进行了如下分类...

weixin_39984982 阅读(484)

python0x452_Python基础之第二篇详解

一、作用域对作用域来说,只要变量在内存里面存在就可以使用:1if1==1:2name='saneri'3printname二、三元运算result...

weixin_39758229 阅读(468)