返回介绍

16 一对多、多对一、SQL 缓存

发布于 2024-07-13 17:49:55 字数 20866 浏览 0 评论 0 收藏 0

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, 'jeskson');

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
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.16.10</version>
</dependency>
@Data //GET,SET,ToString,有参,无参构造
public class Teacher {
 private int id;
 private String name;
}
@Data
public class Student {
 private int id;
 private String name;
 //多个学生可以是同一个老师,即多对一
 private Teacher teacher;
}

Mapper接口

public interface StudentMapper {
}
public interface TeacherMapper {
}
<?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.da.mapper.StudentMapper">
</mapper>
<?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.da.mapper.TeacherMapper">
</mapper>
//获取所有学生及对应老师的信息
public List<Student> getStudents();
<?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.da.mapper.StudentMapper">
    <select id="getStudents" resultMap="StudentTeacher">
      select * from student
    </select>
    <resultMap id="StudentTeacher" type="Student">
        <!--association关联属性  property属性名 javaType属性类型 column在多的一方的表中的列名-->
        <association property="teacher"  column="tid" javaType="Teacher" select="getTeacher"/>
    </resultMap>

    <select id="getTeacher" resultType="teacher">
        select * from teacher where id = #{id}
    </select>
</mapper>
<resultMap id="StudentTeacher" type="Student">
    <association property="teacher"  column="{id=tid,name=tid}" javaType="Teacher" select="getTeacher"/>
</resultMap>

<select id="getTeacher" resultType="teacher">
    select * from teacher where id = #{id} and name = #{name}
</select>
public List<Student> getStudents2();

<select id="getStudents2" 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">
    <id property="id" column="sid"/>
    <result property="name" column="sname"/>
    <!--关联对象property 关联对象在Student实体类中的属性-->
    <association property="teacher" javaType="Teacher">
        <result property="name" column="tname"/>
    </association>
</resultMap>
@Test
public void testGetStudents2(){
    SqlSession session = MybatisUtils.getSession();
    StudentMapper mapper = session.getMapper(StudentMapper.class);
    List<Student> students = mapper.getStudents2();
    for (Student student : students){
        System.out.println(
                "学生名:"+ student.getName()
                        +"\t老师:"+student.getTeacher().getName());
    }
}

按照查询进行嵌套处理,就像 SQL 中的子查询。MyBatis 提供了 <select> 标签中的 <collection><association> 标签来实现嵌套查询。其中,<collection> 标签用于处理集合类型的属性,而 <association> 标签用于处理单个对象类型的属性。例如:

<select id="getUserWithOrders" resultType="User">
  SELECT * FROM User
  WHERE id = #{userId}
  <collection property="orders" ofType="Order">
    SELECT * FROM Order
    WHERE user_id = #{userId}
  </collection>
</select>

按照结果进行嵌套处理,就像 SQL 中的联表查询。在 MyBatis 中,可以使用 <resultMap> 标签来定义映射关系,从而完成对象之间的联表查询。例如:

<select id="getUserWithOrdersAndItems" resultMap="userResultMap">
  SELECT * FROM User u
  LEFT JOIN Order o ON u.id = o.user_id
  LEFT JOIN Item i ON o.id = i.order_id
  WHERE u.id = #{userId}
</select>

<resultMap id="userResultMap" type="User">
  <id column="u.id" property="id"/>
  <result column="u.username" property="username"/>
  <collection property="orders" ofType="Order">
    <id column="o.id" property="id"/>
    <result column="o.order_time" property="orderTime"/>
    <collection property="items" ofType="Item">
      <id column="i.id" property="id"/>
      <result column="i.name" property="name"/>
    </collection>
  </collection>
