Mybatis第四天
Mybatis 延迟加载策略
什么是延迟加载?
延迟加载就是按需加载,什么时候需要数据就什么时候将数据从数据库中加载至内存,而不是不管需不需要都将数据取出,这样一来能在一定程度上减少内存的压力。
我们在对数据库进行查询操作的时候,数据是在sql语句执行之后就被加载到内存中,如下图
在我们执行单表操作的时候,一般不需要考虑延迟加载的问题,但是当我们需要进行多表操作,比如在一个教师表中,包含一个教师的工号、姓名、年龄等基本属性,另一个班级表中则包含教师所执教的班级信息,而我们现在需要从数据库中查询所有教师的基本信息,另外根据后面的实际情况在将个别的教师所执教的班级信息查询显示出来,这时候就可以考虑用到延迟加载策略,这样就不需要在第一次执行操作的时候就将所有教师执教的班级信息全部加载进内存,能在很大的程度上减少内存的使用压力。
延迟加载的缺点
因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
使用assocation实现延迟加载
需求:查询所有账户信息,并延迟加载实现查询账户对应的用户信息
accountDao.java
/**
*
* 查询所有账户
*/
public List<Account> findAll();
AccountDao.xml
<resultMap id="accountMap" type="account">
<id column="id" property="id"></id>
<result column="uid" property="uid"></result>
<result column="money" property="money"></result>
<!-- 一对一的关系映射:配置封装user的内容
select属性指定的内容:查询用户的唯一标识:
column属性指定的内容:用户根据id查询时,所需要的参数的值
-->
<association property="user" column="uid" javaType="user" select="com.gzgs.dao.UserDao.findUserById"></association>
</resultMap>
<select id="findAll" resultMap="accountMap">
select * from account
</select>
<select id="findAccountById" resultType="account">
select * from account where uid=#{uid}
</select>
association标签属性说明
select属性指定的内容:查询用户的唯一标识:
column属性指定的内容:用户根据id查询时,所需要的参数的值
UserDao.java和UserDao.xml
/**
* 通过账户id查询用户信息
*/
public User findUserById(Integer uid);
<select id="findUserById" resultType="user" parameterType="int">
select * from user where id=#{uid}
</select>
在SqlMapConfig.xml文件中开启延迟加载
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
测试类
@Test
public void testFindAll(){
List<Account> accounts=accountDao.findAll();
for (Account account:accounts){
System.out.println(account);
System.out.println(account.getUser());
}
}
结果
可以看到由于定义了延迟加载策略,所以每次查询都访问一次数据库,这就是延迟加载的优势,但同时也增加了用户等待的时间。
使用collection实现延迟加载
需求:查询所有用户信息,并延迟加载实现查询用户所有的账户信息
UserDao.java
/**
* 查询所有的用户信息
*/
public List<User> findAll();
UserDao.xml
<resultMap id="userMap" type="com.gzgs.domain.User">
<id property="id" column="id"></id>
<result column="name" property="name"></result>
<result property="age" column="age"></result>
<result column="address" property="address"></result>
<collection property="accounts" ofType="account" column="id" select="com.gzgs.dao.AccountDao.findByUid"></collection>
</resultMap>
<select id="findAll" resultMap="userMap">
select * from user
</select>
AccountDao.java和AccountDao.xml
/**
* 通过UID查询账户信息
*/
public List<Account> findByUid(Integer uid);
<select id="findByUid" parameterType="int" resultType="account">
select * from account where uid=#{uid}
</select>
测试类
@Test
public void findUser(){
List<User> users=userDao.findAll();
for(User user:users){
System.out.println(user);
}
}
结果
Mybatis的缓存技术
Mybatis和大多数的持久层框架一样,都自带了缓存技术,并且还分为一级缓存和二级缓存,使用缓存策略可以将数据库的信息。如下图,在使用Mybatis向数据库提交一份查询数据之后,数据就会被加载进内存中,都是会存入于缓存区域。
这样一来,当第二次向数据库发起同样的请求查询这份数据的时候,Mybatis就会从缓存区域中提取这份数据,这样就不用向数据库再次请求,从而达到了减少和数据库交互的次数。
Mybatis的一级缓存技术
Mybatis的缓存分为一级缓存和二级缓存,其中一级缓存是默认开启的,在我们使用sqlSession向数据库查询一份数据之后,这份数据就会被保存在这个session中,如下:
@Test
public void findUser(){
User user1=userDao.findUserById(1);
User user2=userDao.findUserById(1);
System.out.println(user1);
System.out.println(user2);
System.out.println(user1==user2);
}
查询结果
从上图可以看出两次的查询请求,但是只像数据库发起一次申请,第二次的数据是从Mybatis的sqlSession的缓存区域中得到的,这就是Mybatis的一级缓存,但是使用缓存的一个安全问题就是:如何保证缓存区域中的数据和数据库中的数据是一样的呢?也就是在数据库第一次查询之后,假设这份数据被修改了,然后第二次查询的时候,还是不是从缓存区域取出数据呢?如下:
@Test
public void findUser(){
User user1=userDao.findUserById(1);
//关闭sqlSession
sqlSession.close();
//再次开启sqlSession
sqlSession=factory.openSession();
userDao = sqlSession.getMapper(UserDao.class);
User user2=userDao.findUserById(1);
System.out.println(user1);
System.out.println(user2);
System.out.println(user1==user2);
}
结果
由此看出,当sqlSession被关闭的时候,缓存区域的数据也会被清空。此外,当调用 SqlSession 的修改,添加,删除, commit(), close() 等方法时,也会清空一级缓存,这样就保证了数据的正确性。
Mybatis的二级缓存技术
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的,它的结构图如下:
使用Mybatis二级缓存技术的步骤
1.在 SqlMapConfig.xml 文件开启二级缓存
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为false 代表不开启二级缓存。
2.在相关的 Mapper 映射文件开启二级缓存
<mapper namespace="com.gzgs.dao.UserDao">
<!--开启二级缓存-->
<cache></cache>
<select id="findUserById" resultType="user" parameterType="int">
select * from user where id=#{uid}
</select>
</mapper>
只要在mapper中添加了< cache>标签则表示当前这个mapper映射将使用二级缓存
3.配置查询语句的 useCache 属性
<select id="findUserById" resultType="user" parameterType="int" useCache="true">
select * from user where id=#{uid}
</select>
将select标签的useCache的属性设置为true,则表示这个select将使用二级缓存,然后使用这个语句所查询出来的数据都会被存入二级缓存中。
4.测试二级缓存
@Test
public void testFirstLevelCache(){
SqlSession sqlSession1 = factory.openSession();
UserDao dao1 = sqlSession1.getMapper(UserDao.class);
User user1 = dao1.findUserById(1);
System.out.println(user1);
sqlSession1.close();//一级缓存消失
SqlSession sqlSession2 = factory.openSession();
UserDao dao2 = sqlSession2.getMapper(UserDao.class);
User user2 = dao2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
System.out.println(user1 == user2);
}
结果
这里查询的结果是false,这是因为Mybatis的二级缓存它不像一级缓存那样,一级缓存存储的是整个对象,而二级缓存存储的是数据字段,比如在上面的例子中,二级缓存存储的是id为1的user的数据信息,如{id:1,name=“zhansan”,age=18},所以比较对象是否相等的时候,结果为false。
注意:使用二级缓存的时候,所涉及到的所有的实体类都必须实现java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。
Mybatis基于注解的操作
Mybatis注解操作所涉及的常见注解
注解 | 作用 |
---|---|
@Insert | 实现新增 |
@Update | 实现更新 |
@Delete | 实现删除 |
@Select | 实现查询 |
@Result | 实现结果集封装 |
@Results | 可以与@Result 一起使用,封装多个结果集 |
@ResultMap | 实现引用@Results 定义的封装 |
@One | 实现一对一结果集封装 |
@Many | 实现一对多结果集封装 |
@SelectProvider | 实现动态 SQL 映射 |
@CacheNamespace | 实现注解二级缓存的使用 |
基于注解的单表CRUD
UserDao接口
package com.gzgs.dao;
import com.gzgs.domain.User;
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 java.util.List;
/**
* 用户持久层接口
*/
public interface UserDao {
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
public List<User> findAll();
/**
* 查询单个
*/
@Select("select * from user where id=#{id}")
public User findUserById(Integer id);
/**
* 模糊查询
*/
@Select("select * from user where name like #{name}")
public List<User> findUserByName(String name);
/***
* 增加用户
*/
@Insert("insert into user(id,name,age,address) values(#{id},#{name},#{age},#{address})")
public void insertUser(User user);
/**
* 删除用户
*/
@Delete("delete from user where id=#{id}")
public void deleteUser(Integer id);
/**
* 修改用户
*/
@Update("update user set name=#{name},age=#{age},address=#{address} where id=#{id}")
public void updateUser(User user);
}
测试类
package com.gzgs.test;
import com.gzgs.dao.UserDao;
import com.gzgs.domain.User;
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 java.io.InputStream;
import java.util.List;
public class UserTest {
private InputStream in;
private SqlSessionFactory factory;
private SqlSession session;
private UserDao userDao;
/**
* 管理配置文件,创建代理对象
* @throws Exception
*/
@Before
public void init()throws Exception{
in = Resources.getResourceAsStream("SqlMapConfig.xml");
factory = new SqlSessionFactoryBuilder().build(in);
session = factory.openSession();
userDao = session.getMapper(UserDao.class);
}
/**
* 释放资源
* @throws Exception
*/
@After
public void destroy()throws Exception{
session.commit();
session.close();
in.close();
}
/**
* 测试查询所有
*/
@Test
public void findTest(){
List<User> list=userDao.findAll();
for(User user:list){
System.out.println(user);
}
}
/**
* 查询单个
*/
@Test
public void testFindOne(){
User user =userDao.findUserById(1);
System.out.println(user);
}
/**
* 模糊查询
*/
@Test
public void testFindByName(){
List<User> list=userDao.findUserByName("%王%");
for(User user:list){
System.out.println(user);
}
}
/**
* 增加用户
*/
@Test
public void testInsert(){
User user=new User();
user.setId(5);
user.setName("王菲");
user.setAge(19);
user.setAddress("北京");
System.out.println(user);
userDao.insertUser(user);
}
/**
* 删除用户
*/
@Test
public void testDelete(){
userDao.deleteUser(5);
}
/**
* 修改用户
*/
@Test
public void testUpdate(){
User user=userDao.findUserById(5);
user.setAddress("深圳");
userDao.updateUser(user);
}
}
使用注解实现一对多复杂关系映射及延迟加载
需求:查询数据库中所有的账户信息,并且返回每个账户信息所对应的用户信
账户持久层接口
/**
* 查询所有账户
*/
@Results(id = "accountMap", value = {
@Result(id = true,property = "id",column = "id"),
@Result(property = "uid",column = "uid"),
@Result(property = "money",column = "money"),
@Result(column= "uid",property = "user",one = @One(select = "com.gzgs.dao.UserDao.findUserById",fetchType = FetchType.LAZY))
})
@Select("select * from account")
public List<Account> findAll();
测试类
@Test
public void testFindAll(){
List<Account> accounts=accountDao.findAll();
for(Account account:accounts){
System.out.println(account);
System.out.println(account.getUser());
}
}
结果
使用注解实现一对多复杂关系映射
需求:查询所有的用户信息,并查询每个用户的全部账户信息
用户持久层接口
@Select("select * from user")
@Results(id = "userMap" ,value = {
@Result(id = true,column = "id",property = "id"),
@Result(column = "name",property = "name"),
@Result(column = "age",property = "age"),
@Result(column = "address",property = "address"),
@Result(column = "id",property = "accounts",
many = @Many(select = "com.gzgs.dao.AccountDao.findByUserId",fetchType = FetchType.LAZY))
})
public List<User> findAll();
测试类
@Test
public void findTest(){
List<User> list=userDao.findAll();
for(User user:list){
System.out.println(user);
System.out.println(user.getAccounts());
}
}
结果
使用注解开启二级缓存功能
在SqlMapConfig.xml中开启支持二级缓存功能配置
<!-- 配置二级缓存 -->
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
在持久层接口开启二级缓存功能配置
@CacheNamespace(blocking=true)//mybatis 基于注解方式实现配置二级缓存
public interface IUserDao {}
本博客纯属个人学习笔记,学习资源来自黑马训练营,如有错误,感激指正