Mybatis(四)
Mybatis中的延迟加载
Mybatis中的缓存
什么是缓存
为什么使用缓存
什么样的数据能使用缓存,什么样的数据不能使用
Mybatis中的一级缓存和二级缓存
Mybatis中的注解开发
环境搭建
单表CRUD操作(代理Dao方式)
多表查询操作
缓存的配置
Mybatis中的延迟加载
在前面的学习中,我们已经掌握了 Mybatis 中一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询。实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的账户信息,此时就是我们所说的延迟加载。
问题:在一对多中,当我们有一个用户,它有100个账单。在查询用户的时候,要不要把关联的张大宝查出来?在查询账单的时候,要不要把关联的用户查出来?
在查询用户时,用户下的账单信息应该是,什么时候需要账户信息,就什么时候查询(一个用户多个账单)
在查询账单时,账单的所属用户信息应该是随着账单查询,一起查询出来(一个账单只能对应一个用户)
延迟加载和立即加载:
延迟加载:在真正使用数据时才发起查询,不用的时候不查询 (也叫按需加载,懒加载)
立即加载:不管用不用,只要一调用方法,马上发起查询
MyBatis中的延迟加载
MyBatis 延迟加载和立即加载
在对应的一对多,一对一(多对一),多对多这四种表关系中:
一对多,多对多:通常情况下我们都是采用延迟加载
一对一(多对一):通常情况下我们都是采用立即加载
(很好理解的)
在之前的笔记 中,我们使用了resultMap来实现一对一,一对多,多对多关系的操作。主要是通过 <association>
、<collection>
实现一对一及一对多映射。其实**<association>
、<collection>
也具备延迟加载功能。**
Mybatis一对一(多对一)实现延迟加载
视频讲解
在之前一对一的案例 的基础上,我们进行如下操作:
首先在主配置文件(SqlMapConfig.xml)添加对<settings>
的配置
1 2 3 4 5 6 <settings > <setting name ="lazyLoadingEnabled" value ="true" /> <setting name ="aggressiveLazyLoading" value ="false" /> </settings >
关于<settings>
下的两个属性:
lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载 。 特定关联关系中可通过设置 fetchType
属性来覆盖该项的开关状态 ,默认值是false
aggressiveLazyLoading:开启时,任一方法的调用都会加载该对象的所有延迟加载属性(即实现立即加载) 。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods
)。默认值是 false (在 3.4.1 及之前的版本中默认为 true)
关于**fetchType
属性**:可选的。有效值为 lazy
和 eager
。 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled
,使用属性的值
具体可查看官网:设置(settings) ,关联
然后在持久层接口添加代码如下:
1 2 3 4 5 /** * 查询所有账户信息(包括账户所对应的用户的信息,但是用户的信息采用延迟加载策略) * @return */ List<Account > findAll();
接着在映射配置文件中添加代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <resultMap id ="accountUserMap" type ="top.qing.domain.Account" > <id property ="ID" column ="ID" /> <result property ="UID" column ="UID" /> <result property ="MONEY" column ="MONEY" /> <association property ="user" column ="UID" javaType ="top.qing.domain.User" select ="top.qing.dao.UserDao.findById" > </association > </resultMap > <select id ="findAll" resultMap ="accountUserMap" > SELECT * FROM account </select >
MyBatis有两种不同的方式加载关联 :
嵌套Select查询:通过执行另外一个SQL映射语句来加载期望的复杂类型
嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集
这里我们采用了嵌套Select查询来加载关联,而在之前的案例 中我们采用的是嵌套结果映射来处理连接结果的重复子集从而实现加载关联
注意 :这里由于我们在使用了**<association>
标签中使用了 column属性和 select属性**,所以在下面的findAll方法的SQL语句我们只需要写上SELECT * FROM account
,最后依然可以查询到user表的相关内容(因为select属性 中指定了UserDao的对应方法(id))
关于**<association>
**标签的相关属性:
column:数据库中的列名,或者是列的别名,指定传递给嵌套Select查询语句的列名 (要传递给select映射的参数)
select:用于加载复杂类型属性的映射语句的ID,它会从column属性指定的列中检索数据,作为参数传递给目标 select 语句 (要调用的select映射的id)
fetchType:可选的,有效值为 lazy 和 eager(具体上面已经提到过了)
具体可查看官网:关联
最后在测试类中(执行代码):
1 2 3 4 5 6 7 8 @Test public void testFindAll () { List<Account> accounts = accountDao.findAll(); }
结果如下:
可以发现如果我们不输出user相关信息的话,就不会执行UserDao下的findById方法 。这样我们一对一的延迟加载就配置成功了。
Mybatis一对多实现延迟加载
视频讲解
在之前一对多的案例 基础上,我们进行如下改造:
首先要提前准备一下AccountDao,补充一个findAccountByUid方法。
在AccountDao下添加代码如下:
1 2 3 4 5 List<Account> findAccountByUid (Integer userID) ;
在AccountDao.xml下添加代码如下:
1 2 3 4 <select id ="findAccountByUid" resultType ="top.qing.domain.Account" > SELECT * FROM account WHERE UID = #{uid} </select >
接下来就可以进行Mybatis一对多的延迟加载了。
首先在主配置文件(SqlMapConfig.xml)添加对<settings>
的配置
1 2 3 4 5 6 <settings > <setting name ="lazyLoadingEnabled" value ="true" /> <setting name ="aggressiveLazyLoading" value ="false" /> </settings >
相关说明上面已经提到过了
然后在持久层接口添加代码如下:
1 2 3 4 5 List<User> findAll () ;
接着在映射配置文件中添加代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <resultMap id ="userAccountMap" type ="top.qing.domain.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 ="top.qing.domain.Account" column ="id" select ="top.qing.dao.AccountDao.findAccountByUid" > </collection > </resultMap > <select id ="findAll" resultMap ="userAccountMap" > SELECT * FROM user </select >
<collection>
标签在配置延迟加载的相关属性大体上和<association>
是一样的,具体可以看上面的笔记。
select 属性 :用于指定查询的sql语句 ,填写的是该sql映射的id
column 属性 :用于指定select属性的sql语句的参数来源 (上面的参数来自于use 的id列,所以就写成id 这一个字段名了)
具体可看官网:集合
最后在测试类中(执行代码):
1 2 3 4 5 6 7 8 @Test public void testFindAll () throws IOException { List<User> users = userDao.findAll(); }
结果如下:
Mybatis中的缓存
像大多数的持久化框架一样,Mybatis也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。
Mybatis中缓存分为一级缓存,二级缓存。
什么是缓存:存在于内存中的临时数据
为什么使用缓存:减少和数据库的交互次数,提高执行效率
什么样的数据能使用缓存,什么样的数据不能使用:
适用于缓存:经常查询并且不经常改变的,数据的正确与否对最终结果影响不大的
不适用于缓存:经常改变的数据,数据的正确与否对最终结果影响很大的(例如:商品的库存,银行的汇率,股市的牌价)
MyBatis缓存机制
mybatis的缓存机制(一级缓存二级缓存和刷新缓存)和mybatis整合ehcache
Mybatis一级缓存
视频讲解
在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。具体执行过程如下图所示:
一级缓存:它指的是Mybatis中SqlSession对象的缓存 。当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中查询是否有,有的话直接拿出来用**。当SqlSession对象消失时,mybatis的一级缓存也就消失了。**
一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。
Mybatis二级缓存
视频讲解
在上文中提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示:
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
二级缓存:它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
二级缓存的使用步骤:
让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
让当前的映射文件支持二级缓存(在UserDao.xml中配置)
让当前的操作支持二级缓存(在<select>
标签中配置)
Mybatis中的注解开发
这几年来注解开发越来越流行,Mybatis 也可以使用注解开发方式,这样我们就可以减少编写 Mapper 映射文件了 。这里我们先学习一些基本的CRUD,再学习复杂映射关系及延迟加载。
注意:在Mybatis中的用一个Dao接口中,不能同时使用xml和注解
Mybatis的常用注解说明:
@Insert
:实现新增
@Update
:实现更新
@Delete
:实现删除
@Select
:实现查询
@Result
:实现结果集封装
@Results
:可以与@Result 一起使用,封装多个结果集
@ResultMap
:实现引用@Results 定义的封装
@One
:实现一对一结果集封装
@Many
:实现一对多结果集封装
@SelectProvider
: 实现动态 SQL 映射
@CacheNamespace
:实现注解二级缓存的使用
MyBatis 注解(摘自MyBatis官方文档)
MyBatis 注解方式的基本用法
MyBatis 注解
使用Mybatis注解实现基本的CRUD
CRUD相关注解:
@Insert
:实现新增
@Update
:实现更新
@Delete
:实现删除
@Select
:实现查询
关于环境准备最开始的pom.xml以及resources相关的配置这里就不多说了。这里就将使用的方法一起给出吧:
保存操作(增):saveUser
更新操作(改):updateUser
删除操作(删):deleteUser
查询一个(查):findById
模糊查询(查):findByName
查询user表的总记录数(聚合函数):findTotal
首选创建User类,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package top.qing.domain;import lombok.Data;import java.io.Serializable;import java.util.Date;@Data public class User implements Serializable { private Integer id; private String username; private Date birthday; private String sex; private String address; }
然后创建主配置文件SqlMapConfig.xml,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <?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 > <properties > <property name ="driver" value ="com.mysql.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" /> <property name ="username" value ="root" /> <property name ="password" value ="123456" /> </properties > <settings > <setting name ="lazyLoadingEnabled" value ="true" /> <setting name ="aggressiveLazyLoading" value ="false" /> </settings > <environments default ="mysql" > <environment id ="mysql" > <transactionManager type ="JDBC" > </transactionManager > <dataSource type ="POOLED" > <property name ="driver" value ="${driver}" /> <property name ="url" value ="${url}" /> <property name ="username" value ="${username}" /> <property name ="password" value ="${password}" /> </dataSource > </environment > </environments > <mappers > <package name ="top.qing.dao" /> </mappers > </configuration >
创建持久层接口代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package top.qing.dao;import org.apache.ibatis.annotations.Delete;import org.apache.ibatis.annotations.Insert;import org.apache.ibatis.annotations.Select;import org.apache.ibatis.annotations.Update;import top.qing.domain.User;import java.util.List;public interface UserDao { @Select (value = "SELECT * FROM `user`" ) List<User> findAll () ; @Select ("select * from user where id = #{uid}" ) User findById (Integer userId) ; @Select ("select * from user where username like #{uStr}" ) List<User> findByName (String UserStr) ; @Select ("select count(id) from user;" ) Integer findTotal () ; @Insert ("insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})" ) void saveUser (User user) ; @Update ("update user set username = #{username},birthday = #{birthday},sex = #{sex}, address = #{address} where id = #{id}" ) void updateUser (User user) ; @Delete ("delete from user where id = #{uid}" ) void deleteUser (Integer userId) ; }
执行代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 package top.qing;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 top.qing.dao.UserDao;import top.qing.domain.User;import java.io.IOException;import java.io.InputStream;import java.util.Date;import java.util.List;public class UserTest { private InputStream inputStream; private SqlSession session; private UserDao userDao; @Before public void init () { inputStream = Resources.class.getClassLoader().getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(inputStream); session = factory.openSession(); userDao = session.getMapper(UserDao.class ) ; } @After public void destroy () throws IOException { session.commit(); session.close(); inputStream.close(); } @Test public void testFindAll () throws IOException { List<User> users = userDao.findAll(); for (User user : users) { System.out.println(user); } } @Test public void testSaveUser () throws IOException { User user = new User(); user.setUsername("saveUser" ); user.setBirthday(new Date()); user.setSex("男" ); user.setAddress("中国" ); userDao.saveUser(user); } @Test public void testUpdateUser () throws IOException { User user = new User(); user.setId(58 ); user.setUsername("updateUser" ); user.setBirthday(new Date()); user.setSex("女" ); user.setAddress("中国" ); userDao.updateUser(user); } @Test public void testDeleteUser () throws IOException { userDao.deleteUser(52 ); } @Test public void testFindById () throws IOException { User user = userDao.findById(58 ); System.out.println(user); } @Test public void testFindByName () throws IOException { List<User> users = userDao.findByName("%王%" ); for (User user : users) { System.out.println(user); } } @Test public void testFindTotal () throws IOException { Integer total = userDao.findTotal(); System.out.println("总记录数为:" +total); } }
使用Mybatis注解实现复杂关系映射开发
要实现复杂关系映射,之前我们是在映射文件中通过配置<resultMap>
来实现 ,在使用注解开发时我们需要借助@Results
注解,@Result
注解,@One
注解,@Many
注解。
相关注解说明:
@Results
注解:代替的是<resultMap>
标签 ,该注解中可以使用单个@Result
注解,也可以使用@Result
集合
例子:@Results({@Result(),@Result()})或@Results(@Result())
@Resutl
注解:代替了<id>
标签和<result>
标签
@Result
中属性介绍:
id:是否是主键字段
column:数据库的列名
property:需要装配的属性名
one:需要使用的@One 注解(@Result(one=@One)()))
many:需要使用的@Many 注解(@Result(many=@many)()))
@One
注解(一对一):代替了<association>
标签 ,是多表查询的关键,在注解中用来指定子查询返回单一对象
@One
注解属性介绍:
select:指定用来多表查询的sqlmapper
fetchType:会覆盖全局的配置参数lazyLoadingEnabled
使用格式:@Result(column=" “,property=”",one=@One(select=""))
@Many
注解(多对一):代替了<Collection>
标签 ,是多表查询的关键,在注解中用来指定子查询返回对象集合
@One
注解属性介绍:
select:指定用来多表查询的sqlmapper
fetchType:会覆盖全局的配置参数lazyLoadingEnabled
注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType(一般为 ArrayList)但是注解中可以不定义
使用格式:@Result(property="",column="",many=@Many(select=""))
使用注解实现一对一复杂关系映射及延迟加载
需求:加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载(注解方式实现)
首先创建Account实体类:
1 2 3 4 5 6 7 8 9 10 11 12 package top.qing.domain;import lombok.Data;import java.io.Serializable;@Data public class Account implements Serializable { private Integer ID; private Integer UID; private Double MONEY; }
然后创建AccountDao代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package top.qing.dao;import org.apache.ibatis.annotations.One;import org.apache.ibatis.annotations.Result;import org.apache.ibatis.annotations.Results;import org.apache.ibatis.annotations.Select;import org.apache.ibatis.mapping.FetchType;import top.qing.domain.Account;import java.util.List;public interface AccountDao { @Select (value = "SELECT * FROM account" ) @Results (id = "accountUserMap" , value = { @Result (id = true , property="ID" , column="ID" ), @Result (property="UID" , column="UID" ), @Result (property="MONEY" , column="MONEY" ), @Result (property="user" , column="UID" , one = @One (select = "top.qing.dao.UserDao.findById" , fetchType = FetchType.LAZY)) }) List<Account> findAll () ; }
在测试类中执行代码如下:
1 2 3 4 5 6 7 8 9 @Test public void testFindAll () { List<Account> accounts = accountDao.findAll(); for (Account account : accounts) { System.out.println(account); } }
结果如下:
使用注解实现一对多复杂关系映射
需求:查询用户信息时,也要查询他的账户列表(使用注解方式实现)
分析:一个用户具有多个账户信息,所以形成了用户(User)与账户(Account)之间的一对多关系。
首先要在User类中添加一个Account集合的属性(这里就不展示),然后在AccountDao添加一个findByUid方法:
1 2 3 4 5 * 根据用户id查询账单集合(因为一个用户对应对个账单) * @return */ @Select ("SELECT * FROM account WHERE UID = #{uid}" )List<Account> findByUid (Integer userID) ;
然后修改UserDao中的findAll方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Select (value = "SELECT * FROM `user`" ) @Results (id = "userAccountMap" , value = { @Result (id = true , property="id" , column="id" ), @Result (property="username" , column="username" ), @Result (property="birthday" , column="birthday" ), @Result (property="sex" , column="sex" ), @Result (property="address" , column="address" ), @Result (property = "accounts" ,column = "id" , many = @Many (select = "top.qing.dao.AccountDao.findByUid" , fetchType = FetchType.LAZY) ) }) List<User> findAll () ;
执行代码如下:
1 2 3 4 5 6 7 8 @Test public void testFindAll () throws IOException { List<User> users = userDao.findAll(); for (User user : users) { System.out.println(user); } }
结果如下:
Mybatis基于注解的二级缓存
视频讲解