MyBatis学习笔记--下篇

MyBatis学习笔记–下篇

1、多对一的处理(关联)

在这里插入图片描述

  • 多个学生,对应一个老师
  • 对于学生而言,关联…多个关联一个老师
  • 对于老师而言,**集合…**一个老师有多个学生
    在这里插入图片描述

1.1、表的创建

Teacher表

DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='老师表';

-- ----------------------------
-- Records of teacher
-- ----------------------------
INSERT INTO `teacher` VALUES ('1', 'radan');
INSERT INTO `teacher` VALUES ('2', 'js');

Student表

DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `tid` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `tid` (`tid`),
  CONSTRAINT `tid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES ('1', '张三', '1');
INSERT INTO `student` VALUES ('2', '李四', '1');
INSERT INTO `student` VALUES ('3', '王五', '1');
INSERT INTO `student` VALUES ('4', '溜溜', '1');

1.2、实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String  name;
}

public class Student {
    private int id;
    private String name;
   // 学生需要关联一个老师
    private Teacher teacher;
}

1.2、按照查询嵌套处理

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

    <resultMap id="studentAndTeacher" type="student">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <!--
            复杂属性需要单独处理
            对象:association
            集合:collection
        -->
        <association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>

    </resultMap>
    <select id="getAllStudents" resultMap="studentAndTeacher">
        select * from student
    </select>
    <select id="getTeacher" resultType="teacher">
        select * from teacher where id = #{id}
    </select>

1.3、按照结果嵌套处理(联表查询 )

        注意点:就是当要联立的两张表都有相同的字段名时,会起“冲突”,导致查询的结果都是前面一张表的字段属性值
        解决思路:在编写sql语句时,给冲突的列起别名,然后在resultMap结果集映射中利用别名可以有效的避免冲突。

<!--
    联表查询
    注意点:就是当要联立的两张表都有相同的字段名时,会起“冲突”,导致查询的结果都是前面一张表的字段属性值。
    解决思路:在编写sql语句时,给冲突的列起别名,然后在resultMap结果集映射中利用别名可以有效的避免冲突。
-->
    <select id="getAllStudents2" resultMap="studentMap">
        select s.id sid,s.name sname, t.name tname, t.id t_id from student s,teacher t where s.tid=t.id
    </select>
    <resultMap id="studentMap" type="student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="teacher">
            <result property="id"  column="t_id"/>
            <result property="name" column="tname"/>
        </association>
    </resultMap>

2、一对多的处理(Collection)

例如:一个老师拥有多个学生
对于老师而言就是一个一对多的处理。
在这里插入图片描述

2.1、实体类

public class Teacher {
    private int id;
    private String  name;
    //一个老师拥有多个老师
    List<Student> students;
}
public class Student {
    private int id;
    private String name;
    //学生只有一个老师
    private int tid;
}

2.2、按结果查询(联表查询)

    <!--    按照结果嵌套查询-->
    <resultMap id="teacherMap" 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>

    <select id="getAllTeacher" resultMap="teacherMap">
        select s.id sid,s.name sname,t.id tid,t.name tname
        from teacher t,student s where s.tid=t.id
    </select>

2.3、按照结果嵌套查询

<!--    按照结果嵌套查询-->
    <select id="getTeacher2" resultMap="teacherMap2">
        select * from teacher where id=#{id}
    </select>
    <resultMap id="teacherMap2" type="teacher">
        <collection property="students"  javaType="ArrayList"  ofType="student" column="id" select="getStudentByTeacher_id">
            
        </collection>
    </resultMap>
    <select id="getStudentByTeacher_id" parameterType="int" resultType="student">
        select * from student where tid=#{tid}
    </select>

总结:

  • 关联—【association】–多对一
  • 集合—【collection 】一对多
  • javatype &ofType
    • javatype :用来指定实体类中属性的类型
    • ofType:用来映射到List或者集合的破击类型,泛型中的约束类型!

注意点:

  • 保证sql的可读性,尽量保证sql的通俗易通
  • 注意一对多和多对一中,属性名和字段的问题!
  • 如果问题不好排查,可以使用日志,建议使用log4j

3、动态Sql

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

3.1、表的创建

DROP TABLE IF EXISTS `blog`;
CREATE TABLE `blog` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `title` varchar(255) DEFAULT NULL COMMENT '博客标题',
  `author` varchar(255) DEFAULT NULL COMMENT '博客作者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `views` int DEFAULT NULL COMMENT '浏览量',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='博客表';

-- ----------------------------
-- Records of blog
-- ----------------------------
INSERT INTO `blog` VALUES ('1', '游戏', 'randan', '2023-06-30 17:22:07', '1');
INSERT INTO `blog` VALUES ('2', '日志', 'radan', '2023-10-30 17:21:21', '22');
INSERT INTO `blog` VALUES ('3', '兰州牛肉面', 'js', '2023-06-30 17:21:20', '33');

3.2、动态sql中三大标签的使用

3.2.1、if
<select id="queryBlog" parameterType="map" resultType="blog">
        select * from blog where 1=1
        <if test="title != null ">
            and title=#{title}
        </if>
        <if test="author != null ">
            and author=#{author}
        </if>
    </select>
3.2.2、choose(when,otherwise)

这个标签像Switch,Case语法的使用规则。(模糊查找,提供了什么就查询这些条件符合的记录)。
只能选择一个 按照顺序来判断

<select id="queryBlogChoose2" parameterType="map" resultType="blog">
        <!--        where 标签可以有效的解决 1=1代码的重复编写-->
        select * from 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>
3.2.3、 trim(where,set)
  • where:where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除
  • set:set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

Where

    <select id="queryBlogChoose" parameterType="map" resultType="blog">
<!--        where 标签可以有效的解决 1=1代码的重复编写-->
        select * from blog
        <where>
            <if test="title != null ">
                and title=#{title}
            </if>
            <if test="author != null ">
                and author=#{author}
            </if>
        </where>

在这里插入图片描述

Set(注意:必须有一个if成立,不然就会导致update语句后面没有set更新的值。)

 <update id="updateBlog" parameterType="map" >
        update blog
        <set>
            <if test="title != null">title=#{title},</if>
            <if test="author != null">author=#{author}</if>
        </set>
        where id=#{id}
    </update>
3.2.4、 for each

在这里插入图片描述

    <select id="queryBlogForEach" parameterType="map" resultType="blog">
<!--        查询前三个人的信息-->
        select * from blog
        <where>
            <foreach collection="ids" index="index" item="id" open=" and ("  close=")" separator="or">
                 id =  #{id}
             </foreach>
        </where>

测试:

  @org.junit.Test
    public void test006() throws IOException {
        SqlSession sqlSession= MyBatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        HashMap map=new HashMap();
        List<Integer> ids=new ArrayList<Integer>();
        ids.add(1);
        ids.add(2);
        ids.add(3);
        map.put("ids",ids);
        System.out.println(mapper.queryBlogForEach(map));
        sqlSession.commit();
        sqlSession.close();
    }
3.2.5、 Sql片段

作用:将重复的SQL语句抽取出来,放到标签中,可以进行复用。

  1. 使用sql标签抽取公共部分
   <sql id="title-author">
        <if test="title != null ">
            and title=#{title}
        </if>
        <if test="author != null ">
            and author=#{author}
        </if>
    </sql>
  • 使用include标签引用公共部分定义的sql片段
    <select id="queryBlog" parameterType="map" resultType="blog">
        select * from blog where 1=1
        <!--refid:引用id-->
        <include refid="title-author"></include>
       
    </select>

注意事项:

  • 最好基于单表来定义sql片段
  • sql抽取的部分不要存在where标签

4、缓存

4.1、简介

查询数据–>连接数据库,好资源!

什么是缓存[Cache]?

  • 存在内存中的临时数据。
  • 将用户经常查询的数据放在缓存(内存)中,用户查询数据就不用偶从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
    为什么使用缓存?
  • 减少和数据库的交互次数,减少系统开销,提高系统效率。

什么样的数据能使用缓存

  • 经常查询并且不经常改变的数据。

4.2、Mybatis缓存

  • MyBatis 包含了一个非常强大的查询缓存特性,他可以非常方便地定制和配置缓存,缓存可以极大的提升查询效率。

  • MyBatis 系统中默认定义了两级缓存:以及缓存和二级缓存

    • 默认情况下,只有以及缓存开启。(SQLSession级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和设置,它是基于namespace级别的缓存。为了提高扩展性,MyBatis定义了缓存接口Cache 。我们可以通过缓存Cache接口来定义二级缓存。

4.3、一级缓存(作用小)

测试步骤:

  • 开启日志
    <settings>
        <!--配置标准日志工厂-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
  • 测试(在一个session中查询两次相同操作)
    @Test
    public void  test001() throws IOException {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> allUsers = mapper.getAllUsers();
        List<User> users = allUsers;
        System.out.println(users==allUsers);
        sqlSession.close();
    }

在这里插入图片描述
缓存失效的情况:

  1. 查询不同的条件
  2. 增删改操作,可能会改变原来的啥时间,所以必定会刷新缓存!
  3. 查询不同的Mapper.xml
  4. 手动清理缓存!
sqlSession.clearCache();//清理缓存

小结:一级缓存默认是开启的,只在一次SQLSession中有效,也就是拿到连接到关闭连接的区间。

4.4、二级缓存

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

步骤:

  • 在mybatis配置文件中,配置全局缓存设置;
 <settings>
        <!--显示的开启全局缓存   默认是开启的-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
  • 在mapper.xml加缓存标签

创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

    <!--开启二级缓存-->
        <cache
        eviction="FIFO"
        flushInterval="60000"
        size="512"
        readOnly="true"/>
<!--    eviction:缓存执行的策略
        flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
        size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
        readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
   -->
  • 测试
@Test
    public void  test002() throws IOException {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        SqlSession sqlSession1 = MyBatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user1 = mapper.findUserById(1);
        sqlSession.close();
        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
        User user2 = mapper1.findUserById(1);
        System.out.println(user1==user2);
        sqlSession1.close();
    }

注意点:
二级缓存存在于 SqlSessionFactory 生命周期中

我一直以为二级缓存针对的对象是一个Mapper对象,只要是针对同一个Mapper的操作,都可以实现二级缓存。但是前提是操作必须在同一个SqlSessionFactory 中进行。

在一开始的代码中,通过调用三次getFactory()并打开session,实例化了三个不同的SqlSessionFactory 对象,这样在后续的SQL操作中,是不可能命中二级缓存的。

在这里插入图片描述

4.5、缓存原理

在这里插入图片描述

4.6、自定义缓存–Ehcache

Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存。

要在程序中使用,先导入依赖

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

在mapper.xml中使用对应的缓存即可

   <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

编写ehcache.xml文件,如果在 加载时 未找到 /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" dynamicConfig="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>

测试结果还是和之前一样的。

5、问题

问题描述:当我们在mybatis的配置文件中,写好绑定Mapper.xml文件时,运行结果依旧提示找不到Mapper.xml或是没有绑定。
MyBatis 配置信息:
在这里插入图片描述

错误信息:
在这里插入图片描述
此时,我们可以查看target目录下的class文件夹,发现并没有编译出UserMapper.xml文件。
在这里插入图片描述
解决方法:主要原因就是Maven在导出资源失败,资源过滤出现问题。我们需要在pom.xml中添加防止一些指定的资源文件的过滤配置。

       <!-- 在build中配置resources。来防止我们资源导出失败的问题 资源过滤问题-->
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值