> 文章列表 > MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发

MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发

MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发

MyBatis学习总结(四) MyBatis 延迟加载策略/MyBatis 一级缓存、二级缓存/MyBatis注解开发

一、 MyBatis 延迟加载策略

通过前面的学习,我们已经掌握了 MyBatis 中一对一(多对一)、一对多、多对多关系的配置及实现,可以实现对象的关联查询。实际开发过程中很多时候我们并不需要总是将关联的对象的信息也一并加载出来,此时就是我们所说的延迟加载

举例用户和账户
一个账户只能属于一个用户(多个账户也可以属于同一个用户):一对一(多对一)
一个用户可以有多个账户:一对多

在查询用户时,可能我就只是想查询用户信息,并不关心其账户,因此用户下的账户信息应该是什么时候使用什么时候才加载出来(延迟加载)。
在查询账户时,光是查出账户信息而不知道此账户属于哪个用户会很不直观,因此账户的所属用户信息应该是随着账户查询时一起立即查询出来(立即加载)。

(一)什么是延迟加载

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

  • 好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
  • 坏处:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

在对应的四种表关系中:

  • 一对一,多对一:通常情况下我们都是采用立即加载
  • 一对多,多对多:通常情况下我们都是采用延迟加载

(二)延迟加载的实现

在实现延迟加载之前,我们必须做一些配置。

进入 MyBatis 的官方文档,找到 settings 的说明信息:

设置名 描述 默认值
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 false
aggressiveLazyLoading 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。 false (在 MyBatis 3.4.1 及之前的版本中默认为 true)

我们需要在 MyBatis 的核心配置文件中添加延迟加载的配置:

<settings> <setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="false"/>
</settings>

如果MyBatis版本是3.4.1以后就可以不添加aggressiveLazyLoading,因为默认是false。

1.一对一(多对一)实现延迟加载

案例:查询所有账户及其所属用户信息
用户表、账户表、用户实体类、账户实体类,可在前篇博客查看。

IAccountDao:

import com.fox.pojo.Account;
import java.util.List;public interface IAccountDao {//查询所有账户及其所属用户信息List<Account> findAll();
}

IAccountDao.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace对应接口的全限定类名-->
<mapper namespace="com.fox.dao.IAccountDao"><resultMap id="accountUser" type="com.fox.pojo.Account"><id property="id" column="id"></id><result property="uid" column="uid"></result><result property="money" column="money"></result><!-- 一对一的关系映射:配置封装user的内容select属性指定的内容:查询用户的唯一标识:column属性指定的内容:用户根据id查询时,指定使用哪个字段的值作为条件查询--><association property="user" column="uid" javaType="com.fox.pojo.User" select="com.fox.dao.IUserDao.findUserById"></association></resultMap><!--查询所有账户及其所属用户信息--><select id="findAll" resultMap="accountUser">select * from account;</select>
</mapper>

IUserDao:

import com.fox.pojo.User;
import java.util.List;public interface IUserDao {//根据id查询用户以及所包含的账户信息User findUserById(Integer id);
}

IUserDao.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace对应接口的全限定类名-->
<mapper namespace="com.fox.dao.IUserDao"><!--根据id查询用户以及所包含的账户信息--><select id="findUserById" parameterType="Integer" resultType="com.fox.pojo.User">select * from user where id=#{id};</select>
</mapper>

测试类:

import com.fox.dao.IAccountDao;
import com.fox.pojo.Account;
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.IOException;
import java.io.InputStream;
import java.util.List;public class AccountTest {private InputStream in = null;private SqlSession sqlSession = null;private IAccountDao accountDao = null;@Before //标记在非静态方法上。在@Test方法前面执行,而且是在每一个@Test方法前面都执行。public void init() throws IOException {//1.读取配置文件in = Resources.getResourceAsStream("SqlMapConfig.xml");//2.使用构建者创建工厂对象 SqlSessionFactorySqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);//3.使用 SqlSessionFactory 生产 SqlSession 对象sqlSession = factory.openSession();//4.使用 SqlSession 创建 dao 接口的代理对象accountDao = sqlSession.getMapper(IAccountDao.class);}@After //标记在非静态方法上。在@Test方法后面执行,而且是在每一个@Test方法后面都执行。public void destroy() throws IOException {sqlSession.commit();//释放资源sqlSession.close();in.close();}@Testpublic void testFindAll() {List<Account> accounts = accountDao.findAll();}
}

MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发
我们发现,因为本次只是将 Account对象查询出来放入 List 集合中,并没有涉及到 User对象,所以就没有发出 SQL 语句查询账户所关联的 User 对象的查询。

(三)一对多实现延迟加载

案例:查询所有用户及其所包含的账户信息
用户表、账户表、用户实体类、账户实体类,可在前篇博客查看。

IUserDao:

import com.fox.pojo.User;
import java.util.List;public interface IUserDao {//查询所有用户以及所包含的账户信息List<User> findAll();
}

IUserDao.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace对应接口的全限定类名-->
<mapper namespace="com.fox.dao.IUserDao"><!-- 定义User的resultMap--><resultMap id="userAccountMap" type="com.fox.pojo.User"><id property="id" column="id"></id><result property="username" column="username"></result><result property="birthday" column="birthday"></result><result property="sex" column="sex"></result><result property="address" column="address"></result><!--select属性指定的内容:查询账户的唯一标识:column属性指定的内容:根据用户id查询账户时,指定使用哪个字段的值作为条件查询--><collection property="accounts" ofType="com.fox.pojo.Account" column="id" select="com.fox.dao.IAccountDao.findAccountByUid"></collection></resultMap><!-- 查询所有用户以及所包含的账户信息 --><select id="findAll" resultMap="userAccountMap">select * from user;</select>
</mapper>

IAccountDao:

import com.fox.pojo.Account;public interface IAccountDao {//根据用户id查询账户及用户信息List<Account> findAccountByUid(Integer uid);
}

IAccountDao.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace对应接口的全限定类名-->
<mapper namespace="com.fox.dao.IAccountDao"><!--根据用户id查询账户及用户信息--><select id="findAccountByUid" parameterType="Integer" resultType="com.fox.pojo.Account">select * from account where uid=#{uid};</select>
</mapper>

测试类:

import com.fox.dao.IUserDao;
import com.fox.pojo.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.IOException;
import java.io.InputStream;
import java.util.List;public class UserTest {private InputStream in = null;private SqlSession sqlSession =null;private IUserDao userDao = null;@Before //标记在非静态方法上。在@Test方法前面执行,而且是在每一个@Test方法前面都执行。public void init() throws IOException {//1.读取配置文件in = Resources.getResourceAsStream("SqlMapConfig.xml");//2.使用构建者创建工厂对象 SqlSessionFactorySqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);//3.使用 SqlSessionFactory 生产 SqlSession 对象sqlSession = factory.openSession();//4.使用 SqlSession 创建 dao 接口的代理对象userDao = sqlSession.getMapper(IUserDao.class);}@After //标记在非静态方法上。在@Test方法后面执行,而且是在每一个@Test方法后面都执行。public void destroy() throws IOException {sqlSession.commit();//释放资源sqlSession.close();in.close();}@Testpublic void testFindAll(){List<User> users = userDao.findAll();}
}

MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发
同样的,我们发现并没有加载 Account 账户信息。

二、MyBatis 缓存

  • 什么是缓存?
    存在于内存中的临时数据。
  • 为什么使用缓存?
    减少和数据库的交互次数,提高执行效率。
  • 什么样的数据能使用缓存,什么样的数据不能使用?
    • 适用于缓存:
      (1)经常查询并且不经常改变的。
      (2)数据的正确与否对最终结果影响不大的。
    • 不适用于缓存:
      (1)经常改变的数据
      (2)数据的正确与否对最终结果影响很大的。
      例如:商品的库存,银行的汇率,股市的牌价。

像大多数的持久化框架一样,MyBatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。

MyBatis 中缓存分为一级缓存,二级缓存。

MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发

(一)MyBatis一级缓存

1.证明一级缓存的存在

User实体类:

import java.io.Serializable;
import java.util.Date;public class User implements Serializable {private Integer id;private String username;private Date birthday;private String sex;private String address;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;}}

IUserDao:

import com.fox.pojo.User;public interface IUserDao {//根据id查询用户以及所包含的账户信息User findUserById(Integer id);
}

IUserDao.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace对应接口的全限定类名-->
<mapper namespace="com.fox.dao.IUserDao"><!--根据id查询用户--><select id="findUserById" parameterType="Integer" resultType="com.fox.pojo.User">select * from user where id=#{id};</select>
</mapper>

测试类:

import com.fox.dao.IUserDao;
import com.fox.pojo.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.IOException;
import java.io.InputStream;public class UserTest {private InputStream in = null;private SqlSession sqlSession =null;private IUserDao userDao = null;@Before //标记在非静态方法上。在@Test方法前面执行,而且是在每一个@Test方法前面都执行。public void init() throws IOException {//1.读取配置文件in = Resources.getResourceAsStream("SqlMapConfig.xml");//2.使用构建者创建工厂对象 SqlSessionFactorySqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);//3.使用 SqlSessionFactory 生产 SqlSession 对象sqlSession = factory.openSession();//4.使用 SqlSession 创建 dao 接口的代理对象userDao = sqlSession.getMapper(IUserDao.class);}@After //标记在非静态方法上。在@Test方法后面执行,而且是在每一个@Test方法后面都执行。public void destroy() throws IOException {sqlSession.commit();//释放资源sqlSession.close();in.close();}@Testpublic void testFindAll(){User user1 = userDao.findUserById(41);System.out.println(user1);User user2 = userDao.findUserById(41);System.out.println(user2);System.out.println(user1==user2);}
}

MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发
我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是 MyBatis 提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询 id 为 41 的记录时,并没有发出 sql 语句从数据库中查询数据,而是从一级缓存中查询。

2.一级缓存的分析

  • 一级缓存指的是MyBatis中SqlSession对象的缓存。
  • 当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供的一块区域中,该区域的结构是一个Map。
  • 当我们再次查询同样的数据,MyBatis会先去SqlSession中查询是否有,有的话直接拿出来用。当SqlSession对象消失时,MyBatis的一级缓存也就消失了。
  • 一级缓存是 SqlSession 范围的缓存,当调用修改、添加、删除、sqlSession.commit()sqlSession.close()等方法时,就会清空一级缓存。

MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发

  • 第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。
  • 得到用户信息,将用户信息存储到一级缓存中。
  • 如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
  • 第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。

(1)测试一级缓存的清空

①sqlSession.close();
import com.fox.dao.IUserDao;
import com.fox.pojo.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.IOException;
import java.io.InputStream;public class UserTest {private InputStream in = null;private SqlSession sqlSession =null;private IUserDao userDao = null;private SqlSessionFactory factory =null;@Before //标记在非静态方法上。在@Test方法前面执行,而且是在每一个@Test方法前面都执行。public void init() throws IOException {//1.读取配置文件in = Resources.getResourceAsStream("SqlMapConfig.xml");//2.使用构建者创建工厂对象 SqlSessionFactoryfactory = new SqlSessionFactoryBuilder().build(in);//3.使用 SqlSessionFactory 生产 SqlSession 对象sqlSession = factory.openSession();//4.使用 SqlSession 创建 dao 接口的代理对象userDao = sqlSession.getMapper(IUserDao.class);}@After //标记在非静态方法上。在@Test方法后面执行,而且是在每一个@Test方法后面都执行。public void destroy() throws IOException {sqlSession.commit();//释放资源sqlSession.close();in.close();}@Testpublic void testFindAll(){User user1 = userDao.findUserById(41);System.out.println(user1);sqlSession.close();//再次获取SqlSession对象sqlSession = factory.openSession();userDao = sqlSession.getMapper(IUserDao.class);User user2 = userDao.findUserById(41);System.out.println(user2);System.out.println(user1==user2);}
}

MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发

②sqlSession.clearCache();
import com.fox.dao.IUserDao;
import com.fox.pojo.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.IOException;
import java.io.InputStream;public class UserTest {private InputStream in = null;private SqlSession sqlSession =null;private IUserDao userDao = null;@Before //标记在非静态方法上。在@Test方法前面执行,而且是在每一个@Test方法前面都执行。public void init() throws IOException {//1.读取配置文件in = Resources.getResourceAsStream("SqlMapConfig.xml");//2.使用构建者创建工厂对象 SqlSessionFactorySqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);//3.使用 SqlSessionFactory 生产 SqlSession 对象sqlSession = factory.openSession();//4.使用 SqlSession 创建 dao 接口的代理对象userDao = sqlSession.getMapper(IUserDao.class);}@After //标记在非静态方法上。在@Test方法后面执行,而且是在每一个@Test方法后面都执行。public void destroy() throws IOException {sqlSession.commit();//释放资源sqlSession.close();in.close();}@Testpublic void testFindAll(){User user1 = userDao.findUserById(41);System.out.println(user1);sqlSession.clearCache();User user2 = userDao.findUserById(41);System.out.println(user2);System.out.println(user1==user2);}
}

MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发

③sqlSession.commit();(增删改)

IUserDao:

import com.fox.pojo.User;public interface IUserDao {//根据id查询用户以及所包含的账户信息User findUserById(Integer id);//更新用户void updateUser(User user);
}

IUserDao.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace对应接口的全限定类名-->
<mapper namespace="com.fox.dao.IUserDao"><!--根据id查询用户--><select id="findUserById" parameterType="Integer" resultType="com.fox.pojo.User">select * from user where id=#{id};</select><update id="updateUser" parameterType="com.fox.pojo.User">update user set username=#{username},address=#{address} where id=#{id};</update>
</mapper>

测试类:

import com.fox.dao.IUserDao;
import com.fox.pojo.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.IOException;
import java.io.InputStream;public class UserTest {private InputStream in = null;private SqlSession sqlSession =null;private IUserDao userDao = null;@Before //标记在非静态方法上。在@Test方法前面执行,而且是在每一个@Test方法前面都执行。public void init() throws IOException {//1.读取配置文件in = Resources.getResourceAsStream("SqlMapConfig.xml");//2.使用构建者创建工厂对象 SqlSessionFactorySqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);//3.使用 SqlSessionFactory 生产 SqlSession 对象sqlSession = factory.openSession();//4.使用 SqlSession 创建 dao 接口的代理对象userDao = sqlSession.getMapper(IUserDao.class);}@After //标记在非静态方法上。在@Test方法后面执行,而且是在每一个@Test方法后面都执行。public void destroy() throws IOException {sqlSession.commit();//释放资源sqlSession.close();in.close();}@Testpublic void testFindAll(){//根据id查询用户User user1 = userDao.findUserById(41);System.out.println(user1);//更新用户信息user1.setUsername("小何");user1.setAddress("广东");userDao.updateUser(user1);//再次查询id为41的用户User user2 = userDao.findUserById(41);System.out.println(user2);System.out.println(user1==user2);}
}

MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发

(二)MyBatis二级缓存

  • 二级缓存是 Mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
  • 二级缓存实际上指的是MyBatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
  • 二级缓存的使用步骤:
    第一步:让MyBatis框架支持二级缓存(在MyBatis核心配置文件中配置)
    第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
    第三步:让当前的操作支持二级缓存(在select标签中配置)

案例
让MyBatis框架支持二级缓存(在MyBatis核心配置文件中配置):

<settings><!-- 开启二级缓存的支持 --><setting name="cacheEnabled" value="true"/>
</settings>

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

让当前的映射文件支持二级缓存<cache></cache>(在IUserDao.xml中配置)并且让当前的操作支持二级缓存(在select标签中配置)useCache="true"

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace对应接口的全限定类名-->
<mapper namespace="com.fox.dao.IUserDao"><!-- 开启二级缓存的支持 --><cache></cache><!--根据id查询用户--><select id="findUserById" parameterType="Integer" resultType="com.fox.pojo.User" useCache="true">select * from user where id=#{id};</select></mapper>

注意:针对每次查询都需要最新的数据的 sql,要设置成 useCache="false",不要用二级缓存。

测试类:

import com.fox.dao.IUserDao;
import com.fox.pojo.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.IOException;
import java.io.InputStream;public class UserTest {private InputStream in = null;private SqlSessionFactory factory =null;@Before //标记在非静态方法上。在@Test方法前面执行,而且是在每一个@Test方法前面都执行。public void init() throws IOException {//1.读取配置文件in = Resources.getResourceAsStream("SqlMapConfig.xml");//2.使用构建者创建工厂对象 SqlSessionFactoryfactory = new SqlSessionFactoryBuilder().build(in);}@After //标记在非静态方法上。在@Test方法后面执行,而且是在每一个@Test方法后面都执行。public void destroy() throws IOException {in.close();}@Testpublic void testFindAll(){SqlSession sqlSession1 = factory.openSession();IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);User user1 = dao1.findUserById(41);System.out.println(user1);sqlSession1.close();//一级缓存消失,不让一级缓存干扰二级缓存的结果SqlSession sqlSession2 = factory.openSession();IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);User user2 = dao2.findUserById(41);System.out.println(user2);sqlSession2.close();//一级缓存消失,不让一级缓存干扰二级缓存的结果System.out.println(user1 == user2);}
}

MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发
经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库再发出第二次的 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。但是为什么还是false呢,这是因为在二级缓存中存放的内容是数据而不是对象。它会重新创建一个User对象,将缓存的数据再放入这个新User对象,因此user1!=user2
MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发
注意:当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。

三、MyBatis注解开发

MyBatis注解开发是用注解代替了映射的xml配置文件,但是MyBatis核心配置文件还是需要的。
注意:使用了MyBatis注解开发就不能同时写xml映射文件,不然会报错。

(一)注解实现基本 CRUD

案例
User类:

import java.io.Serializable;
import java.util.Date;public class User implements Serializable {private Integer id;private String username;private Date birthday;private String sex;private String address;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;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\\'' +", birthday=" + birthday +", sex='" + sex + '\\'' +", address='" + address + '\\'' +'}';}
}

MyBatis核心配置文件:
由于注解开发替代了Mapper映射文件,所以本例无Mapper映射文件,因此映射器的写法也有变动:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- mybatis的主配置文件 -->
<configuration><properties resource="jdbc.properties"></properties><!--使用typeAliases配置别名 --><typeAliases><!--typeAlias用于配置别名。type属性指定的是实体类全限定类名。alias属性指定别名--><!-- 用于指定要配置别名的包,当指定之后,该包下的所有实体类都会注册别名,并且类名就是别名,不再区分大小写--><package name="com.fox.pojo"></package></typeAliases>
<!-- 配置环境 --><environments default="mysql"><!-- 配置mysql的环境--><environment id="mysql"><!-- 配置事务的类型--><transactionManager type="JDBC"></transactionManager><!-- 配置数据源(连接池) --><dataSource type="POOLED"><!-- 配置连接数据库的4个基本信息 --><property name="driver" value="${jdbc.driver}"/><!--xml中不允许&符号直接出现,我们需要使用 &amp; 代替--><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 --><mappers><package name="com.fox.dao"/><!--或者<mapper class="com.fox.dao.IUserDao"/>--></mappers>
</configuration>

IUserDao:
注意:传入单个参数可以省略@Param ,传入多个参数必须使用@Param并用逗号隔开

