Mybatis学习笔记(下)

本文深入探讨MyBatis框架的高级特性,包括多对一和一对多查询、动态SQL、缓存机制等内容,旨在帮助开发者掌握MyBatis的复杂场景应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

九、多对一查询

测试环境搭建:

  1. 新建实体类 Teacher,Student
  2. 建立Mapper接口
  3. 建立Mapper.XML文件
  4. 在核心配置文件中绑定注册我们的Mapper接口或者文件!

数据库

CREATE TABLE `teacher` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师'); 

CREATE TABLE `student` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  `tid` INT(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fktid` (`tid`),
  CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8


INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
  • 对于学生这边而言, 关联 … 多个学生,关联一个老师 【多对一】
  • 对于老师而言, 集合 , 一个老师,有很多学生 【一对多】

student类

成员变量tid变为Teacher,其他方法省略了。

public class Student {
    private int id;
    private String name;
    private Teacher teacher;
}

teacher类

public class Teacher {
    private int id;
    private String name;
}
9.1 按照结果嵌套查询

思路:
1. 查询所有的学生信息
2. 根据查询出来的学生的tid,寻找对应的老师! 子查询

<select id="getStudent" resultMap="StudentTeacher">
    select * from student
</select>

<resultMap id="StudentTeacher" type="Student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <!--复杂的属性,我们需要单独处理 对象: association 集合: collection -->
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
    <!--select会将column对应数据作为参数去做查询-->
</resultMap>

<select id="getTeacher" resultType="Teacher">
    select * from teacher where id = #{id}
</select>
9.2 按照结果嵌套处理

连接查询

<!--按照结果嵌套处理-->
<select id="getStudent2" resultMap="StudentTeacher2">
    select s.id sid,s.name sname,t.name tname
    from student s,teacher t
    where s.tid = t.id;
</select>

<resultMap id="StudentTeacher2" type="Student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="Teacher">
        <result property="name" column="tname"/>
    </association>
</resultMap>

十、一对多查询

student类

public class Student {
    private int id;
    private String name;
    private int tid;
}

teacher类

public class Teacher {
    private int id;
    private String name;
    private List<Student> students;
}
10.1 按照查询嵌套查询
<select id="getTeacher2" resultMap="TeacherStudent2">
    select * from mybatis.teacher where id = #{tid}
</select>

<resultMap id="TeacherStudent2" type="Teacher">
    <collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/>
</resultMap>

<select id="getStudentByTeacherId" resultType="Student">
    select * from mybatis.student where tid = #{tid}
</select>
10.2 按照结果嵌套处理
<!--按结果嵌套查询-->
    <select id="getTeacher" resultMap="TeacherStudent">
        select s.id sid, s.name sname, t.name tname,t.id tid
        from student s,teacher t
        where s.tid = t.id and t.id = #{tid}
    </select>

    <resultMap id="TeacherStudent" type="Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <!--复杂的属性,我们需要单独处理 对象: association 集合: collection
        javaType="" 指定属性的类型!
        集合中的泛型信息,我们使用ofType获取(集合中的数据类型)
        -->
        <collection property="students" ofType="Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>

十一、动态SQL

什么是动态SQL:动态SQL就是指根据不同的条件生成不同的SQL语句

先看一下没有用Mybatis时动态SQL的实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0u7sZLSK-1595414938290)(C:%5CUsers%5CWIN10%5CDesktop%5C%E6%96%87%E4%BB%B6%5CMybatis.assets%5C1595390129592.png)]

Mybatis的动态SQL

   <select id="query" parameterType="map" resultType="pojo.User">
        select * from mybatis.User where 1 = 1
        <if test="userName != null">
            and userName = #{userName}
        </if>
        <if test="userRole != null">
            and author = #{userRole}
        </if>
    </select>
创建环境
CREATE TABLE `blog` (
  `id` varchar(50) NOT NULL COMMENT '博客id',
  `title` varchar(100) NOT NULL COMMENT '博客标题',
  `author` varchar(30) NOT NULL COMMENT '博客作者',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8

11.1 IF

当if条件满足时,会将if标签中的语句加到sql语句后面

<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from mybatis.blog where 1=1
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</select>
11.2 choose (when, otherwise)

和c语言的switch一样,当满足一个when条件时,就会跳出choose,都不满足时,就会选择otherwise

 <select id="queryBlogChoose" parameterType="map" resultType="blog">
        select * from mybatis.blog
        <where>
            <choose>
                <when test="title != null">
                    title = #{title}
                </when>
                <when test="author != null">
                    and author = #{author}
                </when>
                <otherwise>
                    and views = #{views}
                </otherwise>
            </choose>
        </where>
    </select>
11.3 trim (where,set)

where标签会自动去掉头个判断条件的and,这就是上面choose标签外套一个where标签的原因,如果没有where就会变成where and (条件) 会报sql语法错误

select * from mybatis.blog
<where>
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</where>
<update id="updateBlog" parameterType="map">
    update mybatis.blog
    <set>
        <if test="title != null">
            title = #{title},
        </if>
        <if test="author != null">
            author = #{author}
        </if>
    </set>
    where id = #{id}
</update>
11.4 SQL片段

将可以复用的代码写在一个sql标签里,用到的时候直接用include引用就行

<sql id="if-title-author">
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</sql>

<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <include refid="if-title-author"></include>
    </where>
</select>
11.5 forEach
<!--
        select * from mybatis.blog where id in (1,2,3)
-->
<select id="selectById" resultType="pojo.Blog" parameterType="ArrayList">
        SELECT * FROM mybatis.blog where id in 
        <foreach item="item" index="index" collection="list"
                 open="(" separator="," close=")">
            #{item}
        </foreach>
</select>

你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。 open是开始字符,close是关闭字符,separator是分隔符

十二、缓存

缓存:接之前的查找结果保存起来,当再次执行查找时,如果缓存中有,直接将结果从缓存中拿出来

  • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
  • MyBatis系统中默认定义了两级缓存:一级缓存二级缓存
    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
    • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
12.1 一级缓存

测试一个sqlSession中调用两次相同的查询,观察日志输出

@Test
    public void getUserById(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);

        User user = userDao.getUserById(1);
        User user1 = userDao.getUserById(1);
        System.out.println(user);
        System.out.println(user1);

        System.out.println(user.equals(user1));

        sqlSession.close();
 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wUrOBj95-1595414938292)(C:%5CUsers%5CWIN10%5CDesktop%5C%E6%96%87%E4%BB%B6%5CMybatis.assets%5C1595403714343.png)]

日志输出只进行了一次查询,因为第二次查询是在缓存中直接拿到的结果。

缓存失效的情况:

  1. 查询不同的东西

  2. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存!

  3. 查询不同的Mapper.xml

  4. 手动清理缓存!

小结:一级缓存默认是开启的,只在一次SqlSession中有效,也就是说在缓存不失效的情况下可以一直到关闭sqlSession

12.2 二级缓存
  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
  • 工作机制
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取内容;
    • 不同的mapper查出的数据会放在自己对应的缓存中;

1.开启全局缓存

<setting name="cacheEnabled" value="true"/>

2.在要使用二级缓存的Mapper中开启

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RsFiRBhC-1595414938293)(C:%5CUsers%5CWIN10%5CDesktop%5C%E6%96%87%E4%BB%B6%5CMybatis.assets%5C1595404005086.png)]

也可以自定义参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nzH31DBy-1595414938293)(C:%5CUsers%5CWIN10%5CDesktop%5C%E6%96%87%E4%BB%B6%5CMybatis.assets%5C1595404051590.png)]

3.在没有设置eviction时,需要将实体类序列化

测试:

@Test
    public void getUserById(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = userDao.getUserById(1);
        System.out.println(user);
        sqlSession.close();

        SqlSession sqlSession1 = MybatisUtil.getSqlSession();
        UserDao userDao1 = sqlSession1.getMapper(UserDao.class);
        User user1 = userDao1.getUserById(1);
        System.out.println(user1);

        System.out.println(user.equals(user1));

        sqlSession1.close();
    }

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7QgJzmD9-1595414938294)(C:%5CUsers%5CWIN10%5CDesktop%5C%E6%96%87%E4%BB%B6%5CMybatis.assets%5C1595414342064.png)]

这里有个问题就是user和user1的判等为false,这里是因为readOnly参数的问题,因为这个参数默认为false,当你把它配成true时,这两个对象就相等了,看一下readOnly的官方解释也就明白了。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。速度上会慢一些,但是更安全,因此默认值是 false。

12.3 自定义缓存-ehcache
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存

要在程序中使用ehcache,先要导包!

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>

在mapper中指定使用我们的ehcache缓存实现!

<!--在当前Mapper.xml中使用二级缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="./tmpdir/Tmp_EhCache"/>
    
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>
 
    <cache
            name="cloud_user"
            eternal="false"
            maxElementsInMemory="5000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="1800"
            memoryStoreEvictionPolicy="LRU"/>
    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统当机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->

</ehcache>

结语

Mybatis更像一个实用性道具,学习起来比较简单,但需要在实践中多加练习

个。
–>






### 结语

Mybatis更像一个实用性道具,学习起来比较简单,但需要在实践中多加练习



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值