三、MyBatis加载策略及注解开发

一、MyBatis加载策略

1.什么是延迟加载?

问题:Mybatis中一对一,一对多,多对多关系的配置及实现,可以实现
对象的关联查询。实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的订单信息。此时就是我们所说的延迟加载。
举个栗子

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

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

优点:
		先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
缺点:
		因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
在多表中
		一对多,多对多:通常情况下采用延迟加载
		一对一(多对一):通常情况下采用立即加载
注意:
		延迟加载是基于嵌套查询来实现的
区别:
		当前对象它的关联对象的加载时机:
		如果它的关联对象在查询当前对象立刻查询出来就是立即加载;
		关联对象什么时候用什么时候去查就就是延迟加载;

2.实现局部延迟加载

2.1 局部延迟加载

在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策略。

<!--一对多嵌套查询:查询所有用户,与此同时查询出该用户具有的订单-->
    <!-- 延迟加载是基于嵌套查询的 -->
    <resultMap id="userMap2" type="user">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="birthday" column="birthday"/>
        <result property="sex" column="sex"/>
        <result property="address" column="address"/>
        <!--
            fetchType="lazy" 表示延迟加载策略
            fetchType="eager" 表示立即加载策略
        -->
        <collection property="ordersList" ofType="orders" fetchType="lazy"
                        select="cn.xuguowen.mapper.OrdersMapper.findById" column="id"/>
    </resultMap>
    <select id="findAllWithOrders2" resultMap="userMap2">
        select * from user
    </select>

2.2 设置触发延迟加载的方法

我发现在配置了延迟加载策略后,发现即使没有调用关联对象的任何方法,但是在你调用当前对象的equals、clone、hashCode、toString方法时也会触发关联对象的查询。
我们可以在核心配置文件中使用lazyLoadTriggerMethods配置项覆盖掉上面四个方法。

<!--设置触发延迟加载策略的方法-->
    <settings>
        <!--当执行了当前对象的toString()方法就使用延迟加载策略-->
        <setting name="lazyLoadTriggerMethods" value="toString()"/>
    </settings>

3.实现全局延迟加载

在Mybatis的核心配置文件中可以使用setting标签修改全局的加载策略。

	 <settings>
        <!--当执行了当前对象的toString()方法就使用延迟加载策略-->
        <setting name="lazyLoadTriggerMethods" value="toString()"/>
        <!--全局延迟加载策略-->
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>

注意 局部的加载策略优先级高于全局的加载策略

<!-- 一对一嵌套查询 查询所有订单,与此同时还要查询出每个订单所属的用户信息-->
    <!-- 关闭全局延迟加载策略,使用立即加载策略 -->
    <resultMap id="ordersMap2" type="orders">
        <id property="id" column="id"/>
        <result property="ordertime" column="ordertime"/>
        <result property="total" column="total"/>
        <result property="uid" column="uid"/>
        <!--写到这里:就要考虑到第二次sql查询语句如何引入,
                    并且将第一条sql语句查询出来的uid作为参数传递到第二天sql语句中
            使用select引入第二条sql语句:值为第二条sql语句的statementid
            使用column标签将上面查询出来的uid作为参数传递到第二条sql语句中
        -->

        <association property="user" javaType="user" fetchType="eager"
                     select="cn.xuguowen.mapper.UserMapper.findById" column="uid"/>
    </resultMap>
    <select id="findAllWithUser2" resultMap="ordersMap2">
        <!-- 注意返回结果类型不能使用resultType进行自动封装查询结果,
                因为Orders实体类中还存在一个属性 user
        -->
        select * from orders
    </select>

二、MyBatis缓存

1.为什么要使用缓存?

当用户频繁查询某些固定的数据时,第一次将这些数据从数据库中查询出来,保存在缓存中。当用户再次查询这些数据时,不用再通过数据库查询,而是去缓存里面查询。减少网络连接和数据库查询带来的损耗,从而提高我们的查询效率,减少高并发访问带来的系统性能问题。
一句化概括:经常查询一些不经常发生变化的数据,使用缓存来提高查询效率。
像大多数的持久化框架一样,Mybatis也提供了缓存策略,通过缓存策略来少数据库的查询次数,从而提高性能。 Mybatis中缓存分为一级缓存,二级缓存。

2.一级缓存