import com.fox.pojo.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 IUserDao {//查询所有用户@Select("select * from user")List<User> findAll();//根据id查询用户@Select("select * from user where id=#{id}")User findUserById(Integer id);//根据用户名模糊查询用户@Select("select * from user where username like #{username}")List<User> findUserByName(String name);//根据id和用户名查询用户@Select("select * from user where id=#{id} and username=#{name}")User findUserByTwo(@Param("id") Integer id,@Param("name") String name);//查询总用户数量@Select("select count(*) from user")int findTotal();//添加用户@Insert("insert into user(username,birthday,address,sex) values(#{username},#{birthday},#{address},#{sex})")void addUser(User user);//更新用户@Update("update user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address} where id=#{id}")void updateUser(User user);//删除用户@Delete("delete from user where id=#{id}")void deleteUserById(Integer id);
}

测试类:

import com.fox.dao.IUserDao;
import com.fox.pojo.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.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;public class UserTest {private InputStream in = null;private SqlSession sqlSession =null;private IUserDao userDao = null;@Before //标记在非静态方法上。在@Test方法前面执行,而且是在每一个@Test方法前面都执行。public void init() throws IOException {//1.读取配置文件in = Resources.getResourceAsStream("SqlMapConfig.xml");//2.使用构建者创建工厂对象 SqlSessionFactorySqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);//3.使用 SqlSessionFactory 生产 SqlSession 对象sqlSession = factory.openSession();//4.使用 SqlSession 创建 dao 接口的代理对象userDao = sqlSession.getMapper(IUserDao.class);}@After //标记在非静态方法上。在@Test方法后面执行,而且是在每一个@Test方法后面都执行。public void destroy() throws IOException {//提交事务sqlSession.commit();//释放资源sqlSession.close();in.close();}//查询所有@Testpublic void testFindAll(){List<User> users = userDao.findAll();for (User user : users) {System.out.println(user);}}//根据id查询@Testpublic void testFindUserById(){User user = userDao.findUserById(41);System.out.println(user);}//根据用户名模糊查询@Testpublic void testFindUserByName(){List<User> users = userDao.findUserByName("%王%");for (User user : users) {System.out.println(user);}}//根据id和用户名查询用户@Testpublic void testFindUserByTwo(){User user = userDao.findUserByTwo(41, "小何");System.out.println(user);}//查询总用户数@Testpublic void testFindTotal(){int total = userDao.findTotal();System.out.println("共有"+total+"个用户");}//添加用户@Testpublic void testAddUser(){User user = new User();user.setUsername("小张");user.setAddress("河南");user.setBirthday(new Date());user.setSex("女");userDao.addUser(user);}//更新用户@Testpublic void testUpdateUser(){User user = new User();user.setId(46);user.setUsername("小杰");user.setAddress("天津");user.setBirthday(new Date());user.setSex("男");userDao.updateUser(user);}//删除用户@Testpublic void testDeleteUser(){userDao.deleteUserById(49);}
}

(二)注解建立实体类属性和数据表中列的对应关系

假如数据表中的列名是这样:
MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发

User类是这样:

import java.io.Serializable;
import java.util.Date;public class User implements Serializable {private Integer userId;private String userName;private Date userBirthday;private String userSex;private String userAddress;public Integer getUserId() {return userId;}public void setUserId(Integer userId) {this.userId = userId;}public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public Date getUserBirthday() {return userBirthday;}public void setUserBirthday(Date userBirthday) {this.userBirthday = userBirthday;}public String getUserSex() {return userSex;}public void setUserSex(String userSex) {this.userSex = userSex;}public String getUserAddress() {return userAddress;}public void setUserAddress(String userAddress) {this.userAddress = userAddress;}@Overridepublic String toString() {return "User{" +"userId=" + userId +", userName='" + userName + '\\'' +", userBirthday=" + userBirthday +", userSex='" + userSex + '\\'' +", userAddress='" + userAddress + '\\'' +'}';}
}

