一 MyBatis加载策略
1.1 什么是延迟加载?
问题
通过前面的学习,我们已经掌握了Mybatis中一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询,实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的订单信息,此时就是我们所说的延迟加载
举个栗子:
在一对多中,当我们有一个用户,它有100个订单
在查询用户的时候,要不要把关联的订单查出来
在查询订单的时候,要不要把关联的用户查出来
回答
在查询用户时,用户下的订单应该是,什么时候用,什么时候查询
在查询订单时,用户所属的用户信息应该是随着订单一起查询出来
延迟加载
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据,延迟加载也称为懒加载
优点:
先从单表查询,需要时再从关联表去关联查询,大大提高数据性能,因为查询单表要比关联查询多张表速读更快
缺点:
因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也需要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降
在多表中:
一对多,多对多:通常情况下才有延迟加载
一对一(多对一):通常情况下采用立即加载
注意:
延迟加载是基于嵌套查询来实现的
1.2 实现
1.2.1 局部延迟加载
在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策略
<resultMap id="userMap2" type="com.lagou.domain.User">
<id property="id" column="id"></id>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<!--fetchType="lazy":延迟加载策略
fetchType="eager":立即加载策略-->
<collection property="ordersList" ofType="com.lagou.domain.Orders" select="com.lagou.mapper.OrderMapper.findByUid" column="id" fetchType="lazy">
<id property="id" column="oid"></id>
<result property="ordertime" column="ordertime"/>
<result property="total" column="total"/>
<result property="uid" column="uid"/>
</collection>
</resultMap>
<!--一对多嵌套查询-->
<select id="findAllWithOrder2" resultMap="userMap2">
select * from user
</select>
1.2.2 设置触发延迟加载的方法
大家在配置延迟加载策略后,发现即使没有调用关联对象的任何方法,但是在你调用对象的equals、clone、hashCode、toString方法时也会触发关联对象的查询
我们可以在配置文件中使用lazyLoadingTriggerMethods配置项覆盖掉上面的四个方法
<settings>
<!--所有方法都会延迟加载-->
<setting name="lazyLoadTriggerMethods" value="toString()"/>
</settings>
该方法配置在sqlMapConfig.xml的property标签下面
测试类这么写:
/*
* 一对多嵌套查询:查询所有用户及关联的订单信息
* */
@Test
public void test5() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> allWithOrder2 = mapper.findAllWithOrder2();
for (User user:allWithOrder2) {
System.out.println(user);
//要用到该用户的订单信息
System.out.println(user.getOrdersList());
}
sqlSession.close();
}
结果为:
1.2.3 全局延迟加载
在Mybatis的核心配置文件中可以使用setting标签修改全局的加载策略
<settings>
<!--开启全剧延迟加载功能-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--所有方法都会延迟加载-->
<setting name="lazyLoadTriggerMethods" value="toString()"/>
</settings>
注意
局部的加载策略优先级高于全局的加载策略
二 MyBatis缓存
2.1 为什么使用缓存?
当用户频繁查询某些固定的数据时,第一次将这些数据从数据库查询出来,保存在缓存中。当用户再次查询这些数据时,不用再通过数据库查询,而是去缓存里面查询,减少网络连接和数据库查询所带来的损耗,从而提高我们的查询效率,减少高并发访问带来的系统性能问题
一句话概括:经常查询一些不经常发生变化的数据,使用缓存来提高查询效率。
像大多数的持久化框架一样,Mybatis也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。 Mybatis中缓存分为一级缓存,二级缓存。
2.2 一级缓存
2.2.1 介绍
一级缓存是SqlSession级别的缓存,是默认开启的
所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。
/*
* 验证mybatis中的一级缓存
* */
@Test
public void test7() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//根据id查询用户信息
//第一次查询,是查询的数据库
User user1 = userMapper.findById(1);
System.out.println(user1);
//第二次查询,查询的是一级缓存
User user2 = userMapper.findById(1);
System.out.println(user2);
sqlSession.close();
}
4.2.3 分析
一级缓存是SqlSession范围的缓存,执行SqlSession的C(增加)U(更新)D(删除)操作,或者调用clearCache()、commit()、close()方法,都会清空缓存
2.3 二级缓存
2.3.1 介绍
二级缓存是namspace级别(跨sqlSession)的缓存,是默认不开启的
二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置 < cache/ > 就可以开启二级缓存了
2.3.2 验证
(a)配置核心配置文件
(b)配置UserMapper.xml映射
(c)修改User实体
必须为序列化的
(4)测试类
/*
* 验证mybatis中的二级缓存
* */
@Test
public void test8() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
//第一次查询
User user1 = userMapper1.findById(1);
// 只有执行sqlSession.commit或者sqlSession.close,那么一级缓存中内容才会刷新到二级缓存中去
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.findById(2);
sqlSession2.close();
}
事实证明,二级缓存是可以跨sqlSession的
2.3.3 分析
二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的
1 映射语句文件中的所有select语句将会被缓存
2 映射语句文件中的所有insert、update和delete语句会刷新缓存
2.3.4 注意问题(脏读)
mybatis的二级缓存因为是那么namespace级别,所以在进行多表查询时会产生脏读问题
建议不要使用mybatis的二级缓存,二级缓存就是鸡肋
实际开发中,会使用redis来做第三方缓存
2.4 小结
1 mybatis的缓存,都不需要我们手动存储和获取数据,mybatis自动维护的
2 mybatis开启了二级缓存后,那么查询顺序:二级缓存=》一级缓存=》数据库
3 注意:mybatis的二级缓存会存在脏读问题,需要使用第三方的缓存技术解决问题
三 MyBatis注解
3.1 MyBatis常用注解
这几年来注解开发越来越流行,Mybatis也可以使用注解开发方式,这样我们就可以减少编写Mapper映射文件了,我们先围绕一些基本的CRUD来学习,再学习复杂映射多表操作
@insert:实现新增,代替了<insert><insert/>
@delete:实现删除,代替了<delete><delete/>
@Select:实现查询,代替了<select></select>
@Result:实现结果集封装,代替了<result></result>
@Results:可以与@Result 一起使用,封装多个结果集,代替了<resultMap></resultMap>
@One:实现一对一结果集封装,代替了<association></association>
@Many:实现一对多结果集封装,代替了<collection></collection>
3.2 MyBatis注解的增删改查(重点)
UserMapper接口
public interface UserMapper {
/*
* 查询用户
* */
@Select("select * from user")
public List<User> findAll();
/*
* 添加用户
* */
@Insert("insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})")
public void save(User user);
/*
* 更新用户
* */
@Update("update user set username = #{username},birthday = #{birthday} where id = #{id}")
public void update(User user);
/*
* 删除用户
* */
@Delete("delete from user where id = #{id}")
public void delete(Integer id);
}
测试类
public class MybatisTest {
private SqlSessionFactory sqlSessionFactory;
private SqlSession sqlSession;
//在@Test方法标注的方法执行之前来执行
@Before
public void before() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
sqlSession = sqlSessionFactory.openSession();
}
@After
public void after(){
sqlSession.commit();
sqlSession.close();
}
@Test
public void testSelect(){
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.findAll();
for (User user:users) {
System.out.println(user);
}
}
//测试添加方法
@Test
public void testInsert(){
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("汤唯");
user.setSex("女");
user.setBirthday(new Date());
user.setAddress("北京");
mapper.save(user);
}
@Test
public void testUpdate(){
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("噗噗");
user.setBirthday(new Date());
user.setId(6);
mapper.update(user);
}
@Test
public void testDelete(){
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.delete(6);
}
}
3.3 使用注解实现复杂映射开发
之前我们在映射文件中通过配置< resultMap >、< association >、< collection >来实现复杂关系映射
使用注解开发后,我们可以使用@Results、@Result、@One、@Many注解组合完成复杂关系的配置
3.4 一对一查询
3.4.1 介绍
需求:查询一个订单,与此同时查询出该订单所属的用户
一对一查询语句
select * from orders;
select * from user where id = #{id};
3.4.2 代码实现
OrderMapper接口
/*
* 查询所有订单,同时查询订单所属的用户信息
* */
@Select("select * from orders")
@Results({ //代替的就是ResultMap这个标签
@Result(property = "id",column = "id"),
@Result(property = "ordertime",column = "ordertime"),
@Result(property = "total",column = "total"),
@Result(property = "uid",column = "uid"),
@Result(property = "user",javaType = User.class,column = "uid",one = @One(select = "com.lagou.mapper.UserMapper.findById",fetchType = FetchType.EAGER))
})
public List<Orders> findAllWithUser();
UserMapper接口
@Select("select * from user where id = #{id}")
public User findById(Integer id);
测试类
//一对一查询:注解方式
@Test
public void testOneToOne(){
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
List<Orders> allWithUser = mapper.findAllWithUser();
for (Orders order:allWithUser) {
System.out.println(order);
}
}
3.5 一对多查询
3.5.1 介绍
需求:查询一个用户,与此同时查询出该用户具有的订单
一对多查询语句
select * from user
select * from orders where id = #{用户id}
OrderMapper接口
/*根据传递过来的用户id查询该用户所具有的订单信息*/
@Select("select * from orders where uid = #{uid}")
public List<Orders> findOrderByUid(Integer uid);
UserMapper接口
@Select("select * from user")
@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 = "ordersList",javaType = List.class,column = "id",many = @Many(select = "com.lagou.mapper.OrderMapper.findOrderByUid",fetchType = FetchType.LAZY))
})
public List<User> findAllWithOrder();
测试类
//一对多查询:注解方式
@Test
public void testOneToMany(){
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.findAllWithOrder();
for (User user:users) {
System.out.println(user);
System.out.println(user.getOrdersList());
}
}
3.6 多对多查询
3.6.1 介绍
需求:查询所有用户,同时查询出该用户的所有角色
多对多查询语句
select * from user;
select * from role r
UserMapper接口
@Select("select * from user")
@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 = "roleList",javaType = List.class,column = "id",many = @Many(select = "com.lagou.mapper.RoleMapper.findAllByUid"))
})
public List<User> findAllWithRole();
RoleMapper接口
/*
* 根据传递过来的用户id,查询该用户所具有的角色信息
* */
@Select("select * from sys_role r inner join sys_user_role ur on ur.roleid=r.id where ur.userid = #{uid}")
public List<Role> findAllByUid(Integer uid);
测试类
//多对多查询:注解方式
@Test
public void testManyToMany(){
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.findAllWithRole();
for (User user:users) {
System.out.println(user);
System.out.println(user.getRoleList());
}
}