一级缓存是SqlSeesion级别的缓存,是默认开启的
所以在参数和SQL语句完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。

	@Test
    public void testOneCache() throws IOException {
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = factory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 第一次查询
        User user1 = mapper.findById(1);
        System.out.println(user1);

        // 第二次查询
        User user2 = mapper.findById(1);
        System.out.println(user2);
    }

在这里插入图片描述
注意 一级缓存是SqlSession范围的缓存,执行SqlSession的C(增加)U(更新)D(删除)操作,或者调用clearCache()、commit()、close()方法,都会清空缓存,避免脏读。
在这里插入图片描述
每次查询时,都会清除缓存

	<select flushCache="true"></select>

3.二级缓存

二级缓存是namspace级别(跨sqlSession)的缓存,是默认不开启的
二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置 就可以开启二级缓存了。
在这里插入图片描述

核心配置文件
<settings>    
        <!--
            1.name="cacheEnabled" 表示二级缓存,默认值就是true,
            2.value="true"  表示开启二级缓存
        -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
配置UserMapper.xml文件
 <!--当前映射配置文件开启二级缓存-->
    <cache></cache>
    <!-- useCache="true" 表示当前UserMapper使用二级缓存 -->
    <select id="findById" resultType="user" parameterType="int" useCache="true">
        select * from user where id = #{id}
    </select>
修改User实体
public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private Character sex;
    private String address;

    // 表示多的关系:当前用户具备的订单列表
    private List<Orders> ordersList;

    // 表示多的关系:当前用户具备的角色列表
    private List<Role> roleList;
测试结果
	/**
     * 验证二级缓存
     * @throws IOException
     */
    @Test
    public void testTwoCache() throws IOException {
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession1 = factory.openSession();
        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
        User user1 = mapper1.findById(1);
        System.out.println(user1);

        // 只有执行SqlSession.commit()或者SqlSession.close,那么一级缓存中的内容才会刷新到二级缓存
        sqlSession1.close();

        SqlSession sqlSession2 = factory.openSession();
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = mapper2.findById(1);
        System.out.println(user2);
        sqlSession2.close();
    }

4.二级缓存的分析

在这里插入图片描述


5.二级缓存所产生的严重问题(脏读)

**mybatis的二级缓存因为是namespace级别,所以在进行多表查询时会产生脏读问题
**
在这里插入图片描述

6.小结

1. mybatis的缓存,都不需要我们手动存储和获取数据。mybatis自动维护的。
2. mybatis开启了二级缓存后,那么查询顺序:二级缓存--》一级缓存--》数据库
2. 注意:mybatis的二级缓存会存在脏读问题,需要使用第三方的缓存技术解决问题。

三、MyBatis注解开发

1.MyBatis常用注解

* @Insert:实现新增,代替了<insert></insert>
* @Delete:实现删除,代替了<delete></delete>
* @Update:实现更新,代替了<update></update>
* @Select:实现查询,代替了<select></select>
* @Result:实现结果集封装,代替了<result></result>
* @Results:可以与@Result 一起使用,封装多个结果集,代替了<resultMap></resultMap>
* @One:实现一对一结果集封装,代替了<association></association>
* @Many:实现一对多结果集封装,代替了<collection></collection>

2.MyBatis注解开发实现增删改查

①:UserMapper接口

package cn.xuguowen.mapper;

import cn.xuguowen.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;

/**
 * @author 徐国文
 * @create 2021-10-03 17:40
 */
public interface UserMapper {
    /**
     * 查询所有用户信息
     * @return
     */
    @Select("select * from user")
    List<User> findAll();

    /**
     * 保存用户信息
     * @param user
     */
    @Insert("insert into user (username,birthday,sex,address) values (#{username}, #{birthday}, #{sex},#{address} )")
    void save(User user);

    /**
     * 根据用户id修改用户信息
     * @param user
     */
    @Update("update user set username = #{username},birthday = #{birthday} where id = #{id}")
    void update(User user);

    /**
     * 根据id删除用户
     * @param id
     */
    @Delete("delete from user where id = #{id}")
    void delete(Integer id);
}

②:核心配置文件

<!--我们使用了注解替代的映射文件,所以我们只需要加载使用了注解的Mapper接口即可-->
<mappers>
	<!--扫描使用注解的Mapper类-->
	<!-- <mapper class="com.lagou.mapper.UserMapper"></mapper> -->
	<!--扫描使用注解的Mapper类所在的包-->
	<package name="com.lagou.mapper"></package>