</resultMap>
@Data public class Student { private int id; private String name; //多个学生可以是同一个老师,即多对一 private Teacher teacher; }

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

@Data 
public class Teacher {
    private int id;
    private String name;
    //一个老师多个学生
    private List<Student> students;
}
//获取指定老师,及老师下的所有学生
public Teacher getTeacher(int id);
<mapper namespace="com.da.mapper.TeacherMapper">
    <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=#{id}
    </select>
    <resultMap id="TeacherStudent" type="Teacher">
        <result  property="name" column="tname"/>
        <collection property="students" ofType="Student">
            <result property="id" column="sid" />
            <result property="name" column="sname" />
            <result property="tid" column="tid" />
        </collection>
    </resultMap>
</mapper>

集合的话,使用collection!

<resultMap id="StudentTeacher2" type="Student"> <id property="id" column="sid"/> <result property="name" column="sname"/> <!--关联对象property 关联对象在Student实体类中的属性--> <association property="teacher" javaType="Teacher"> <result property="name" column="tname"/> </association> </resultMap>

JavaType和ofType都是用来指定对象类型的

JavaType是用来指定pojo中属性的类型

ofType指定的是映射到list集合属性中pojo的类型

<mappers>
    <mapper resource="mapper/TeacherMapper.xml"/>
</mappers>

@Test
public void testGetTeacher(){
    SqlSession session = MybatisUtils.getSession();
    TeacherMapper mapper = session.getMapper(TeacherMapper.class);
    Teacher teacher = mapper.getTeacher(1);
    System.out.println(teacher.getName());
    System.out.println(teacher.getStudents());
}

按查询嵌套处理

public Teacher getTeacher2(int id);
<select id="getTeacher2" resultMap="TeacherStudent2">
  select * from teacher where id = #{id}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
    <!--column是一对多的外键 , 写的是一的主键的列名-->
    <collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
    select * from student where tid = #{id}
</select>
@Test
public void testGetTeacher2(){
    SqlSession session = MybatisUtils.getSession();
    TeacherMapper mapper = session.getMapper(TeacherMapper.class);
    Teacher teacher = mapper.getTeacher2(1);
    System.out.println(teacher.getName());
    System.out.println(teacher.getStudents());
}
  1. 关联-association
  2. 集合-collection
  3. 所以association是用于一对一和多对一,而collection是用于一对多的关系

使用说明: 关联和集合都是数据结构中常用的概念。关联通常用来表示两个元素之间的映射关系,而集合用于存储一组元素。在编程中,我们可以使用不同的数据类型来实现这些概念,如哈希表、数组、链表等。

  1. JavaType和ofType都是用来指定对象类型的

    • JavaType是用来指定pojo中属性的类型
    • ofType指定的是映射到list集合属性中pojo的类型。

使用 mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同时,也大大提高了开发人员的效率。

MyBatis是一款支持动态SQL的ORM框架,其提供了丰富的标签和功能用于生成不同的SQL语句。

动态SQL指的是根据不同的查询条件动态地生成SQL语句,这个过程通常在Java代码中完成。使用动态SQL可以避免在Java代码中手工拼接SQL语句,从而更加安全、灵活、易于维护。

MyBatis提供了以下几种动态SQL标签:

  1. if标签:用于根据条件判断是否包含某段SQL语句。
  2. choose、when、otherwise标签:用于实现类似于Java中的switch语句的功能。
  3. foreach标签:用于循环遍历某个集合,并将集合中的元素作为参数传递给SQL语句。
  4. where标签:用于判断是否需要在SQL语句中添加WHERE关键字。
  5. set标签:用于生成UPDATE语句中SET子句的内容。
<select id="findUsers" resultType="user">
  SELECT * FROM users
  <where>
    <if test="name != null">
      AND name = #{name}
    </if>
    <if test="age != null">
      AND age = #{age}
    </if>
    <if test="gender != null">
      AND gender = #{gender}
    </if>
  </where>
  <choose>
    <when test="order == 'name'">
      ORDER BY name
    </when>
    <when test="order == 'age'">
      ORDER BY age
    </when>
    <otherwise>
      ORDER BY id
    </otherwise>
  </choose>
</select>
  1. if 标签:该标签用于根据指定条件有条件地包含 SQL 语句的一部分。
<select id="getUsers" resultType="User">
SELECT * FROM users
<if test="name != null">
  WHERE name = #{name}
</if>
</select>

在此示例中,如果 "name" 参数不为空,则 WHERE 子句将被添加到 SQL 语句中。

  1. choose、when 和 otherwise 标签:这些标签用于在 SQL 中创建类似于 switch 的语句,其中只有多个条件之一可以满足。
<select id="getUserById" resultType="User">
SELECT * FROM users
<choose>
  <when test="id != null">
    WHERE id = #{id}
  </when>
  <when test="email != null">
    WHERE email = #{email}
  </when>
  <otherwise>
    WHERE name = 'guest'
  </otherwise>
</choose>
</select>

在此示例中,如果存在 "id" 或 "email" 参数,则仅会执行相应的 WHEN 子句。如果这两个参数都不存在,则会执行 OTHERWISE 子句。

  1. foreach 标签:该标签用于迭代集合并动态生成 SQL 语句。
<update id="updateUsers" parameterType="Map">
UPDATE users SET
<trim suffixOverrides=",">
  <foreach collection="users" item="user" separator=",">
    <set>
      name = #{user.name},
      email = #{user.email}
    </set>
  </foreach>
</trim>
WHERE id IN
<foreach collection="ids" item="id" open="(" close=")" separator=",">
  #{id}
</foreach>
</update>
create table `blog` (
 `id` varchar(50) not null comment `博客id`,
 `title` varchar(100) not null comment `博客标题`,
 `author` varchar(30) not null comment `博客作者`,
 `create_time` datatime not null comment `创建时间`,
 `views` int(30) not null comment `浏览量`
) engine = InnoDB default charset = utf8
public class IDUtil {
    public static String genId(){
        return UUID.randomUUID().toString().replaceAll("-","");
    }
}
import java.util.Date;
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime;
    private int views;
    //set,get....
}
public interface BlogMapper {
}
<?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.da.mapper.BlogMapper">
</mapper>
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--注册Mapper.xml-->
<mappers>
  <mapper resource="mapper/BlogMapper.xml"/>
</mappers>
//新增一个博客
int addBlog(Blog blog);
<insert id="addBlog" parameterType="blog">
    insert into blog (id, title, author, create_time, views)
    values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
//需求1
List<Blog> queryBlogIf(Map map);
<select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from blog where
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</select>

如果 author 等于 null,那么查询语句为 select from user where title=#{title},但是如果title为空呢?那么查询语句为 select from user where and author=#{author},这是错误的 SQL 语句,如何解决呢?

<select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from blog 
    <where>
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </where>
</select>

如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。

int updateBlog(Map map);
<!--注意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>

choose语句

  1. 有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句
List<Blog> queryBlogChoose(Map map);
<select id="queryBlogChoose" parameterType="map" resultType="blog">
    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>

提取SQL片段:

<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 blog
    <where>
        <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
        <include refid="if-title-author"></include>
        <!-- 在这里还可以引用其他的 sql 片段 -->
    </where>
</select>

最好基于 单表来定义 sql 片段,提高片段的可重用性

在 sql 片段中不要包括 where

Foreach

List<Blog> queryBlogForeach(Map map);

collection:指定输入对象中的集合属性

item:每次遍历生成的对象

open:开始遍历时的拼接字符串

close:结束时拼接的字符串

separator:遍历对象之间需要拼接的字符串

<select id="queryBlogForeach" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
            id=#{id}
        </foreach>
    </where>
</select>

缓存,也称为Cache,是指存储在计算机内存中的临时数据。它可以将用户经常查询的数据放在内存中,提高查询效率,从而解决高并发系统的性能问题。使用缓存可以减少与数据库的交互次数,降低系统开销,提高系统效率。

通常情况下适合缓存的数据是那些经常被查询但不经常改变的数据。这样的数据可以被缓存到内存中,使得下一次查询时不需要从磁盘上读取,减少了系统IO开销,从而提高了系统性能。

MyBatis拥有强大的查询缓存特性,可以轻松地进行定制和配置。缓存的使用可以显著提高查询效率。

在MyBatis中,默认情况下只开启了一级缓存(SqlSession级别的本地缓存),而二级缓存需要手动开启和配置。二级缓存是基于namespace级别的缓存,在扩展性方面有很多优势。

为了实现更灵活的缓存功能,MyBatis定义了一个Cache接口,用户可以通过实现这个接口来自定义二级缓存。

一级缓存,也称为本地缓存,是指在与数据库进行同一次会话期间查询到的数据会被放置在本地缓存中。如果后续需要获取相同的数据,直接从缓存中取出,无需再次查询数据库,可以显著提高系统性能。

//根据id查询用户
User queryUserById(@Param("id") int id);
<select id="queryUserById" resultType="user">
    select * from user where id = #{id}
</select>

一级缓存失效的四种情况

一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;

一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!

一级缓存可以提高查询效率,但需要注意以下四种情况可能会导致缓存失效:

  1. SqlSession被关闭或清空:一级缓存的生命周期与SqlSession一致。当SqlSession被关闭或清空时,缓存也将被清空。
  2. 数据库数据发生变化:如果在同一个会话中更新了数据库中的数据,那么与这些数据相关的缓存也会被清空。
  3. 查询不同的数据:如果两次查询参数不同,即使是同样的SQL语句,缓存也会失效。
  4. 手动清空缓存:我们可以通过调用SqlSession的clearCache()方法来手动清空缓存。

sqlSession不同

结论:每个sqlSession中的缓存相互独立

sqlSession相同,查询条件不同

结论:当前缓存中,不存在这个数据

sqlSession相同,两次查询之间执行了增删改操作!

//修改用户
int updateUser(Map map);
<update id="updateUser" parameterType="map">
    update user set name = #{name} where id = #{id}
</update>

结论:因为增删改操作可能会对当前数据产生影响

sqlSession相同,手动清除一级缓存

一级缓存就是一个map

  1. SqlSession被关闭或清空:在一个请求中,我们创建了一个SqlSession用于查询数据。如果在查询完成后没有关闭或清空SqlSession,那么下次查询相同的数据时,将会从缓存中获取数据,而不是再次查询数据库。但是,如果SqlSession被关闭或清空,缓存也会被清空,下次查询相同的数据时,将会再次执行查询SQL。
  2. 数据库数据发生变化:假设我们有一个缓存了所有用户信息的一级缓存。现在我们创建了一个新用户,并提交到数据库保存。由于此时所有与用户相关的缓存都是无效的,所以下次查询用户信息时需要重新查询数据库。
  3. 查询不同的数据:我们创建了一个SqlSession,并使用参数“id=1”查询了一个用户信息。此时缓存中会缓存这个用户信息。接着,我们又创建了一个SqlSession,并使用参数“id=2”查询另一个用户信息,即使这两个查询语句完全相同,但由于传入的参数不同,缓存也会失效,需要重新去数据库查询。
  4. 手动清空缓存:我们可以通过调用SqlSession的clearCache()方法来手动清空缓存。当我们调用clearCache()方法时,缓存中的所有数据都会被清空,下次查询需要重新去数据库查询数据。
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();

// 第一次查询,数据被缓存
User user1 = sqlSession.selectOne("com.example.mapper.UserMapper.selectById", 1L);
System.out.println("第一次查询:" + user1);

// 第二次查询,从缓存中获取数据
User user2 = sqlSession.selectOne("com.example.mapper.UserMapper.selectById", 1L);
System.out.println("第二次查询:" + user2);

// 更新数据库中的数据
User updateUser = new User();
updateUser.setId(1L);
updateUser.setName("newName");
sqlSession.update("com.example.mapper.UserMapper.updateById", updateUser);
sqlSession.commit();

// 第三次查询,缓存失效,需要查询数据库
User user3 = sqlSession.selectOne("com.example.mapper.UserMapper.selectById", 1L);
System.out.println("第三次查询:" + user3);

// 手动清空缓存
sqlSession.clearCache();

// 第四次查询,缓存已经被清空,需要查询数据库
User user4 = sqlSession.selectOne("com.example.mapper.UserMapper.selectById", 1L);
System.out.println("第四次查询:" + user4);

// 关闭SqlSession
sqlSession.close();
  1. 在配置文件 mybatis-config.xml 中开启二级缓存,并在需要使用二级缓存的 Mapper.xml 文件中添加 <cache> 标签来配置缓存策略。

  2. 在实体类中实现 Serializable 接口,以便能够将数据序列化到缓存中。如果实体类不实现 Serializable 接口,则会报 NotSerializableException 的异常。

  3. 在需要使用二级缓存的 Mapper.xml 文件中,使用 <cache-ref> 标签来引用顶层 Mapper.xml 文件中已经定义好的缓存。

  4. 避免在 Mapper.xml 文件中使用动态 SQL,因为动态 SQL 无法被缓存。如果需要使用动态 SQL,可以将它们转换成静态 SQL 来使用。

  5. 注意事项:

    • 二级缓存是基于 namespace 级别的缓存,一个 namespace 对应一个缓存。
    • 如果一个语句中含有任何动态标签(例如:where),那么这个语句就不能被缓存。
    • 如果两个相同的语句使用了不同的参数,那么这两次查询的结果都会被缓存。
    • 缓存可能会占用大量内存,因此在使用缓存的时候需要考虑缓存的生命周期、缓存的清理策略等问题。

开启全局缓存 【mybatis-config.xml】

<setting name="cacheEnabled" value="true"/>
<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

在MyBatis中,会话(session)指的是一个数据库连接对象。在同一个会话中进行的数据库操作共享同一个数据库连接,这样可以提高数据库的性能。

一级缓存(local cache)是指默认开启的基于PerpetualCache实现的本地缓存,在同一个会话中查询到的数据会被缓存在该缓存中,以便后续的查询可以直接从缓存中获取数据,而不需要再次去查询数据库。

二级缓存(second level cache)是指基于Cache接口实现的全局缓存。它可以在多个会话之间共享缓存数据,从而避免了重复查询和浪费资源。只要开启了二级缓存,在同一个Mapper中的查询会先从二级缓存中获取数据,如果缓存中没有,则会从数据库中查询,并将查询结果放入缓存中。当会话提交或关闭时,一级缓存中的数据会被转移到二级缓存中。

因此,这里所说的会话并不是指浏览器的会话,而是指MyBatis中的数据库连接对象。

  • Ehcache是一种广泛使用的java分布式缓存,用于通用缓存;
<!-- 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 namespace = “org.acme.FooMapper” > 
    <cache type = “org.mybatis.caches.ehcache.EhcacheCache” /> 
</mapper>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文