实战项目完成代码:MybatisTest_GitHub
上一章:(二)Mybatis增删查改、动态SQL语句、多表操作(简单易懂,图文教学,整套实战)
1.延迟加载
1.1 什么是延迟加载?
需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
个人理解:就是把查询分成两次,例如我们在上章中讲一对一查询,查询账户和用户,我们是一次性完成的,延迟加载就是第一次只查询账户,如果用到用户,再查询用户。
使用要点:
- 一对一通常使用立即加载
- 一对多通常使用延迟加载
1.2 准备工作
- 创建子工程day3
- 将day2中的资源和代码都复制过来
1.3 association的延迟加载
- 向com.lois.dao.UserDao接口类中添加方法findById
public User findById(int id);
- 向com/lois/dao/AccountDao.xml中添加映射
<select id="findAll" resultMap="accountMap">
select * from account;
</select>
- 向测试类中添加方法进行测试
@Test
public void findByIdUser(){
System.out.println(userDao.findById(1));
}
- 修改com/lois/dao/AccountDao.xml映射文件
<?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.lois.dao.AccountDao">
<resultMap id="accountMap" type="account">
<id column="id" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<!--映射单个对象
property:在account实体类中的变量名
javaType:实体类类型,应该写全类名,但是取过别名
column:查询出来的表的字段名,该列作为传入select方法中的变量
select:调用的查询方法,封装成javaType类
-->
<association property="user" javaType="user" column="uid" select="com.lois.dao.UserDao.findById"/>
</resultMap>
<select id="findAll" resultMap="accountMap">
select * from account;
</select>
</mapper>
- 修改测试类方法findAllAccount,并测试运行
@Test
public void findAllAccount(){
List<Account> all = accountDao.findAll();
for (Account account :
all) {
System.out.println("---------------------");
System.out.println(account);
}
}
我们发现查询确实分成了三次,但是并不是在我们使用的时候才查询的,还是一次性查询出来的。
- 这是因为我们没有打开延迟加载,查看mybatis文档后知道要在SqlMapConfig.xml进行设置,放在configuration标签内,但是不能放在第一个
<settings>
<!--开启延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
- 再次运行测试类findAllAccount
我们发现他是在使用的时候才进行第二次查询,且查询过的对象不会再次发起查询(第三次输出没有查询)
1.4 collection的延迟加载
- 修改com/lois/dao/UserDao.xml映射文件
<?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.lois.dao.UserDao">
<resultMap id="userMap" type="user">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<!--collection 是用于建立一对多中集合属性的对应关系
ofType 用于指定集合元素的数据类型
此处的column、select和association中的意义一样-->
<collection property="accounts" ofType="account" column="id" select="com.lois.dao.AccountDao.findByUid"/>
</resultMap>
<select id="findAll" resultMap="userMap">
select * from user
</select>
<select id="findById" resultType="user" parameterType="int">
select * from user where id = #{id}
</select>
</mapper>
我们发现在AccountDao中没有findByUid,那么我们写一个
- 向com.lois.dao.AccountDao接口类中添加方法
public List<Account> findByUid(int uid);
- 向com/lois/dao/AccountDao.xml映射文件中添加映射
<select id="findByUid" parameterType="int" resultType="account">
select * from account where uid = #{uid}
</select>
- 向测试类中添加方法进行测试
@Test
public void findByUidAccount(){
System.out.println(accountDao.findByUid(1));
}
- 修改测试类中的findAllUser方法,为了更好的看到延迟加载
@Test
public void findAllUser(){
List<User> all = userDao.findAll();
for (User user :
all) {
System.out.println("------------------------");
System.out.println(user);
}
}
- 运行测试方法findAllUser
2.缓存
像大多数的持久化框架一样,Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。
Mybatis 中缓存分为一级缓存,二级缓存。
2.1 一级缓存
一级缓存是存在SqlSession中,只要SqlSession没有 flush 或 close,它就存在。
2.1.1 验证一级缓存
- 在测试类中添加测试方法
@Test
public void testFirstLevelCache1(){
User user1 = userDao.findById(1);
System.out.println(user1);
User user2 = userDao.findById(1);
System.out.println(user2);
System.out.println(user1 == user2);
}
- 运行测试
我们可以看到只发生了一次查询,且user1 == user2为true,我们知道这是比较的地址,所以user1和user2指向同一个对象。其实一级缓存在上面的实验中已经出现过了,在延迟加载查询账户时,小明只查询了一次。
2.2 分析一级缓存
一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
2.2.1 重启session
- 将测试类中SqlSessionFactory factory提取成为成员变量
private InputStream in;
private SqlSession session;
private AccountDao accountDao;
private UserDao userDao;
private RoleDao roleDao;
private SqlSessionFactory factory;
//在测试方法执行之前执行
@Before
public void before() throws IOException {
//1.获取工厂图纸(配置信息)
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.招募工人(创建构建者)
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.给工人图纸建造工厂(构建者构建工厂对象)
factory = builder.build(in);
//4.构建产品流水线(打开自动提交事务)
session = factory.openSession(true);
//5.根据产品设计图生产出产品(动态代理)
accountDao = session.getMapper(AccountDao.class);
userDao = session.getMapper(UserDao.class);
roleDao = session.getMapper(RoleDao.class);
}
- 添加测试方法
@Test
public void testFirstLevelCache2(){
User user1 = userDao.findById(1);
System.out.println(user1);
//重启session
session.close();
session = factory.openSession(true);
userDao = session.getMapper(UserDao.class);
User user2 = userDao.findById(1);
System.out.println(user2);
System.out.println(user1 == user2);
}
- 运行测试
我们发现查询了两次,而且两者比较为false
2.2.2 其他方法
- 将上面的重启session注释掉,换成session.clearCache(),效果跟上面一样
- 如果换成在中间调用了修改,添加,删除,commit()的效果也是一样
2.2 二级缓存
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
2.3 开启二级缓存
- 在 SqlMapConfig.xml 文件开启二级缓存
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
- 配置相关的 Mapper 映射文件(我们以UserDao.xml为例),在mapper标签内添加
<!--开启User支持二级缓存-->
<cache/>
- 配置 statement 上面的 useCache 属性
<select id="findById" resultType="user" parameterType="int" useCache="true">
select * from user where id = #{id}
</select>
- 添加测试方法
@Test
public void testSecondLevelCache(){
SqlSession session1 = factory.openSession();
UserDao userDao1 = session1.getMapper(UserDao.class);
User user1 = userDao1.findById(1);
System.out.println(user1);
session1.close();//一级缓存消失
SqlSession session2 = factory.openSession();
UserDao userDao2 = session2.getMapper(UserDao.class);
User user2 = userDao2.findById(1);
System.out.println(user2);
session2.close();//一级缓存消失
System.out.println(user1 == user2);
}
- 运行测试
我们发现确实只查询了一次,但是user1 == user2 却是false,这表示他们不是同一个对象,这是为什么?
注意: 这样因为二级缓存存储在factory中,但是他的存储形式不是以对象存储,而是以数据的方式存储,需要用的时候再生成对象,所以在上面查询中,生成了两个对象。
3.注解开发
Mybatis不仅提供了使用xml映射来完成配置,还提供了一种跟简单的配置方法,注解配置。xml映射和注解配置不能在同一个Dao接口类中使用!!
3.1 准备工作
- 创建子工程day3_annotation
- 复制子工程day1的main和test文件(不要在idea中复制)
- 删除UserDao.xml映射文件
3.2 增删查改
- 修改UserDao接口类
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
public List<User> findAll();
/**
* 根据id查询用户
* @param id
* @return
*/
@Select("select * from user where id = #{id}")
public User findById(int id);
/**
* 保存用户
* @param user
*/
@Insert("insert into user(name,age) values (#{name},#{age})")
public void save(User user);
/**
* 修改用户
* @param user
*/
@Update("update user set name = #{name},age = #{age} where id = #{id}")
public void update(User user);
/**
* 根据id删除用户
* @param id
*/
@Delete("delete from user where id = #{id}")
public void deleteById(int id);
我们只需要在方法的上方使用@Select、@Insert、@Update、@Delete即可完成配置
- 运行测试方法进行测试
3.3 实体类属性与数据库表映射
如果实体类属性的名称和查询出来的表列字段不一致时,我们就要手动设置映射关系
@Select("select * from user")
@Results(value = {
@Result(id = true,column = "ids" ,property = "id"),
@Result(column = "username" ,property = "name"),
@Result(column = "age" ,property = "age")
})
public List<User> findAll();
如果我们要在别处使用怎么办,Results注解还有一个id属性,设置后可以在别处使用
@Results(id = "userMap",value = {
@Result(id = true,column = "ids" ,property = "id"),
@Result(column = "username" ,property = "name"),
@Result(column = "age" ,property = "age")
})
在其他方法上添加注解ResultMap
@ResultMap(value = {"userMap"})
3.4 一对一(使用延迟加载)
- 将day3子项目中的Account实体类和AccountDao接口类复制到day3_annotation子项目中
- 在AccountDao接口中修改方法findAll
@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",property = "user",one = @One(select = "com.lois.dao.UserDao.findById",fetchType = FetchType.LAZY))
})
public List<Account> findAll();
使用@one即可分布查询,如果将 fetchType 属性设置 FetchType.LAZY 即开启懒加载
- 修改测试类,获取accountDao,并添加测试方法
accountDao = session.getMapper(AccountDao.class);
@Test
public void findAllAccount(){
List<Account> all = accountDao.findAll();
for (Account account :
all) {
System.out.println("---------------------");
System.out.println(account);
}
}
其实跟使用xml映射配置差不多,只是采用了注解的方式,如果调用的也在前面讲过了,这里就不重复了
3.5 一对多(使用延迟加载)
- 将day3子项目中的User实体类复制到day3_annotation子项目中
- 修改UserDao接口中的findAll方法
/**
* 查询所有用户,并且查询出用户下所有账户信息
* @return
*/
@Select("select * from user")
@Results(id = "userMap", value = {
@Result(column = "id",property = "id"),
@Result(column = "name",property = "name"),
@Result(column = "age",property = "age"),
@Result(column = "id",property = "accounts",many = @Many(select = "com.lois.dao.AccountDao.findByUid",fetchType = FetchType.LAZY))
})
public List<User> findAll();
- 发现AccountDao.findByUid方法未完成,修改完成
@Select("select * from account where uid = #{uid}")
public List<Account> findByUid(int uid);
- 向测试类中添加方法,进行测试
@Test
public void findAllUser(){
List<User> all = userDao.findAll();
for (User user :
all) {
System.out.println("------------------------");
System.out.println(user);
}
}
3.6 缓存
一级缓存在这里就不说了,默认是开启的
那么我们来看看二级缓存是如何使用
- 配置SqlMapConfig.xml开启二级缓存(其实默认就是开启的,这步可以跳过)
<!-- 配置二级缓存 -->
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
- 然后只需要在需要使用二级缓存的dao接口类上方加入@CacheNamespace(blocking = true)即可
@CacheNamespace(blocking = true)
public interface UserDao {
我在这里就不测试了,需要的话自行测试
3.7 注解开发总结
- 注解开发跟xml开发大致一样
- 注解开发使用延迟加载不需要对SqlMapConfig.xml配置文件进行修改
- 一个Dao接口只能有一个实现方式,要么是注解开发要么是xml映射
- 如果你想看各个注解还有什么属性,可以按ctrl+鼠标点击该注解查看
- 个人认为注解开发要比xml简单许多
上一章:(二)Mybatis增删查改、动态SQL语句、多表操作(简单易懂,图文教学,整套实战)
相关内容:
(一)初见MyBatis,快速入门以及配置文件映射文件讲解(构建思路讲解,图文教学,整套实战)