问题
通过前面的学习,我们已经掌握了Mybatis中一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询。实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的订单信息。此时就是我们所说的延迟加载。
举个栗子
延迟加载
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。
在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策略。
<resultMap id="userMap" type="user">
<id column="id" property="id">id>
<result column="username" property="username">result>
<result column="password" property="password">result>
<result column="birthday" property="birthday">result>
<collection property="orderList" ofType="order" column="id" select="com.lagou.dao.OrderMapper.findByUid" fetchType="lazy">
collection>
resultMap>
<select id="findAll" resultMap="userMap">
SELECT * FROM `user`
select>
大家在配置了延迟加载策略后,发现即使没有调用关联对象的任何方法,但是在你调用当前对象的equals、clone、hashCode、toString方法时也会触发关联对象的查询。
我们可以在配置文件中使用lazyLoadTriggerMethods配置项覆盖掉上面四个方法。
<settings>
<setting name="lazyLoadTriggerMethods" value="toString()"/>
settings>
在Mybatis的核心配置文件中可以使用setting标签修改全局的加载策略。
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
settings>
注意
局部的加载策略优先级高于全局的加载策略。
<resultMap id="orderMap" type="order">
<id column="id" property="id">id>
<result column="ordertime" property="ordertime">result>
<result column="total" property="total">result>
<association property="user" column="uid" javaType="user" select="com.lagou.dao.UserMapper.findById" fetchType="eager">
association>
resultMap>
<select id="findAll" resultMap="orderMap">
SELECT * from orders
select>
当用户频繁查询某些固定的数据时,第一次将这些数据从数据库中查询出来,保存在缓存中。当用户再次查询这些数据时,不用再通过数据库查询,而是去缓存里面查询。减少网络连接和数据库查询带来的损耗,从而提高我们的查询效率,减少高并发访问带来的系统性能问题。
一句话概括:经常查询一些不经常发生变化的数据,使用缓存来提高查询效率。
像大多数的持久化框架一样,Mybatis也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。 Mybatis中缓存分为一级缓存,二级缓存。
一级缓存是SqlSession级别的缓存,是默认开启的。
所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。
@Test
public void testOneCache() throws Exception {
SqlSession sqlSession = MyBatisUtils.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = userMapper.findById(1);
System.out.println("第一次查询的用户:" + user1);
User user2 = userMapper.findById(1);
System.out.println("第二次查询的用户:" + user2);
sqlSession.close();
}
我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是Mybatis提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询id为1的记录时,并没有发出sql语句从数据库中查询数据,而是从一级缓存中查询。
一级缓存是SqlSession范围的缓存,执行SqlSession的C(增加)U(更新)D(删除)操作,或者调用clearCache()、commit()、close()方法,都会清空缓存。
@Test
public void testClearOneCache() throws Exception {
SqlSession sqlSession = MybatisUtils.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = userMapper.findById(41);
System.out.println("第一次查询的用户:" + user1);
//调用sqlSession清除缓存的方法
sqlSession.clearCache();
User user2 = userMapper.findById(41);
System.out.println("第二次查询的用户:" + user2);
}
<select flushCache="true">select>
二级缓存是namspace级别(跨sqlSession)的缓存,是默认不开启的。二级缓存的开启需要进行配置,实现二级缓存的时候,Mybatis要求返回的POJO必须是可序列化的。
也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置
a)配置核心配置文件
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
b)配置UserMapper.xml映射
<mapper namespace="com.lagou.dao.UserMapper">
<cache>cache>
<select id="findById" parameterType="int" resultType="user" useCache="true">
SELECT * FROM `user` where id = #{id}
select>
mapper>
c)修改User实体
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
private List<Role> roleList;
private List<Order> orderList;
}
d)测试结果
@Test
public void testTwoCache() throws Exception {
SqlSession sqlSession = MyBatisUtils.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.findById(41);
System.out.println("第一次查询的用户:" + user);
sqlSession.close();
SqlSession sqlSession1 = MyBatisUtils.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.findById(41);
System.out.println("第二次查询的用户:"+user1);
sqlSession1.close();
}
二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
mybatis的二级缓存因为是namespace级别,所以在进行多表查询时会产生脏读问题。
这几年来注解开发越来越流行,Mybatis也可以使用注解开发方式,这样我们就可以减少编写Mapper映射文件了。
* @Insert:实现新增,代替了<insert>insert>
* @Delete:实现删除,代替了<delete>delete>
* @Update:实现更新,代替了<update>update>
* @Select:实现查询,代替了<select>select>
* @Result:实现结果集封装,代替了<result>result>
* @Results:可以与@Result 一起使用,封装多个结果集,代替了<resultMap>resultMap>
* @One:实现一对一结果集封装,代替了<association>association>
* @Many:实现一对多结果集封装,代替了<collection>collection>
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},sex = #{sex},address = #{address} WHERE id = #{id}")
public void update(User user);
@Delete("DELETE FROM `user` where id = #{id}")
public void delete(Integer id);
<mappers>
<mapper class="com.lagou.mapper.UserMapper">mapper>
mappers>
<mappers>
<package name="com.lagou.mapper">package>
mappers>
public class TestUser extends TestBaseMapper {
// 查询
@Test
public void testFindAll() throws Exception {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> list = userMapper.findAll();
for (User user : list) {
System.out.println(user);
}
}
// 添加
@Test
public void testSave() throws Exception {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("于谦");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("北京德云社");
userMapper.save(user);
}
// 更新
@Test
public void testUpdate() throws Exception {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setId(49);
user.setUsername("郭德纲");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("北京德云社");
userMapper.update(user);
}
// 删除
@Test
public void testDelete() throws Exception {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.delete(49);
}
}
之前我们在映射文件中通过配置
使用注解开发后,我们可以使用 @Results、@Result,@One、@Many 注解组合完成复杂关系的配置。
注解 | 说明 |
---|---|
@Results | 代替的是标签 |
@Result | 代替了 @Result中属性介绍: column:数据库的列名 property:需要装配的属性名 one:需要使用的@One注解(@Result(one=@One) () ) many:需要使用的@Many注解(@Result(many=@many) () ) |
@One(一对一) | 代替了 @One注解属性介绍: select:指定用来多表查询的 sqlmapper 使用格式:@Result(column=" “,property=” “,one=@One(select=” ")) |
@Many(一对多) | 代替了 使用格式:@Result(property=" “, column=” “, many=@Many(select=”")) |
需求:查询一个订单,与此同时查询出该订单所属的用户。
一对一查询语句
SELECT * FROM orders;
SELECT * FROM `user` WHERE id = #{订单的uid};
a)OrderMapper接口
public interface OrderMapper {
@Select("SELECT * FROM orders")
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(column = "ordertime", property = "ordertime"),
@Result(column = "money", property = "money"),
@Result(property = "user", javaType = User.class,
column = "uid", one = @One(select = "com.lagou.mapper.UserMapper.findById", fetchType = FetchType.EAGER))
})
public List<Order> findAllWithUser();
}
b)UserMapper接口
public interface UserMapper {
@Select("SELECT * FROM `user` WHERE id = #{id}")
public User findById(Integer id);
}
c)测试代码
@Test
public void testOrderWithUser() throws Exception {
OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
List<Order> list = orderMapper.findAllWithUser();
for (Order order : list) {
System.out.println(order);
}
}
需求:查询一个用户,与此同时查询出该用户具有的订单。
一对多查询语句
SELECT * FROM `user`;
SELECT * FROM orders where uid = #{用户id};
a)UserMapper接口
public interface UserMapper {
@Select("SELECT * FROM `user`")
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(column = "brithday", property = "brithday"),
@Result(column = "sex", property = "sex"),
@Result(column = "address", property = "address"),
@Result(property = "orderList", javaType = List.class, column = "id",
many = @Many(select = "com.lagou.mapper.OrderMapper.findByUid"))
})
public List<User> findAllWithOrder();
}
b)OrderMapper接口
public interface OrderMapper {
@Select("SELECT * FROM orders WHERE uid = #{uid}")
public List<Order> findByUid(Integer uid);
}
c)测试代码
@Test
public void testUserWithOrder() throws Exception {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> list = userMapper.findAllWithOrder();
for (User user : list) {
System.out.println(user);
}
}
需求:查询所有用户,同时查询出该用户的所有角色。
多对多查询语句
SELECT * FROM `user`;
SELECT * FROM role r INNER JOIN user_role ur ON r.`id` = ur.`rid` WHERE ur.`uid` = #{用户id};
a)UserMapper接口
public interface UserMapper {
@Select("SELECT * FROM `user`")
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(column = "brithday", property = "brithday"),
@Result(column = "sex", property = "sex"),
@Result(column = "address", property = "address"),
@Result(property = "roleList", javaType = List.class, column = "id",
many = @Many(select = "com.lagou.mapper.RoleMapper.findByUid"))
})
public List<User> findAllWithRole();
}
b)RoleMapper接口
public interface RoleMapper {
@Select("SELECT * FROM role r INNER JOIN user_role ur ON r.`id` = ur.`rid` WHERE ur.`uid` = #{uid}")
public List<Role> findByUid(Integer uid);
}
c)测试代码
@Test
public void testUserWithRole() throws Exception {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> list = userMapper.findAllWithRole();
for (User user : list) {
System.out.println(user);
}
}
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
@CacheNamespace
public interface UserMapper {...}
不管是一对一还是一对多 ,在注解配置中都有fetchType的属性。
注解开发和xml配置优劣分析: