mybatis第四天

1、Mybatis中的延迟加载

问题:在一对多中,当我们有一个用户,它有100个账户。
      在查询用户的时候,要不要把关联的账户查出来?
      在查询账户的时候,要不要把关联的用户查出来?
    
      在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询的。
      在查询账户时,账户的所属用户信息应该是随着账户查询时一起查询出来。

什么是延迟加载
    在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)
什么是立即加载
    不管用不用,只要一调用方法,马上发起查询。

在对应的四种表关系中:一对多,多对一,一对一,多对多
    一对多,多对多:通常情况下我们都是采用延迟加载。
    多对一,一对一:通常情况下我们都是采用立即加载。

2、Mybatis中的缓存

什么是缓存
    存在于内存中的临时数据。
为什么使用缓存
    减少和数据库的交互次数,提高执行效率。
什么样的数据能使用缓存,什么样的数据不能使用
    适用于缓存:
        经常查询并且不经常改变的。
        数据的正确与否对最终结果影响不大的。
    不适用于缓存:
        经常改变的数据
        数据的正确与否对最终结果影响很大的。
        例如:商品的库存,银行的汇率,股市的牌价。
Mybatis中的一级缓存和二级缓存
    一级缓存:
        它指的是Mybatis中SqlSession对象的缓存。
        当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。
        该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中
        查询是否有,有的话直接拿出来用。
        当SqlSession对象消失时,mybatis的一级缓存也就消失了。
    
    二级缓存:
        它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
        二级缓存的使用步骤:
            第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
            第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
            第三步:让当前的操作支持二级缓存(在select标签中配置)

3、Mybatis中的注解开发

环境搭建
单表CRUD操作(代理Dao方式)
多表查询操作
缓存的配置

1延迟加载的概念

为什么要延迟加载

延迟加载:

就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.

好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速

度要快。

坏处:

因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗

时间,所以可能造成用户等待时间变长,造成用户体验下降。

实现需求

需求:

查询账户(Account)信息并且关联查询用户(User)信息。如果先查询账户(Account)信息即可满足要

求,当我们需要查询用户(User)信息时再查询用户(User)信息。把对用户(User)信息的按需去查询就是延迟加

载。

mybatis第三天实现多表操作时,我们使用了resultMap来实现一对一,一对多,多对多关系的操作。主要

是通过 association、collection 实现一对一及一对多映射。association、collection 具备延迟加载功

能。

通过assocation实现延迟加载

1.3.1 账户的持久层 DAO 接口

public interface IAccountDao {

/**

* 查询所有账户,同时获取账户的所属用户名称以及它的地址信息

* @return

*/

List findAll();

}

1.3.2 账户的持久层映射文件

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

select="com.itheima.dao.IUserDao.findById"

column="uid">

select: 填写我们要调用的 select 映射的 id

column : 填写我们要传递给 select 映射的参数

1.3.3 用户的持久层接口和映射文件

*

Title: IUserDao

*

Description: 用户的业务层接口

*

Company: http://www.itheima.com/

*/

public interface IUserDao {

/**

* 根据 id 查询

* @param userId

* @return

*/

User findById(Integer userId);

}

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

1.3.4 开启 Mybatis 的延迟加载策略

mybatis第四天_第1张图片

1.3.5 编写测试只查账户信息不查用户信息

/**

*

*

Title: MybastisCRUDTest

*

Description: 一对多账户的操作

*

Company: http://www.itheima.com/

*/

public class AccountTest {

private InputStream in ;

private SqlSessionFactory factory;

private SqlSession session;

private IAccountDao accountDao;

@Test

public void testFindAll() {

//6.执行操作

List accounts = accountDao.findAll();

}

@Before//在测试方法执行之前执行

public void init()throws Exception {

//1.读取配置文件

in = Resources.getResourceAsStream("SqlMapConfig.xml");

//2.创建构建者对象

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

//3.创建 SqlSession 工厂对象

factory = builder.build(in);

//4.创建 SqlSession 对象

session = factory.openSession();

//5.创建 Dao 的代理对象

accountDao = session.getMapper(IAccountDao.class);

}

@After//在测试方法执行完成之后执行

public void destroy() throws Exception{

//7.释放资源

session.close();
in.close();

}

}

测试结果如下:

我们发现,因为本次只是将 Account对象查询出来放入 List 集合中,并没有涉及到 User对象,所以就没有
mybatis第四天_第2张图片
发出 SQL 语句查询账户所关联的 User 对象的查询。

通过collection实现延迟加载

同样我们也可以在一对多关系配置的结点中配置延迟加载策略。

结点中也有 select 属性,column 属性。

需求:

完成加载用户对象时,查询该用户所拥有的账户信息。

1.4.1 在 User 实体类中加入 List属性

/**

*

*

Title: User

*

Description: 用户的实体类

*

Company: http://www.itheima.com/

*/