属性名和列名不对应,之前我们可以在映射文件中通过配置<resultMap>来实现,在使用注解开发时我们需要借助 @Results 注解、@Result 注解。

  • @Results 注解:
    代替的是标签<resultMap>
    • @Results 中 属性介绍:
      • id:此resultMap的唯一标识
      • value:@Result集合
    • 当只有单个@Result,可以省略大括号。
      @Results({@Result(),@Result()})@Results(@Result())都是正确的。
  • @Result 注解:
    代替了<id>标签和<result>标签
    • @Result 中 属性介绍:
      • id:是否是主键字段
      • column:数据库的列名
      • property:需要装配的属性名
      • one:需要使用的@One 注解(@Result(one=@One())
      • many:需要使用的@Many 注解(@Result(many=@many())
  • @ResultMap 注解
    当这段@Results代码需要在多个方法用到时,为了提高代码复用性,我们可以使用@ResultMap注解来复用这段代码,@ResultMap注解的value就是这个@Results注解的id值。

案例

import com.fox.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;public interface IUserDao {//查询所有用户@Select("select * from user")@Results(id = "userMap",value = {@Result(id = true,property = "userId",column = "id"),@Result(property = "userName",column = "username"),@Result(property = "userBirthday",column = "birthday"),@Result(property = "userAddress",column = "address"),@Result(property = "userSex",column = "sex")})List<User> findAll();//根据id查询用户@Select("select * from user where id=#{id}")@ResultMap("userMap")User findUserById(Integer id);//根据用户名模糊查询用户@Select("select * from user where username like #{username}")@ResultMap("userMap")List<User> findUserByName(String name);
}

(三)注解实现复杂关系映射及延迟加载

  • @One 注解

    (多对一、一对一)

    代替了

    <assocation>
    

    标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。

    • @One 注解属性介绍:
      • select:指定用来多表查询的 sql 映射
      • fetchType:代表加载方式,会覆盖全局的配置参数 lazyLoadingEnabled。取值有:FetchType.LAZY(延迟加载)、FetchType.EAGER(立即加载)、FetchType.DEFAULT(默认)
    • 使用格式:@Result(property="",column="",one=@One(select="",fetchType=))
  • @Many 注解

    (一对多)

    代替了

    <Collection>
    

    标签,是多表查询的关键,在注解中用来指定子查询返回对象集合。

    • @Many注解属性介绍:
      • select :指定用来多表查询的 sql 映射
      • fetchType :代表加载方式,会覆盖全局的配置参数 lazyLoadingEnabled。取值有:FetchType.LAZY(延迟加载)、FetchType.EAGER(立即加载)、FetchType.DEFAULT(默认)
    • 使用格式:@Result(property="",column="",many=@Many(select="",fetchType=))

注意:xml方式需要指定映射的 Java 实体类的属性 ofType或 javaType,但是注解中可以不定义。

1.注解实现一对一复杂关系映射及延迟加载

案例:一个账户对应一个用户
Account类:

import java.io.Serializable;public class Account implements Serializable {private Integer id;private Integer uid;private Double money;//一对一(多对一)的映射:从表实体应该包含一个主表实体的对象引用private User user;public User getUser() {return user;}public void setUser(User user) {this.user = user;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public Integer getUid() {return uid;}public void setUid(Integer uid) {this.uid = uid;}public Double getMoney() {return money;}public void setMoney(Double money) {this.money = money;}@Overridepublic String toString() {return "Account{" +"id=" + id +", uid=" + uid +", money=" + money +'}';}
}

User类:

import java.io.Serializable;
import java.util.Date;public class User implements Serializable {private Integer id;private String username;private Date birthday;private String sex;private String address;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;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\\'' +", birthday=" + birthday +", sex='" + sex + '\\'' +", address='" + address + '\\'' +'}';}
}

IAccountDao:

import com.fox.pojo.Account;
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 java.util.List;public interface IAccountDao {//查询所有账户,并且获取每个账户所属的用户信息@Select("select * from account")@Results(id = "accountMap",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 = "com.fox.dao.IUserDao.findUserById",fetchType = FetchType.EAGER))})List<Account> findAll();
}

IUserDao:

import com.fox.pojo.User;
import org.apache.ibatis.annotations.*;public interface IUserDao {//根据id查询用户@Select("select * from user where id=#{id}")User findUserById(Integer id);
}

2.注解实现一对多复杂关系映射及延迟加载

案例:一个用户对应多个账户
User类:

import java.io.Serializable;
import java.util.Date;public class User implements Serializable {private Integer id;private String username;private Date birthday;private String sex;private String address;//一对多关系映射:一个用户对应多个账户private List<Account> accounts;public List<Account> getAccounts() {return accounts;}public void setAccounts(List<Account> 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;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\\'' +", birthday=" + birthday +", sex='" + sex + '\\'' +", address='" + address + '\\'' +'}';}
}

Account类:

import java.io.Serializable;public class Account implements Serializable {private Integer id;private Integer uid;private Double money;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public Integer getUid() {return uid;}public void setUid(Integer uid) {this.uid = uid;}public Double getMoney() {return money;}public void setMoney(Double money) {this.money = money;}@Overridepublic String toString() {return "Account{" +"id=" + id +", uid=" + uid +", money=" + money +'}';}
}

IUserDao:

import com.fox.pojo.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;
import java.util.List;public interface IUserDao {//查询所有用户@Select("select * from user")@Results(id = "userMap",value = {@Result(id = true,property = "id",column = "id"),@Result(property = "username",column = "username"),@Result(property = "birthday",column = "birthday"),@Result(property = "address",column = "address"),@Result(property = "sex",column = "sex"),@Result(property = "accounts",column = "id",many = @Many(select = "com.fox.dao.IAccountDao.findAccountByUid",fetchType = FetchType.LAZY))})List<User> findAll();
}

IAccountDao:

import com.fox.pojo.Account;
import org.apache.ibatis.annotations.Select;
import java.util.List;public interface IAccountDao {//根据用户id查询账户信息@Select("select * from account where uid=#{uid}")List<Account> findAccountByUid(Integer uid);
}

(三)注解开发使用二级缓存

步骤

  1. 在 MyBatis核心配置文件中开启二级缓存支持:
<!-- 配置二级缓存 --> 
<settings><!-- 开启二级缓存的支持 --> <setting name="cacheEnabled" value="true"/>
</settings>
  1. 在持久层接口中使用注解配置二级缓存:
@CacheNamespace(blocking=true)//mybatis 基于注解方式实现配置二级缓存
public interface IUserDao {//查询所有用户@Select("select * from user")@Results(id = "userMap",value = {@Result(id = true,property = "id",column = "id"),@Result(property = "username",column = "username"),@Result(property = "birthday",column = "birthday"),@Result(property = "address",column = "address"),@Result(property = "sex",column = "sex"),@Result(property = "accounts",column = "id",many = @Many(select = "com.fox.dao.IAccountDao.findAccountByUid",fetchType = FetchType.LAZY))})List<User> findAll();//根据id查询用户@Select("select * from user where id=#{id}")@ResultMap("userMap")User findUserById(Integer id);
}
  1. 测试:
import com.fox.dao.IUserDao;
import com.fox.pojo.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.IOException;
import java.io.InputStream;public class UserTest {private InputStream in = null;private SqlSessionFactory factory =null;@Before //标记在非静态方法上。在@Test方法前面执行,而且是在每一个@Test方法前面都执行。public void init() throws IOException {//1.读取配置文件in = Resources.getResourceAsStream("SqlMapConfig.xml");//2.使用构建者创建工厂对象 SqlSessionFactoryfactory = new SqlSessionFactoryBuilder().build(in);}@After //标记在非静态方法上。在@Test方法后面执行,而且是在每一个@Test方法后面都执行。public void destroy() throws IOException {in.close();}//查询所有@Testpublic void testFindAll(){SqlSession sqlSession1=factory.openSession();IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);User user1 = userDao1.findUserById(41);System.out.println(user1);sqlSession1.close();SqlSession sqlSession2=factory.openSession();IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class);User user2 = userDao2.findUserById(41);System.out.println(user2);sqlSession2.close();System.out.println(user1==user2);}
}

MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发
我们发现sql语句只执行了一次,且一级缓存已经关闭,证明使用了二级缓存。