Mybatis框架延迟加载、缓存和注解开发
- Mybatis中的延迟加载:
- 什么是延迟加载:就是用到数据的时候才进行加载,不需要用到数据的时候就不加载数据,延迟加载又称为懒加载或者是叫按需加载。与之对应的就是立即加载:不管用不用,只要一调用方法,马上发起查询。
- 使用延迟加载的坏处:只有使用数据的时候才会从数据库中查询数据,这样在大批量的数据的时候,用户等待的时间也会增长,造成用户的体验下降。
- 两种加载策略的使用场景:
- 在在对应的四种表关系中,一对多、多对多通常情况下采用延迟加载。
- 多对一、一对一通常情况下采用立即加载。
- Mybatis中延迟加载的实现:
- Mybatis中使用resultMap来进行一对一、一对多和多对多关系的操作,多表关系的映射是通过association和collection来完成的。而association和collection同样是具备延迟加载的功能。
- SqlMapConfig.xml中开启延迟加载的策略:
<!--配置属性 使用的是setting标签 --> <settings> <!-- 开启mybatis支持延迟加载--> <setting name="lazyLoadingEnabled" value="true"/> <!--aggressiveLazyLoading:是否按需加载属性 在 3.4.1 及之后的版本默认值为 false 不用再手动的配置--> <setting name="aggressiveLazyLoading" value="false"/> </settings>
-
lazyLoadingEnabled:是否启用延迟加载,mybatis默认为false,不启用延迟加载。lazyLoadingEnabled属性控制全局是否使用延迟加载,特殊关联关系也可以通过嵌套查询中fetchType属性单独配置(fetchType属性值lazy或者eager)。
-
aggressiveLazyLoading:是否按需加载属性,现在默认值为false,lazyLoadingEnabled属性启用时只要加载对象,就会加载该对象的所有属性;关闭该属性则会按需加载,即使用到某关联属性时,实时执行嵌套查询加载该属性。
-
- UserMapper中的配置:(使用collection来实现)
<resultMap id="UserResultMap" type="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="address" column="address"></result> <result property="sex" column="sex"></result> <result property="birthday" column="birthday"></result> <!-- 配置user对象中的accounts对象的映射一对多 ofType:表示的是集合中元素的类型 没有配置类名需要使用全限定类名 resultMap中<id property="id" column="id"> 和collection中</id><id property="id" column="zid"></id> 一致的情况下 需要给和collection中column属性起别名只要不一致就行 --> <collection property="accounts" ofType="account" select="com.itheima.dao.AccountDao.findAccountByUid" column="id"></collection> </resultMap> <select id="findAllUserAndAccount" resultMap="UserResultMap"> <!--sql语句的结尾的分号写不写都行 --> select * from user </select>
- Mybatis中的缓存:和大多数的持久层框架一样,mybatis中提供了缓存的策略,通过缓存来减少查询数据库的次数,从而提高性能。
- mybatis中缓存的分类:
- 一级缓存:一级缓存是SqlSession级别的存在,只要SqlSession没有被flush或者是close,他就存在。
- 二级缓存:二级缓存是mapper映射级别的缓存(同一个namespace),多个SqlSession去操作同一个mapper映射的SQL语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
- 一级缓存的使用:
- 用户持久层映射文件的配置: 使用的属性是useCache=“true”
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.dao.IUserDao"> <!-- 根据 id 查询 --> <select id="findById" resultType="UsEr" parameterType="int" useCache="true"> select * from user where id = #{uid} </select> </mapper>
- 对一级缓存的分析:当调用 SqlSession 的修改,添加,删除,commit(),close()等就会清空缓存。
- 第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
- 如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的是为了让缓存中存储的是最新的信息,避免脏读。
- 第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。
- 用户持久层映射文件的配置: 使用的属性是useCache=“true”
- 二级缓存的使用:MyBatis二级缓存是和命名空间绑定的 ,即二级缓存需要配置在 Mapper.xml 映射文件中
或者配置在 Mapper 接口中- 二级缓存的结构图:
- 二级缓存的开启和关闭步骤:
-
开启:在主配置文件中开启二级缓存:
<settings> <!-- 开启二级缓存的支持 --> <setting name="cacheEnabled" value="true"/> </settings> <!--因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为 false 代表不开启二级缓存。-->
-
配置相关的mapper映射文件:
<cache>
标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.dao.IUserDao"> <!-- 开启二级缓存的支持 --> <cache></cache> </mapper>
cache
标签中的属性:(常用的属性)- eviction (收回策略)
- LRU (最近最少使用的) 移除最长时间不被使用的对象,这是默认值
- FIFO (先进先出〉 按对象进入缓存的顺序来移除它们
- SOFT (软引用) 移除基于垃圾回收器状态和软引用规则的对象
- WEAK (弱引用) 更积极地移除基于垃圾收集器状态和弱引用规则的对象
- flushinterval (刷新间隔〉。可以被设置为任意正整数单位是毫秒 默认的是不设置表示的是没有刷新时间
- size (引用数目) 可以被设置为任意正整数,要记住缓存的对象数目和运行环境的可用内存资源数 默认值是1024
- readOnly (只读)。属性可以被设置为 true/false 。只读的缓存会给所有调用者返回缓存对象的相同实例 读写的缓存会通过序列化返回缓存对象的拷贝 这样的方式比较慢但是安全 默认值是false
- eviction (收回策略)
-
在statement上面使用userCache属性:使用二级缓存就设置为true 不使用就设为false
<!-- 根据 id 查询 --> <select id="findById" resultType="user" parameterType="int" useCache="true"> select * from user where id = #{uid} </select>
-
使用默认的二级缓存效果:
- 映射语句文件中的所有 SELECT 语句将会被缓存
- 映射语句文件中的所有 INSERT UPDATE DELETE 语句会刷新缓存
- 缓存会使用 Least Recently Used LRU ,最近最少使用的)算法来收回
- 根据时间表(如 no Flush Interval ,没有刷新间隔),缓存不会以任何时间顺序来刷新
- 缓存会存储集合或对象(无论查询方法返回什么类型的值)的 10 个引用。
- 缓存会被视为 read/write (可读/可写)的 意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其 调用者或线程所做的潜在修改
-
【注意】1 如果每次查询都要使用最新的数据就不能使用二级缓存。2 使用二级缓存,所缓存的类必须实现Serializable接口,必须能够使用序列化的方式来保存对象。
-
- 二级缓存的结构图:
- 集成 EhCache 缓存:EhCache是一个Java 进程内的缓存框架,具有快速、精干等特点 使用步骤:
- 添加项目依赖:在pom.xml 添加如下依赖:
<dependency> <groupid>org.mybatis.caches</groupid> <artifactid>mybatis-ehcache</artifactid> <version>l.0 .3</version> </dependency>
- 配置 EhCache:在src/main/resources 目录下新增 ehcache.xml 文件:
<?xml version 1. encoding UTF ?〉 <ehcache xmlns : xsi="http://www.w3.org/2001/XMLSchema-instance " xsi : noNamespaceSchemaLocation=" ehcache . xsd" updateCheck="false " monitoring="autodetect" dynam cConf g= true <diskStore path=" D:/cache" /> <defaultCache maxElementsinMemory="3000" eternal="false" copyOnRead="true" copyOnWrite="true" timeToidleSeconds="3600" timeToLiveSeconds="3600" overf lowToDisk="true" diskPersistent="true" /> </ehcache>
- 配置中有两个重要的属性:
copyOnRead
的含义是,判断从缓存中读取数据时是返回对象的引用还是复制一个对象返回。默认情况下是 false ,即返回数据的引用,这种情况下返回的都是相同的对象,和 MyBatis默认缓存中的只读对象是相同的copyOnWrite
的含义是 ,判断写入缓存时是直接缓存对象的引用还是复制 个对象然后缓存,默认也是 false 如果想使用可读写缓存,就需要将这两个属性配置为 true ,如果使用只读缓存,可以不配置这两个属性,使用默认值 false 即可
- 配置中有两个重要的属性:
- 修改 Mapper文件中的缓存配置 ehcache-cache 提供了两种缓存实现
org .mybatis . caches . ehcache . EhcacheCache
org . mybatis.caches . ehcache.LoggingEhcache
第二种是带日志的缓存 只需要设type属性就能使用EhCache缓存了<mapper namespace= "全限定类名"> <cache type="org.mybatis caches ehcache.EhcacheCache"/> <!--其他的配置--> </mapper >
- 添加项目依赖:在pom.xml 添加如下依赖:
- 集成 Redis 缓存(至于Redis有多牛以前的博客已经讲解了 就不赘述了)直接使用步骤:
-
添加项目依赖pom.xml 添加依赖:
<dependency> <groupid>org.mybatis.caches</groupid> <artifactid>mybatis-redis</artifactid> <version>l.0 .0-beta2</version> </dependency>
-
进行Redis 的配置:Redis的安装 和使用 也就不说了 配置完成之后启动Redis 在
src/main /resources
目录下新增redis. properties
文件:host=localhost port=6379 connectionTimeout=5000 soTimeout=5000 password= database=0 clientName=
-
修改
Mapper.xml
中的缓存配置redis-cache
提供了 MyBatis 缓存实现,org mybatis caches redis RedisCache
需要注意的就是:RedisCache 在保存缓存数据和获取缓存数据时,使用了 Java 序列化和反序列化,因此还需要保证被缓存的对象必须 Serializable 接口<mapper namespace="全限定类名"> <cache type="org.mybatis.caches.redis.RedisCache" /> <!--其他的配置--> </mapper>
-
- 使用二级缓存可能出现的问题:脏数据的出现 解决方式:就需要用到参照缓存了 当某几个表可以作为一个业务整体时,通常是让几个会关联的表同时使用同 一个二级缓存,这样就能解决脏数据问题
- 二级缓存适用的场景:(通俗的说就是不容器出现脏数据)
- 以查询为主的应用中,只有尽可能少的增、删、改操作
- 绝大多数以单表操作存在时,由于很少存在互相关联的情况,因此不会出现脏数据
- 可以按业务划分对表进行分组时 如关联的表比较少,可以通过参照缓存进行配
- mybatis中缓存的分类:
- Mybatis中的注解开发:
- Mybatis中的常用注解:
-
Mybatis中实现CRUD使用的注解: 就是和对应的标签作用一样
- @Insert:实现新增
- @Update:实现更新
- @Delete:实现删除
- @Select:实现查询
- @SelectKey 获取自增主键 不是用主键自增 而是用序列也能获取
- @Options 获取自增主键 只能获取自增主键
@SelectKey(keyColumn="id",keyProperty="id",resultType=Integer.class,before = false, statement = { "select last_insert_id()" }) @Options(useGeneratedKeys =true, keyProperty =”id”)
@Insert("insert into user(username, address, sex, birthday) values (#{username},#{address},#{sex},#{birthday})") void saveUser(User user); @Update("update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id = #{id}") void updateUser(User user); @Delete("delete from user where id = #{id}") void deleteUser(Integer id); @Select("select * from user where id = #{id}") User findById(Integer id);
除了上面 种注解可以使用简单的 SQL 外, MyBatis 还提供了 Provider 注解,分别
@SelectProvider
、@InsertProvider
、@UpdateProvider
和@DeleteProvider
同样可以实现查询、插入、更新、删除操作:(不常用)Provider 的注解中提供了两个必填属性 type method type 配置的是个包含method属性指定方法的类,这个类必须有空的构造方法,这个方法的指的就是要执行的 SQL 语句, 并且method 属性指定的方法的返回值必须是 String类型@SelectProvider(type = PrivilegeProvider.class , method = "selectByid") SysPrivilege selectByid(Long id);
其中 PrivilegeProvider 类代码如下:
public class PrivilegeProv der { public String selectByid(final Long id) { return new SQL{) { SELECT "id, privilege name, privilege url"); FROM ("sys privilege"); WHERE ("id= #{id }"); } . toString () ; } }
-
@Param注解的作用是给参数命名,参数命名后就能根据名字得到参数值,正确的将参数传入sql语句中
List<SysRole> selectRo lesByUseridAndRoleEnabled( @Param (”user Id”) Long user Id, @Param (” enable d ”) Integer enabled) ;
-
Mybatis中和结果集封装有关的注解:
- @Result:实现结果集封装
- @Results:可以与@Result 一起使用,封装多个结果集
- @ResultMap:实现引用@Results 定义的封装
- @One:实现一对一结果集封装
- @Many:实现一对多结果集封装
-
MyBatista中的实现复杂关系映射和缓存的使用:
- @SelectProvider: 实现动态 SQL 映射
- @CacheNamespace:实现注解二级缓存的使用
-
- Mybatis中的单表的CRUD(注解开发)
-
持久层接口的编写:使用注解
public interface IUserDao { /** * 查询所有用户 * @return */ @Select("select * from user") @Results(id="userMap", value= { @Result(id=true,column="id",property="userId"), @Result(column="username",property="userName"), @Result(column="sex",property="userSex"), @Result(column="address",property="userAddress"), @Result(column="birthday",property="userBirthday") }) List<User> findAll(); /** * 根据 id 查询一个用户 * @param userId * @return */ @Select("select * from user where id = #{uid} ") @ResultMap("userMap") User findById(Integer userId); /** * 保存操作 * @param user * @return */ @Insert("insert into user(username,sex,birthday,address)values(#{username},#{sex},#{birthday},#{address} )") @SelectKey(keyColumn="id",keyProperty="id",resultType=Integer.class,before = false, statement = { "select last_insert_id()" }) int saveUser(User user); /** * 更新操作 * @param user * @return */ @Update("update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id =#{id} ") int updateUser(User user); /** * 删除用户 * @param userId * @return */ @Delete("delete from user where id = #{uid} ") int deleteUser(Integer userId); /** * 查询使用聚合函数 * @return */ @Select("select count(*) from user ") int findTotal(); /** * 模糊查询 * @param name * @return */ @Select("select * from user where username like #{username} ") List<User> findByName(String name); } //通过注解方式,我们就不需要再去编写 UserDao.xml 映射文件了。使用注解非常的方便
-
主配置文件的编写:
<?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 文件的位置 --> <properties resource="jdbcConfig.properties"></properties> <!-- 配置别名的注册 --> <typeAliases> <package name="com.itheima.domain"/> </typeAliases> <!-- 配置环境 --> <environments default="mysql"> <!-- 配置 mysql 的环境 --> <environment id="mysql"> <!-- 配置事务的类型是 JDBC --> <transactionManager type="JDBC"></transactionManager> <!-- 配置数据源 --> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <!-- 配置映射信息 --> <mappers> <!-- 配置 dao 接口的位置,它有两种方式 第一种:使用 mapper 标签配置 class 属性 第二种:使用 package 标签,直接指定 dao 接口所在的包 --> <package name="com.itheima.dao"/> </mappers> </configuration>
-
外部的properties文件的编写
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/eesy_mybatis jdbc.username=root jdbc.password=root
-
- 使用注解实现复杂关系映射:
- 在使用配置文件的形式的时候,复杂关系的映射使用的是<resultMap>标签来实现,而使用注解实现复杂关系的映射使用的是@Results 注解,@Result 注解,@One 注解,@Many 注解。使用注解使用的是@resultMap注解
- 注解的说明:
-
@Results 注解
- 代替的是标签<resultMap>
- 该注解中可以使用单个@Result 注解,也可以使用@Result 集合
@Results({@Result(),@Result()})或@Results(@Result())
-
@Resutl 注解
- 代替了 <id>标签和<result>标签
- @Result 中 属性介绍:
- id 是否是主键字段
- column 数据库的列名
- property 需要装配的属性名
-
one 需要使用的@One 注解(@Result(one=@One)()))
- @One 注解(一对一)
代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。 - @One 注解属性介绍:
- select 指定用来多表查询的 sqlmapper
- fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。。
- 使用格式:
@Result(column=" “,property=”",one=@One(select=""))
@Select("select * from Account") @Results( id = "resultMap", value = { @Result(column = "id" , property = "id", id = true), @Result(column = "money",property = "money"), @Result(column = "uid",property = "uid"), @Result(property = "user",column = "uid" ,one=@One(select = "com.qst.dao.IUserDao.findById",fetchType = FetchType.EAGER)) })
- @One 注解(一对一)
-
@Many 注解(多对一)
- many 需要使用的@Many 注解(@Result(many=@many)()))
- 代替了<Collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。
注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType(一般为 ArrayList)但是注解中可以不定义; - 使用格式:
Result(property="",column="",many=@Many(select=""))
@Select("select * from employee") @Results(id="resultMap", value = { @Result(column = "id" , property = "userId" ,id=true), @Result(column = "gender" ,property = "gender"), @Result(column = "email",property = "email"), @Result(column = "did",property = "did"), @Result(property = "account",column = "id" , many = @Many(select = "com.qst.dao.IAccountDao.selectbyId", fetchType = FetchType.LAZY)) })
-
- 基于注解实现二级缓存:
-
首先在主配置文件中开启支持二级缓存:
<!-- 配置二级缓存 --> <settings> <!-- 开启二级缓存的支持 --> <setting name="cacheEnabled" value="true"/> </settings>
-
在持久层接口上使用注解配置二级缓存:
@CacheNamespace(blocking=true)//mybatis 基于注解方式实现配置二级缓存 public interface IUserDao {}
-
- Mybatis中的常用注解: