-
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语句 select last_insert_id() insert语句update语句 delete语句 -
参数说明
-
单参数
- 如果参数是简单类型,SQL语句里取参数值:#{随便}
- 如果参数是JavaBean,SQL语句里取参数值:#{JavaBean的属性名}
- 如果参数是复杂JavaBean,SQL语句里取参数值:#{JavaBean属性名.属性名}
-
多参数
- SQL取值方案一:#{arg0}, #{arg1}, ....
- SQL取值方案二:#{param1}, #{param2}, ...
- 给参数命名,根据名称取参数值
List
search(@Param("username")String username, @Param("sex")String sex);
-
-
结果集封装
- resultType:自动映射。要求JavaBean属性名 和 表字段名/字段别名 相同
- resultMap:手动映射。手动配置一下 JavaBean属性名 和 表字段名的对应关系
-
全局配置文件
....
- 能够使用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的拼接。
常用的标签有:
:用来进行判断,相当于Java里的if判断 :通常和if配合,用来代替SQL语句中的where 1=1 :用来遍历一个集合,把集合里的内容拼接到SQL语句中。例如拼接:in (value1, value2, ...) :用于定义sql片段,达到重复使用的目的
- 创建java项目,导入jar包;准备JavaBean
- 创建映射器接口UserDao
- 创建映射配置文件UserDao.xml
- 创建全局配置文件SqlMapConfig.xml
- 创建日志配置文件log4j.properties
使用示例SQL语句内容, 如果判断为true,这里的SQL语句就会进行拼接
- 根据用户的名称和性别搜索用户信息。把搜索条件放到User对象里,传递给SQL语句
- 映射器接口UserDao上加方法
package com.is.fwis.dao;
import com.is.fwis.domain.User;
import java.util.List;
public interface UserDao {
List search1(User user);
}
- 映射文件UserDao.xml里配置statement
select * from user where 1=1 and username like "%"#{username}"%" and sex = #{sex}
- 功能测试,在测试类里加测试方法
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;
public class SqlTest {
private UserDao userDao;
private SqlSession session;
private InputStream is;
@Test
public void testSearch(){
User user = new User();
// user.setUsername("王");
// user.setSex("男");
List 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. 在刚刚的练习的SQL语句中,我们写了where 1=1。如果不写的话,SQL语句会出现语法错误。Mybatis提供了一种代替where 1=1的技术:
把上一章节的实现代码进行优化,使用
- 映射器UserDao的search1方法:已有,不用修改
Listsearch1(User user);
- 在映射文件UserDao.xml里修改SQL语句
- 在测试类里进行功能测试:测试方法不需要修改
@Test
public void testSearch(){
User user = new User();
// user.setUsername("王");
// user.setSex("男");
List userList = userDao.search1(user);
userList.forEach(System.out::println);
}
4. 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标签实现。
使用示例#{id}
- 有搜索条件类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里加方法
Listsearch2(QueryVO vo);
- 在映射文件UserDao.xml里配置statement
select * from user where
#{id}
- 功能测试
@Test
public void testSearch2(){
QueryVO vo = new QueryVO();
vo.setIds(new Integer[]{41,42,43,44,45});
List userList = userDao.search2(vo);
userList.forEach(System.out::println);
}
5. 在映射文件中,我们发现有很多SQL片段是重复的,比如:select * from user。Mybatis提供了一个
在映射文件中定义SQL片段:
sql语句片段
在映射文件中引用SQL片段:
使用示例
在查询用户的SQL中,需要重复编写:select * from user。把这部分SQL提取成SQL片段以重复使用
- 要求:QueryVO里有ids,user对象。根据条件进行搜索
- 修改QueryVO,增加成员变量user
package com.is.fwis.domain;
public class QueryVO {
private Integer[] ids;
private User user;
//get/set方法……
}
- 在映射器UserDao里加方法
List search3(QueryVO vo);
- 在映射文件UserDao.xml里配置statement
#{id} select * from user and username like "%"#{user.username}"%" and sex = #{user.sex}
- 在测试类里加测试方法
@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 userList = userDao.search3(vo);
userList.forEach(System.out::println);
}
小结
拓展:select * from user #{id} and username like "%"#{username}"%" and sex = #{sex}
set标签用于代替update语句 的set关键字,实现动态拼接update语句,它可以用来动态包含需要更新的列,帮我们去掉多余的逗号。
使用示例- 在映射器接口UserDao里添加方法
void edit(User user);
- 在映射文件UserDao.xml里配置SQL语句
update user where id = #{id} username = #{username}, sex = #{sex}, birthday = #{birthday},
- 功能测试
@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;
public class CacheTest {
private UserDao userDao;
private SqlSession session;
private InputStream is;
private SqlSessionFactory factory;
@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二级缓存的效果
- 二级缓存是Mapper级别的缓存(映射器级别的缓存)
- 同样的映射器Mapper共享二级缓存。UserDao
- 注意:
- 二级缓存,缓存的是序列化之后的数据;
- 当从缓存里取数据时,要进行反序列化还原成JavaBean对象
- 要求JavaBean必须实现序列化接口Serializable
- 二级缓存需要手动开启
- 修改全局配置文件,开启二级缓存
-
哪个映射器要使用二级缓存,就在哪个映射文件里开启二级缓存支持
-
哪个方法要使用二级缓存,就在方法的配置statement上加useCache="true"
@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关联的用户详情
- 创建项目,导入jar包,创建JavaBean
- 创建映射器接口UserDao和AccountDao
- 创建映射文件UserDao.xml 和AccountDao.xml
- 创建Mybatis的全局配置文件。 注意,要配置好别名和映射器
- 准备好log4j日志配置文件
- 编写单元测试类备用
- 查询所有帐号,及关联的用户。一个帐号Account只关联一个用户User
- 修改实体类Account
- 在映射器里加方法
- 在映射文件配置statement:
- 编写SQL语句,只查询所有的帐号select * from account
- 使用resultMap封装结果集,使用association封装关联的那个User
public class Account {
private Integer id;
private Integer uid;
private Double money;
private User user;
//get/set...
}
2. 在AccountDao里增加方法
List queryAllAccount();
3. 在AccountDao.xml里配置statement
4. 编写“查询一个用户”的方法 1. 在UserDao里增加方法select * from account
User findById(Integer uid);
2. 在UserDao.xml配置statement
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;
public class NestedQueryTest {
private InputStream is;
private SqlSession session;
private UserDao userDao;
private AccountDao accountDao;
@Test
public void testQueryAllAccount(){
List 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;
private List accounts;
//get/set...
}
- 在UserDao里增加方法,查询所有用户。执行的语句select * from user
- 在UserDao.xml里配置statement:
- 要使用resultMap封装结果集
- 当封装User里accounts时,使用
List queryAllUser();
2. 在UserDao.xml里配置statement
3. 准备根据用户id查询帐号集合的功能
3.1 在AccountDao里增加方法
List findAccountsByUid(Integer uid);
3.2 在AccountDao.xml里配置statement
4. 测试
@Test
public void testQueryAllUser(){
List userList = userDao.queryAllUser();
userList.forEach(System.out::println);
}
四、延迟加载
在多表关联查询时,比如查询用户信息,及其关联的帐号信息,在查询用户时就直接把帐号信息也一并查询出来了。但是在实际开发中,并不是每次都需要立即使用帐号信息,这时候,就可以使用延迟加载策略了。
需要在查询嵌套方式的基础上,才能开启懒加载。
1. 什么是延迟加载 立即加载- 不管数据是否需要使用,只要调用了方法,就立即发起查询。
- 比如:查询帐号,得到关联的用户;查询用户,得到关联的帐号
-
延迟加载,也叫按需加载,或者叫懒加载。
- 只有当真正使用到数据的时候,才发起查询。不使用不发起查询
- 比如:查询用户信息,不使用accounts的时候,不查询帐号的数据;只有当使用了用户的accounts,Mybatis再发起查询帐号的信息
-
先从单表查询,需要使用关联数据时,才进行关联数据的查询。单表查询的速度要比多表关联查询速度快,性能高;
-
用不上的数据,暂时不查询,内存占用小
- 一对一(多对一),通常不使用延迟加载(建议)。比如:查询帐号,关联加载用户信息
- 一对多(多对多),通常使用延迟加载(建议)。比如:查询用户,关联加载帐号信息
只需要在查询嵌套方式的基础上,开启懒加载即可。 开启懒加载方式:
方式一:在全局配置文件中增加配置,开启全局的懒加载
方式二:局部配置懒加载
- 在association标签上、或collection标签上增加属性fetchType,把当前关联加载设置为懒加载
- 局部配置,会覆盖掉全局配置
注意:
- 在配置了延迟加载策略后,即使没有调用关联对象的任何方法,当你调用当前对象的
equals、clone、hashCode、toString方法时也会触发关联对象的查询。 - 在配置文件中可以使用lazyLoadTriggerMethods配置项覆盖掉mybatis的默认行为。
总结
- 懒加载:延迟加载,按需加载。当用不到关联的数据时,就不查询;只要用到时,才会查询
- 开启懒加载:
- 全局开启
- 局部开启:在association标签或者collection标签上加属性 fetchType="lazy"
搭建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标签
Account中要有User的引用
public class Account {
private Integer id;
private Integer uid;
private Double money;
private User user;
//get/set...
}
2) 在映射器接口AccountDao中增加方法
public interface AccountDao {
List queryAllAccount();
}
3) 在映射文件AccountDao.xml中增加statement
- 使用resultMap手动映射封装结果集
4) 在单元测试类中编写测试代码SELECT a.id aid, a.uid, a.money, u.* FROM account a LEFT JOIN USER u ON a.uid = u.id
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;
public class DuobiaoQueryTest {
private InputStream is;
private SqlSession session;
private UserDao userDao;
private AccountDao accountDao;
@Test
public void testQueryAllAccount(){
List 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 accounts;
-
执行SQL语句,用resultMap封装结果;
当封装到其中关联的集合List时,使用collection标签封装
User类中要有List,用于保存用户拥有的帐号信息集合
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
private List accounts;
//get/set...
}
2) 在映射器接口UserDao中增加方法
package com.is.fwis.dao;
import com.is.fwis.domain.User;
import java.util.List;
public interface UserDao {
List queryAllUser();
}
3) 在配置文件UserDao.xml中增加statement
- 封装结果集时,要使用resultMap手动映射
4) 在单元测试类中编写测试代码SELECT u.*, a.id aid, a.uid, a.money FROM USER u LEFT JOIN account a ON u.id = a.uid
@Test
public void testQueryAllUser(){
List userList = userDao.queryAllUser();
userList.forEach(System.out::println);
}