public class User implements Serializable {

private Integer id;

private String username;

private Date birthday;

private String sex;

private String address;

private List accounts;

public List getAccounts() {

return accounts;

}

public void setAccounts(List accounts) {

this.accounts = accounts;

}

public Integer getId() {

return id;

}

public void setId(Integer id) {

this.id = id;

}

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

public Date getBirthday() {

return birthday;

}

public void setBirthday(Date birthday) {

this.birthday = birthday;

}

public String getSex() {

return sex;

}

public void setSex(String sex) {

this.sex = sex;

}

public String getAddress() {

return address;

}

public void setAddress(String address) {

this.address = address;

}

@Override

public String toString() {

return "User [id=" + id + ", username=" + username + ", birthday=" + birthday

+ ", sex=" + sex + ", address="

+ address + "]";

}

}

1.4.2 编写用户和账户持久层接口的方法

/**

* 查询所有用户,同时获取出每个用户下的所有账户信息传智播客——专注于 Java、.Net 和 Php、网页平面设计工程师的培训

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090

* @return

*/

List findAll();

/**

* 根据用户 id 查询账户信息

* @param uid

* @return

*/

List findByUid(Integer uid);

1.4.3 编写用户持久层映射配置

select="com.itheima.dao.IAccountDao.findByUid"

column="id">

标签:

主要用于加载关联的集合对象

select 属性:

用于指定查询 account 列表的 sql 语句,所以填写的是该 sql 映射的 id

column 属性:

用于指定 select 属性的 sql 语句的参数来源,上面的参数来自于 user 的 id 列,所以就写成 id 这一

个字段名了

缓存

mybatis第四天_第3张图片

一级缓存

一级缓存的存在

一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。

一级缓存的测试

编写用户持久层Dao接口

/**

*

*

Title: IUserDao

传智播客——专注于 Java、.Net 和 Php、网页平面设计工程师的培训

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090

*

Description: 用户的业务层接口

*

Company: http://www.itheima.com/

*/

public interface IUserDao {

/**

* 根据 id 查询

* @param userId

* @return

*/

User findById(Integer userId);

}

编写用户持久层映射文件

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

编写测试方法

/**

*

*

Title: MybastisCRUDTest

*

Description: 一对多的操作

*

Company: http://www.itheima.com/

*/

public class UserTest {

private InputStream in ;

private SqlSessionFactory factory;

private SqlSession session;

private IUserDao userDao;

@Test

public void testFindById() {

//6.执行操作传智播客——专注于 Java、.Net 和 Php、网页平面设计工程师的培训

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090

User user = userDao.findById(41);

System.out.println("第一次查询的用户:"+user);

User user2 = userDao.findById(41);

System.out.println("第二次查询用户:"+user2);

System.out.println(user == user2);

}

@Before//在测试方法执行之前执行

public void init()throws Exception {

//1.读取配置文件

in = Resources.getResourceAsStream("SqlMapConfig.xml");

//2.创建构建者对象

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

//3.创建 SqlSession 工厂对象

factory = builder.build(in);

//4.创建 SqlSession 对象

session = factory.openSession();

//5.创建 Dao 的代理对象

userDao = session.getMapper(IUserDao.class);

}

@After//在测试方法执行完成之后执行

public void destroy() throws Exception{

//7.释放资源

session.close();

in.close();

}

}

测试结果如下:
mybatis第四天_第4张图片
我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是 Mybatis 提

供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询 id 为 41 的记录时,并没有发出 sql 语句

从数据库中查询数据,而是从一级缓存中查询。

一级缓存分析

一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等传智播客——专注于 Java、.Net 和 Php、网页平面设计工程师的培训

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090

方法时,就会清空一级缓存。
mybatis第四天_第5张图片
第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查

询用户信息。

得到用户信息,将用户信息存储到一级缓存中。

如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样

做的目的为了让缓存中存储的是最新的信息,避免脏读。

第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存

中获取用户信息。

一级缓存的清空

/**

* 测试一级缓存

*/

@Test

public void testFirstLevelCache(){

User user1 = userDao.findById(41);

System.out.println(user1);

// sqlSession.close();

//再次获取 SqlSession 对象

// sqlSession = factory.openSession();

sqlSession.clearCache();//此方法也可以清空缓存

userDao = sqlSession.getMapper(IUserDao.class);

User user2 = userDao.findById(41);

System.out.println(user2);

System.out.println(user1 == user2);

}

/**传智播客——专注于 Java、.Net 和 Php、网页平面设计工程师的培训

* 测试缓存的同步

*/

@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

语句,从数据库进行了查询操作。

二级缓存

二级缓存的结构图

mybatis第四天_第6张图片
首先开启 mybatis 的二级缓存。

sqlSession1 去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090传智播客——专注于 Java、.Net 和 Php、网页平面设计工程师的培训

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090

如果 SqlSession3 去执行相同 mapper 映射下 sql,执行 commit 提交,将会清空该 mapper 映射下的二

级缓存区域的数据。

sqlSession2 去查询与 sqlSession1 相同的用户信息,首先会去缓存中找是否存在数据,如果存在直接从

缓存中取出数据。

二级缓存的开启和关闭

在sqlMapConfig.xml文件中配置开启二级缓存

因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为

false 代表不开启二级缓存。

配置相关Mapper映射文件

标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

配置statement的userCache属性

将 UserDao.xml 映射文件中的