主流框架一:Mybatis框架(5)Mybatis中的延迟加载,缓存和注解开发
一:Mybatis中的延迟加载
1.延迟加载概念
1.延迟加载
概念:在真正使用数据时才发起查询,不用的时候就不查询。也叫按需加载(懒加载)
2.立即加载
概念:不管用不用,只要一调用方法,马上发起查询。
在对应的四种表关系中:一对多,多对一,一对一,多对多
一对多,多对多:通常情况下我们都是采用延迟加载。
多对一,一对一:通常情况下我们都是采用立即加载。
2.延迟加载的配置
我们需要在 Mybatis 的配置文件 SqlMapConfig.xml 文件中添加延迟加载的配置。
根据API得知:配置后可延迟加载,如需查询时调用,配置value参数即可
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
3.延迟加载实现
主要是通过 association、collection 实现一对一及一对多映射。association、collection 具备延迟加载功能。
(1)association实现
在实现 查询账户信息同时查询用户信息 时 一对一
在账户的映射文件中(IAccountDao.xml)
select : 填写我们要调用的 select 映射的 id
column : 填写我们要传递给 select 映射的参数
<!-- 建立对应关系 -->
<resultMap type="account" id="accountMap">
<id column="aid" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<!-- 它是用于指定从表方的调用实体属性的 -->
<association property="user" javaType="user" select="com.itheima.dao.IUserDao.findById" column="uid">
//引用com.itheima.dao.IUserDao.findById方法 传递这个uid的参数
</association>
</resultMap>
<select id="findAll" resultMap="accountMap">
select * from account
</select>
而用户的映射配置文件中只需要简单的进行实现,参数用uid
<!-- 根据 id 查询 -->
<select id="findById" resultType="user" parameterType="int" >
select * from user where id = #{uid}
</select>
(2)collection实现
同样我们也可以在一对多关系配置的结点中配置延迟加载策略。
< collection > 结点中也有 select 属性,column 属性。
<resultMap type="user" id="userMap">
<id column="id" property="id"></id>
<result column="username" property="username"/>
<result column="address" property="address"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<!-- collection 是用于建立一对多中集合属性的对应关系
ofType 用于指定集合元素的数据类型
select 是将哪个方法一起执行(账户的 dao 全限定类名加上方法名称)
column 是用于指定使用哪个字段的值作为条件查询(id作为查询条件)
-->
<collection property="accounts" ofType="account" select="com.itheima.dao.IAccountDao.findByUid" column="id">
</collection>
</resultMap>
而账户的配置映射文件中,
<select id="findByUid" resultType="account" parameterType="int">
select * from account where uid = #{uid}
</select>
二:Mybatis中的缓存
像大多数的持久化框架一样,Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,减少和数据库的交互次数,提高执行效率。
经常查询并且不经常改变的,数据的正确与否对最终结果影响不大的适用于缓存
1.一级缓存
它指的是Mybatis中SqlSession对象的缓存。当我们执行查询之后,查询的结果会同时存入倒SqlSession为我们提供一块区域中。该区域的结构是一个Map。
当我们再次查询同样的数据,mybatis会先去sqlsession中查询是否有,有的话直接拿出来用当SqlSession对象消失时,mybatis的一级缓存也就消失了。
注:一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。
@Test
public void testFindById() {
//6.执行操作
User user = userDao.findById(41);
System.out.println("第一次查询的用户:"+user);
User user2 = userDao.findById(41);
System.out.println("第二次查询用户:"+user2);
System.out.println(user == user2);
}
我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是 Mybatis 提供给我们的一级缓存在起作用了。
因为一级缓存的存在, 导致第二次查询 id为41的记录时,并没有发出sql语句从数据库中查询数据,而是从一级缓存中查询。
如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
@Test
public void testClearlCache(){
//1.根据 id 查询用户
User user1 = userDao.findById(41);
System.out.println(user1);
//2.更新用户信息
user1.setUsername("update user clear cache");
user1.setAddress("北京市海淀区");
userDao.updateUser(user1);
//3.再次查询 id 为 41 的用户
User user2 = userDao.findById(41);
System.out.println(user2);
System.out.println(user1 == user2);
}
当执行sqlSession.close()后,再次获取sqlSession并查询id=41的User对象时,又重新执行了sql语句,从数据库进行了查询操作。
2.二级缓存
它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建SqlSession共享其缓存。
如果 SqlSession3 去执行相同 mapper 映射下 sql,执行 commit 提交(即执行增删改操作),将会清空该 mapper 映射下的二级缓存区域的数据。
sqlSession2 去执行查询操作,查找与 sqlSession1 相同的用户信息,首先会去缓存中找是否存在数据 如果存在直接从缓存中取出数据。
二级缓存开启与关闭
第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/> </settings>
因为cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置
为 true 代表开启二级缓存;为false 代表不开启二级缓存
第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
<cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。
<mapper namespace="com.itheima.dao.IUserDao">
<!-- 开启二级缓存的支持 -->
<cache></cache>
</mapper>
第三步:让当前的操作支持二级缓存(在select标签中配置)
<!-- 根据 id 查询 -->
<select id="findById" resultType="user" parameterType="int" useCache="true">
select * from user where id = #{uid}
</select>
将 UserDao.xml 映射文件中的<select>标签中设置 useCache=”true”代表当前这个 statement 要使用
二级缓存,如果不使用二级缓存可以设置为 false。
注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。
第四步:测试二级缓存
@Test
public void testFirstLevelCache(){
SqlSession sqlSession1 = factory.openSession();
IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = dao1.findById(41);
System.out.println(user1);
sqlSession1.close();//一级缓存消失,二级缓存仍然在factory中
SqlSession sqlSession2 = factory.openSession();
IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
User user2 = dao2.findById(41);
System.out.println(user2);
sqlSession2.close();
System.out.println(user1 == user2);
}
我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库发出 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。(factory里面数据依然存在)
三:Mybatis中的注解开发
1.环境搭建
创建SqlMapConfig.xml主配置文件,由于注解开发则不需要映射配置文件。
<configuration>
<!-- 引入外部的配置文件-->
<properties resource="jdbcConfig.properties"></properties>
<!-- 配置别名的注册 -->
<typeAliases>
<package name="com.itheima.domain"/>
</typeAliases>
<!--配置环境-->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<!-- 配置映射文件的位置 -->
<mappers>
<package name="com.itheima.dao"></package>
</mappers>
</configuration>
持久层接口使用注解
public interface IUserDao {
@Select(value = "select * from user")
List<User> findAll();
...}
然后进行典型的测试test即可
/**
* 测试查询所有
*/
@Test
public void testFindAll() {
List<User> users = userDao.findAll();
for(User user : users) {
System.out.println(user);
}
2.单表CRUD操作(代理Dao方式)
如若我们实体类的属性名和数据库表的列名不一致。则需要配置 @Results:封装多个结果集
/**
* 查询所有用户
* @return
* @Results的id为名称标记,@Result的id为是否为主键
* column为数据库列名,property为实体类属性
*/
@Select(value = "select * from user")
@Results(id = "userMap", value = {
@Result(id = true, column = "id", property = "userId"),
@Result(column = "username", property = "userName"),
@Result(column = "address", property = "userAddress"),
@Result(column = "sex", property = "userSex"),
@Result(column = "birthday", property = "userBirthday"),
}
)
List<User> findAll();
其他方法也需要引用,则直接获取@Results的id
@Select(value = "select * from user where id = #{id}")
@ResultMap(value = {"userMap"})
User findById(Integer userId);
3.多表查询操作
(1)使用注解实现一对一复杂关系映射
实现复杂关系映射之前我们可以在映射文件中通过配置< resultMap >来实现。
在使用注解开发时我们需要借助@Results 注解,@Result 注解,@One 注解,@Many 注解。
(1)说明:
@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 注解(一对一)
代替了< assocation> 标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
@One 注解属性介绍:
select 指定用的 来多表查询的 sqlmapper
fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。。
使用格式:
@Result(column=" “,property=”",one=@One(select=""))
@Many 注解(多对一)
代替了< Collection> 标签, 是是多表查询的关键,在注解中用来指定子查询返回对象集合。
注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType
**(一般为 ArrayList)**但是注解中可以不定义;
使用格式:
@Result(property="",column="",many=@Many(select=""))
(2)实体类
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
//多对一关系映射:从表方应该包含一个主表方的对象引用
private User user;
...}
(3)账户的持久层接口并使用注解配置
public interface IAccountDao {
/**
* 查询所有账户,采用延迟加载的方式查询账户的所属用户
* @return
*/
@Select("select * from account")
@Results(id="accountMap",
value= {
@Result(id=true,column="id",property="id"),
@Result(column="uid",property="uid"),
@Result(column="money",property="money"),
@Result(column="uid",
//这个数据库中的uid对应user实体类
property="user",
//对一,实现findById方法
one=@One(select="com.itheima.dao.IUserDao.findById",
fetchType=FetchType.LAZY)//并且LAZY延迟加载
)
})
List<Account> findAll();
}
(2)使用注解实现一对多复杂关系映射
例如:查询用户信息时,也要查询他的账户列表。使用注解方式实现。一个用户具有多个账户信息,所以形成了用户(User)与账户(Account)之间的一对多关系。
实体类:
public class User implements Serializable {
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;
//一对多关系映射:有多个账号
private List<Account> accounts;
持久层接口并使用注解配置
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"),
@Result(column="id",property="accounts",
//@Many,返回的是一个list集合
many=@Many(
select="com.itheima.dao.IAccountDao.findByUid",
fetchType=FetchType.LAZY
)
)
})
List<User> findAll();
}
@Many:
相当于<collection>的配置
select 属性:代表将要执行的 sql 语句(对多的语句)
fetchType 属性:代表加载方式,一般如果要延迟加载都设置为 LAZY 的值
4. 缓存的配置
前面提到了二级缓存在非注解开发中的配置步骤,现在学习在注解开发中的配置。
(1)首先在SqlMapConfig中开启二级缓存支持
<!-- 配置二级缓存 -->
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
(2)在持久层接口(IUserDao)中使用注解配置二级缓存
@CacheNamespace(blocking=true)//mybatis 基于注解方式实现配置二级缓存
public interface IUserDao {
...
}