1. MyBatis加载策略
1.1 延迟加载
概念:实际开发过程中,有的时候只需要加载用户信息,而不用加载其对应的角色身份信息,这时候的加载称之为延迟加载(懒加载)。再通俗一点就是,需要数据的时候在再加载。
1.1.1 局部延迟加载
在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策
略。
例子:对获取用户身份信息的时候,局部延迟加载用户角色信息
1.1.1.1 SqlMapperConfig.xml配置
<settings>
<setting name="lazyLoadTriggerMethods" value="toString()"/>
</settings>
1.1.1.2 Mapper.xml映射配置(局部懒加载生效)
<resultMap id="nestedQueryRoleResultMap" type="com.fuyi.entity.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<!--
fetchType="lazy" 懒加载
fetchType="eager" 立即加载
-->
<collection property="roleList" ofType="com.fuyi.entity.SysRole" column="id" select="com.fuyi.mapper.SysRoleMapper.findByUid" fetchType="lazy"></collection>
</resultMap>
<select id="findAllAndRole" resultMap="nestedQueryRoleResultMap">
select * from user
</select>
1.1.1.3 Mapper.xml映射配置(局部懒加载无效)
<resultMap id="baseResultMap" type="com.fuyi.entity.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<!--
一对多使用collection 标签进行关联
property="orderList" 封装到集合的属性名
ofType="com.fuyi.entity.Order" 封装集合的泛型类型
-->
<collection property="orderList" ofType="com.fuyi.entity.Order" fetchType="lazy">
<id property="id" column="oid"></id>
<result property="ordertime" column="ordertime"></result>
<result property="total" column="total"></result>
</collection>
</resultMap>
<select id="findAllWithOrder" resultMap="baseResultMap">
select *, o.id oid from user u left join orders o on u.id = o.uid;
</select>
1.1.1.4 小结
- 了延迟加载策略后,发现即使没有调用关联对象的任何方法,但是在你调用当前对象的
equals、clone、hashCode、toString方法时也会触发关联对象的查询; - 延迟加载是基于嵌套查询,所以对于多表关联查询中,配置局部懒加载是不起作用的。
1.1.2 全局延迟加载
1.1.2.1 SqlMapConfig.xml全局配置
<settings>
<!-- 全局延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="lazyLoadTriggerMethods" value="toString()"/>
</settings>
1.1.2.2 就近原则
<resultMap id="nestedQueryResultMap" type="com.fuyi.entity.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<collection property="orderList" ofType="com.fuyi.entity.Order" column="id" select="com.fuyi.mapper.OrderMapper.findByUid" fetchType="eager"></collection>
</resultMap>
<select id="findAllAndOrder" resultMap="nestedQueryResultMap">
select * from user
</select>
开启了全局设置后,如果在局部文件中设置立即加载,则会使用就近原则,局部的加载策略先于全局的加载策略
2. Mybatis缓存
背景:当频繁的发生数据查询的时候,此时会频繁的操作数据库。于是有没有什么办法能够减少对数据库的访问,减少并发所带来的系统性能问题,于是采用了一种机制,对于不频繁改动的数据,只在第一次查询的查询数据库,并将查询出来的数据放入缓存,然后下一次请求查询同样数据的时候,直接进行访问缓存。同样的,Mybatis也提供了这样的机制。
2.1 Mybatis一级缓存
SqlSession级别的缓存,默认开启
2.1.1 机制原理
在参数和sql相同的情况下,使用同一个sqlsession对象调用mapper的方法,往往只会执行一次sql语句,在缓存没有超时的情况下,sqlsession会去取当前缓存数据。
2.1.2 配置SQL语句日志
- 添加依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.7</version>
</dependency>
- 日志打印配置
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=d:/mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=debug, stdout
2.1.3 测试
/**
* @author raofy
* @date 2021-05-14 13:56
* @desc
*/
public class CacheTest {
private static Logger logger = LoggerFactory.getLogger(CacheTest.class);
/**
* 一级缓存测试
*
*/
@Test
public void level1CacheTest() throws IOException {
// 1. 加载核心配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 获取SQLFactory工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3. 获取SQLSession对象
SqlSession sqlSession = factory.openSession();
// 4. 获取mapper
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
// 5. 执行查询
List<Order> result = mapper.findAllAndUser();
// 6. 打印结果
logger.info("=====================第一次查询====================");
result.forEach(System.out::println);
logger.info("=====================第二次查询====================");
mapper.findAllAndUser().forEach(System.out::println);
sqlSession.close();
resourceAsStream.close();
}
}
2.1.4 分析
当执行sqlsession的增加、更新和删除,或者是调用cleanCache()、commit()和close()都会删除缓存。
-
清除(cleanCache)
@Test public void level1CacheTest() throws IOException { // 1. 加载核心配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml"); // 2. 获取SQLFactory工厂对象 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream); // 3. 获取SQLSession对象 SqlSession sqlSession = factory.openSession(); // 4. 获取mapper OrderMapper mapper = sqlSession.getMapper(OrderMapper.class); // 5. 执行查询 List<Order> result = mapper.findAllAndUser(); // 6. 打印结果 logger.info("=====================第一次查询===================="); result.forEach(System.out::println); sqlSession.clearCache(); logger.info("=====================第二次查询===================="); mapper.findAllAndUser().forEach(System.out::println); sqlSession.close(); resourceAsStream.close(); }
或者
<!-- flushCache 表示清除缓存 --> <select id="findAllAndUser" resultMap="nestedQueryResultMap" flushCache="true"> select * from orders </select>
2.2 Mybatis二级缓存
namespace级别,默认是不开启,MyBatis要求返回的POJO必须是可序列化的。
也就是返回的实体类要求实现Serializable接口
2.2.1 开启二级缓存
2.2.1.1 配置sqlMapConfig.xml全局配置文件
<!--
因为cacheEnabled的取值默认就为true,所以这一步可以省略不配置。
为true代表开启二级缓存;为false代表不开启二级缓存。
-->
<setting name="cacheEnabled" value="true"/>
2.2.1.2 配置userMapper.xml文件
<!-- 当前映射文件开启二级缓存文件 -->
<cache></cache>
<!--
<select>标签中设置useCache=”true”代表当前这个statement要使用二级缓存。
如果不使用二级缓存可以设置为false
注意:
针对每次查询都需要最新的数据sql,要设置成useCache="false",禁用二级缓存。
-->
<select id="findById" resultType="com.fuyi.entity.User" parameterType="integer" useCache="true">
select * from user where id = #{id}
</select>
2.2.1.3 测试
@Test
public void level2CacheTest() throws IOException {
// 1. 加载核心配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 获取SQLFactory工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3. 获取SQLSession对象
SqlSession sqlSession = factory.openSession();
// 4. 获取mapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 5. 执行查询
User result = mapper.findById(1);
// 6. 打印结果
logger.info("=====================第一次查询====================");
System.out.println(result);
sqlSession.close();
SqlSession sqlSession2 = factory.openSession();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
logger.info("=====================第二次查询====================");
System.out.println(mapper2.findById(1));
sqlSession.close();
resourceAsStream.close();
}
2.2.1.4 分析
二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射的sql语句,多个
SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
- 映射语句文件中的所有select语句将会被缓存。
- 映射语句文件中的所有insert、update和delete语句会刷新缓存。
- 由于二级缓存是namepace级别的,所以当嵌套查询的时候,嵌套语句所在的namepace2发生了缓存刷新,在namepace1缓存并没有得到刷新,此时就会出现脏读的问题。(感觉二级缓存很鸡肋)
3. Mybatis注解
3.1 常用注解
- @Insert:实现新增,等价于
<insert></insert>
- @Delete:实现删除,等价于
<delete></delete>
- @Update:实现更新,等价于
<update></update>
- @Select:实现查询,等价于
<select></select>
- @Result:实现结果集封装,等价于
<result></result>
- @Results:可以与@Result 一起使用,封装多个结果集,等价于
<resultMap></resultMap>
- @One:实现一对一结果集封装,等价于
<association></association>
- @Many:实现一对多结果集封装,等价于<>
3.2 CRUD操作(重点)
3.2.1 查询
- userMapper
/**
* 查询所有
*
* @return
*/
@Select("select * from user")
List<User> annotationFindAll();
- 测试
@Test
public void annotationFindAll() throws IOException {
// 1. 加载核心配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 获取SQLFactory工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3. 获取SQLSession对象
SqlSession sqlSession = factory.openSession();
// 4. 执行SQL参数
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 5. 查询
List<User> result = mapper.annotationFindAll();
// 6. 打印
result.forEach(System.out::println);
// 7. 释放资源
sqlSession.close();
}
3.2.2 添加
- userMapper
/**
* 添加
*
* @param user
*/
@Insert("INSERT INTO `user`(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})")
public void annotationSave(User user);
- 测试
@Test
public void annotationSave() throws IOException {
// 1. 加载核心配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 获取SQLFactory工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3. 获取SQLSession对象
SqlSession sqlSession = factory.openSession();
// 4. 执行SQL参数
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("测试");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("广东佛山");
// 5. 查询
mapper.annotationSave(user);
sqlSession.commit();
// 6. 释放资源
sqlSession.close();
}
3.2.3 删除
- userMapper
/**
* 删除
*
* @param id
*/
@Delete("DELETE FROM `user` where id = #{id}")
public void annotationDelete(Integer id);
- 测试
@Test
public void annotationDelete() throws IOException {
// 1. 加载核心配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 获取SQLFactory工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3. 获取SQLSession对象
SqlSession sqlSession = factory.openSession();
// 4. 执行SQL参数
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 5. 查询
mapper.annotationDelete(4);
sqlSession.commit();
// 6. 释放资源
sqlSession.close();
}
3.2.4 更新
- userMapper
/**
* 更新
*
* @param user
*/
@Update("UPDATE `user` SET username = #{username},birthday = #{birthday},sex= #{sex},address = #{address} WHERE id = #{id}")
public void annotationUpdate(User user);
- 测试
@Test
public void annotationUpdate() throws IOException {
// 1. 加载核心配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 获取SQLFactory工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3. 获取SQLSession对象
SqlSession sqlSession = factory.openSession();
// 4. 执行SQL参数
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setId(4);
user.setUsername("测试2");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("广东肇庆");
// 5. 查询
mapper.annotationUpdate(user);
sqlSession.commit();
// 6. 释放资源
sqlSession.close();
}
3.3 复杂映射
3.3.1 一对一
查询订单和对应的用户信息
- SQL语句
select * from orders
select * from user where id = #{订单id}
- OrderMapper
/**
* 一对一查询,基于注解
*
* @return
*/
@Select("select * from orders")
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(column = "ordertimes", property = "ordertimes"),
@Result(column = "total", property = "total"),
@Result(column = "uid", property = "user", javaType = User.class,
one = @One(select = "com.fuyi.mapper.UserMapper.annotationFindById", fetchType = FetchType.EAGER))
})
List<Order> annotationFindAllWithUser();
- UserMapper
/**
* 基于注解查询
*
* @param id
* @return
*/
@Insert("select * from user where id = #{id}")
User annotationFindById(Integer id);
- 测试
/**
* 一对一,多对一测试例子
*/
@Test
public void annotationOneToOneTest() throws IOException {
// 1. 加载核心配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 获取SQLFactory工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3. 获取SQLSession对象
SqlSession sqlSession = factory.openSession();
// 4. 获取mapper
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
// 5. 执行查询
List<Order> result = mapper.annotationFindAllWithUser();
// 6. 打印结果
result.forEach(System.out::println);
sqlSession.close();
resourceAsStream.close();
}
3.3.2 一对多
查询用户和对应的订单信息
- sql语句
select * from user
select * from orders where uid = #{用户id}
- UserMapper
/**
* 查询所有
*
* @return
*/
@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.fuyi.mapper.OrderMapper.annotationFindById", fetchType = FetchType.EAGER))
})
List<User> annotationFindAll();
- OrderMapper
/**
* 基于注解查询
*
* @param id
* @return
*/
@Select("select * from orders where id = #{id}")
List<Order> annotationFindById(Integer id);
- 测试
@Test
public void annotationFindAll() throws IOException {
// 1. 加载核心配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 获取SQLFactory工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3. 获取SQLSession对象
SqlSession sqlSession = factory.openSession();
// 4. 执行SQL参数
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 5. 查询
List<User> result = mapper.annotationFindAll();
// 6. 打印
result.forEach(System.out::println);
// 7. 释放资源
sqlSession.close();
}
3.3.3 多对多
查询用户和对应的角色信息
- sql语句
# 查询所有用户
select * from user
# 根据用户id查询用户的角色列表
select * from sys_role sr left join sys_user_role sur on sur.roleid = sr.id where sur.userid = id
- UserMapper
/**
* 基于注解
*
* @return
*/
@Select("select * from user")
@Results({
@Result(id = true, property = "id", column = "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.fuyi.mapper.SysRoleMapper.annotationFindByUid", fetchType = FetchType.EAGER))
})
List<User> annotationFindAllWithRole();
- RoleMapper
/**
* 基于注解根据用户id查询对应的角色身份
*
* @param uid
* @return
*/
@Select("select * from sys_role sr left join sys_user_role sur on sur.roleid = sr.id where sur.userid = id")
List<SysRole> annotationFindByUid(Integer uid);
- 测试
@Test
public void annotationFindAllWithRoleTest() throws IOException {
// 1. 加载核心配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 获取SQLFactory工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3. 获取SQLSession对象
SqlSession sqlSession = factory.openSession();
// 4. 执行SQL参数
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 5. 查询
List<User> users = mapper.annotationFindAllWithRole();
users.forEach(System.out::println);
// 6. 释放资源
sqlSession.close();
}
3.4 基于注解的二级缓存
- 首先在SQLMapConfig.xml进行配置
<setting name="cacheEnabled" value="true"/>
- UserMapper使用注解
@CacheNamespace
public interface UserMapper {}
3.5 基于注解的延迟加载
fetchType = FetchType.LAZY 表示懒加载
fetchType = FetchType.EAGER 表示立即加载
fetchType = FetchType.DEFAULT 表示使用全局配置
3.6 小结
1.注解开发和xml配置相比,从开发效率来说,注解编写更简单,效率更高。
2.从可维护性来说,注解如果要修改,必须修改源码,会导致维护成本增加。xml维护性更强。