</mappers>

③:测试

package cn.xuguowen.test;

import cn.xuguowen.mapper.UserMapper;
import cn.xuguowen.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;

/**
 * @author 徐国文
 * @create 2021-10-03 17:42
 */
public class TestAnno {
    private SqlSessionFactory factory;
    private SqlSession sqlSession;

    @Before
    public void before() throws IOException {
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        factory = new SqlSessionFactoryBuilder().build(is);
        sqlSession = factory.openSession();

    }

    @After
    public void after() {
        sqlSession.commit();
        sqlSession.close();
    }
    @Test
    public void testFindAll() throws IOException {

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.findAll();

        for (User user : users) {
            System.out.println(user);
        }
    }

    @Test
    public void testSave() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User();
        user.setUsername("徐国文");
        user.setBirthday(new Date());
        user.setSex('男');
        user.setAddress("山西大同");

        mapper.save(user);
    }

    @Test
    public void testUpdate() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User();
        user.setUsername("黄老板");
        user.setBirthday(new Date());
        user.setId(6);
        mapper.update(user);
    }

    @Test
    public void testDelete() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.delete(8);
    }
}

3.MyBatis注解开发实现复杂映射开发

之前我们在映射文件中通过配置 resultMap、association、collection 来实现复杂关系映射。
使用注解开发后,我们可以使用@Results、@Result、@One、@Many注解组合使用完成复杂关系的配置

注解说明
@Results代替的标签是resultMap,该注解中可以使用单个@Result注解,也可以使用@Result集合。使用格式:@Results({@Result(),@Result(),@Result(),@Result()})
@Result代替了标签id和result。@Result中属性的介绍:column:数据库的列名,property需要装配的属性名,one需要使用@One注解( @Result(one=@One()) ),many需要使用@Many注解( @Result(many=@Many()) )
@One (一对一)代替了assocation标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。@One注解属性的介绍:select指定用来查询的sqlmapper,其实就是namespace.id来确定是哪个mapper中的哪个sql语句。使用格式:@Result(column=" “,property=” “,one=@One(select=” ") )
@Many(一对多)代替了collection标签,是多表查询的关键,在注解中用来指定子查询返回对象集合 。使用格式:@Result(property=" “,column=” “,many=@Many(select=” ") )

4.MyBatis注解开发实现一对一查询

4.1 需求:查询订单信息,与此同时查询出该订单所属的用户信息

一对一查询语句
SELECT * FROM orders;
SELECT * FROM `user` WHERE id = #{订单的uid};

4.2 代码实现

a)OrderMapper接口和UserMapper接口
	 /**
     * 查询所有订单,与此同时查询出该订单所属的用户信息
     * @return
     */
    @Select("select * from orders")
    @Results({
            @Result(property = "id",column = "id"),
            @Result(property = "ordertime",column = "ordertime"),
            @Result(property = "total",column = "total"),
            @Result(property = "uid",column = "uid"),
            @Result(property = "user",javaType = User.class,column = "uid",
                    one = @One(select = "cn.xuguowen.mapper.UserMapper.findById", fetchType = FetchType.EAGER))
    })
    List<Orders> findAllWithUser();
	/**
     * 注解实现一对一查询:查询所有订单,以及查询出该订单所属的用户信息
     * @param uid
     * @return
     */
    @Select("select * from user where id = #{uid}")
    User findById(Integer uid);
b)测试代码
	/**
     * 测试一对一查询:查询所有订单,以及该订单所属的用户信息
     */
    @Test
    public void testOneToOne() {
        OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
        List<Orders> allWithUser = mapper.findAllWithUser();
        for (Orders orders : allWithUser) {
            System.out.println(orders);
        }
    }

5.MyBatis注解开发实现一对多查询

5.1 需求:查询所有用户,以及该用户所具有的订单信息

一对多查询语句
SELECT * FROM user;
SELECT * FROM `orders` WHERE uid = #{用户id};

5.2 代码实现

a)UserMapper接口和OrdersMapper接口
/**
     * 注解实现一对多查询:查询所有用户,以及查询出该用户所具有的订单信息
     * @return
     */
    @Select("select * from user")
    @Results({
            @Result(property = "id",column = "id",id = true),
            @Result(property = "username",column = "username"),
            @Result(property = "birthday",column = "birthday"),
            @Result(property = "sex",column = "sex"),
            @Result(property = "address",column = "address"),
            @Result(property = "ordersList",javaType = List.class,column = "id",
                    many = @Many(select = "cn.xuguowen.mapper.OrderMapper.findById") )
    })
    List<User> findAllWithOrders();
/**
     * 注解实现一对多查询:查询所有用户,以及查询出该用户所具有的订单信息
     * @param uid 根据用户id查询出用户所具有的订单信息
     * @return
     */
    @Select("select * from orders where uid = #{uid}")
    List<Orders> findById(Integer uid);
b)测试代码
/**
     * 测试一对多查询:查询所有用户,以及该用户所具有的订单信息
     */
    @Test
    public void testOneToMany() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.findAllWithOrders();
        for (User user : users) {
            // 在核心配置文件中:配置了当调用当前对象的toString()方法就触发延迟加载策略
            System.out.println(user);
            // 现在我需要查询用户的订单信息了
            System.out.println(user.getOrdersList());
        }
    }

在这里插入图片描述


6.MyBatis注解开发实现多对多查询

6.1 需求:查询所有用户,同时查询出该用户的所有角色

多对多查询语句
SELECT * FROM user;
SELECT * FROM sys_role r INNER JOIN sys_user_role ur ON r.id = ur.roleid WHERE ur.userid = #{用户id};

6.2 代码实现

a)UserMapper接口和RoleMapper接口
	 /**
     * 注解实现多对多查询:查询所有用户,以及查询该用户具备的角色信息
     * @return
     */
    @Select("select * from user")
    @Results({
            @Result(property = "id",column = "id",id = true),
            @Result(property = "username",column = "username"),
            @Result(property = "birthday",column = "birthday"),
            @Result(property = "sex",column = "sex"),
            @Result(property = "address",column = "address"),
            @Result(property = "roleList",javaType = List.class,column = "id",
                    many = @Many(select = "cn.xuguowen.mapper.RoleMapper.findByUserId"))
    })
    List<User> findAllWithRole();
	/**
     * 注解实现多对多查询:查询所有用户,以及查询该用户具备的角色信息
     * @param uid 根据用户id查询用户的角色信息
     * @return
     */
    @Select("SELECT * FROM sys_role r INNER JOIN sys_user_role ur ON r.id = ur.roleid WHERE ur.userid = #{uid}")
    List<Role> findByUserId(Integer uid);
b)测试代码
	/**
     * 注解实现多对多查询:查询所有用户,以及查询该用户具备的角色信息
     */
    @Test
    public void testManyToMany() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.findAllWithRole();
        for (User user : users) {
            // 由于在核心配置文件中设置了全局延迟加载策略:当调用当前对象toString()方法会触发延迟加载机制
            System.out.println(user);

            System.out.println(user.getRoleList());
        }
    }

7.MyBatis注解开发实现二级缓存

7.1配置核心配置文件开启二级缓存的支持

<settings>
        <!--
            1.name="cacheEnabled" 表示二级缓存,默认值就是true,
            2.value="true"  表示开启二级缓存
        -->
        <setting name="cacheEnabled" value="true"/>
    </settings>

7.2 在Mapper接口中使用注解配置二级缓存

/*使用@CacheNamespace注解使用二级缓存*/
@CacheNamespace
public interface UserMapper {...}

8.MyBatis注解开发实现延迟加载策略

不管是一对一还是一对多,在注解配置中都有fetchType的属性

* fetchType = FetchType.LAZY 表示懒加载
* fetchType = FetchType.EAGER 表示立即加载
* fetchType = FetchType.DEFAULT 表示使用全局配置,如果全局延迟加载策略的值为true,表示使用延迟加载策略;如果全局延迟加载策略的值为false,表示使用立即加载策略。
*   <!--全局延迟加载策略--> <setting name="lazyLoadingEnabled" value="true"/>

9.小结

注解开发和xml映射文件配置优劣分析
	1.从开发效率来说:注解编写更简单,效率更高。
	2.从可维护性来说:注解如果要修改,必须修改源码,会导致维护成本增加。xml映射配置文件维护性更强。
	3.在实际开发中:两种开发方式结合使用。单表CURD可以使用注解开发;复杂查询可以使用xml映射配置文件的方式开发。
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值