• Mybatis基础


    Mybatis基础

    虽然我们能够通过JDBC来连接和操作数据库,但是哪怕只是完成一个SQL语句的执行,都需要编写大量的代码,更不用说如果我还需要进行实体类映射,将数据转换为我们可以直接操作的实体类型,JDBC很方便,但是还不够方便。

    MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

    MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。

    XML语言概述

    XML语言发明最初是用于数据的存储和传输

    HTML主要用于通过编排来展示数据,而XML主要是存放数据,它更像是一个配置文件

    
    <outer>
      <name>阿伟name>
      <desc>怎么又在玩电动啊desc>
    	<inner type="1">
        <age>10age>
        <sex>sex>
      inner>
    outer>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    一个XML文件存在以下的格式规范:

    • 必须存在一个根节点,将所有的子标签全部包含
    • 可以但不必须包含一个头部声明(主要是可以设定编码格式)
    • 所有的标签必须成对出现,可以嵌套但不能交叉嵌套
    • 区分大小写
    • 标签中可以存在属性,属性的值由单引号或双引号包括

    XML文件也可以使用注释:

    
    
    
    • 1
    • 2

    内容中出现特定字符需要使用转义字符:

    image-20231025082511511

    使用CD来快速创建不解析区域:

    <test>
        <name><><>是一点都不懂哦>>>]]>name>
    test>
    
    • 1
    • 2
    • 3

    使用Mybatis

    导入Mybatis的依赖

    编写Mybatis的配置文件:在项目根目录下新建名为mybatis-config.xml

    
    DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
      <environments default="development">
        <environment id="development">
          <transactionManager type="JDBC"/>
          <dataSource type="POOLED">
            <property name="driver" value="${驱动类(含包名)}"/>
            <property name="url" value="${数据库连接URL}"/>
            <property name="username" value="${用户名}"/>
            <property name="password" value="${密码}"/>
          dataSource>
        environment>
      environments>
    configuration>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    通过进行配置告诉了Mybatis我们链接数据库的一些信息,包括URL、用户名、密码等

    Mybatis对配置文件进行读取并得到一个SqlSessionFactory对象:

    public static void main(String[] args) throws FileNotFoundException {
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(new FileInputStream("mybatis-config.xml"));
        try (SqlSession sqlSession = sqlSessionFactory.openSession(true)){
    			//暂时还没有业务
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的,可以通过SqlSessionFactory来创建多个新的会话

    配合lombok编写实体类:

    import lombok.Data;
    
    @Data
    public class Student {
        int sid;   //名称最好和数据库字段名称保持一致,不然可能会映射失败导致查询结果丢失
        String name;
        String sex;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在根目录下重新创建对象映射器文件TestMapper.xml:

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="TestMapper">
        <select id="selectStudent" resultType="com.test.entity.Student">
            select * from student
        select>
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    其中namespace就是命名空间,每个Mapper都是唯一的,因此需要用一个命名空间来区分,它还可以用来绑定一个接口。

    写入了一个select标签,表示添加一个select操作,同时id作为操作的名称,resultType指定为我们刚刚定义的实体类,表示将数据库结果映射为Student类,然后就在标签中写入我们的查询语句即可。

    在配置文件中添加这个Mapper映射器:

    <mappers>
        <mapper url="file:mappers/TestMapper.xml"/>
        
    mappers>
    
    • 1
    • 2
    • 3
    • 4

    在程序中使用定义好的Mapper:

    public static void main(String[] args) throws FileNotFoundException {
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(new FileInputStream("mybatis-config.xml"));
        try (SqlSession sqlSession = sqlSessionFactory.openSession(true)){
            List<Student> student = sqlSession.selectList("selectStudent");
            student.forEach(System.out::println);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Mybatis非常智能,只需要告诉一个映射关系,就能够直接将查询结果转化为一个实体类

    配置Mybatis

    由于SqlSessionFactory一般只需要创建一次,因此我们可以创建一个工具类来集中创建SqlSession,这样会更加方便一些:

    public class Mybatis {
        private static SqlSessionFactory sqlSessionFactory;
        static {
            try {
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(new FileInputStream("mybatis-config.xml"));
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    
        public static SqlSession getSession(boolean autoCommit) {
            return sqlSessionFactory.openSession(autoCommit);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    public static void main(String[] args) {
        try (SqlSession sqlSession = MybatisUtil.getSession(true)){
            List<Student> student = sqlSession.selectList("selectStudent");
            student.forEach(System.out::println);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    每次都需要去找映射器对应操作的名称,而且还要知道对应的返回类型,再通过SqlSession来执行对应的方法,能不能再方便一点呢?

    通过namespace来绑定到一个接口上,利用接口的特性,我们可以直接指明方法的行为,而实际实现则是由Mybatis来完成。

    public interface TestMapper {
        List<Student> selectStudent();
    }
    
    • 1
    • 2
    • 3

    将Mapper文件的命名空间修改为我们的接口,建议同时将其放到同名包中,作为内部资源:

    <mapper namespace="com.test.mapper.TestMapper">
        <select id="selectStudent" resultType="com.test.entity.Student">
            select * from student
        select>
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    修改一下配置文件中的mapper定义,不使用url而是resource表示是Jar内部的文件:

    <mappers>
        <mapper resource="com/test/mapper/TestMapper.xml"/>
    mappers>
    
    • 1
    • 2
    • 3

    可以直接通过SqlSession获取对应的实现类,通过接口中定义的行为来直接获取结果:

    public static void main(String[] args) throws FileNotFoundException {
        try(SqlSession session = Mybatis.getSession(true)) {//自动提交
            TestMapper testMapper = session.getMapper(TestMapper.class);
            List<Student> selectStudent = testMapper.selectStudent();
            selectStudent.forEach(System.out::println);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    TestMapper是通过动态代理生成的,相当于动态生成了一个实现类,而不是预先定义好的

    配置文件介绍:

    <configuration>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/study"/>
                    <property name="username" value="test"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <mapper resource="com/test/mapper/TestMapper.xml"/>
        </mappers>
    </configuration>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    environments标签指定一个数据库的配置信息,包含连接URL、用户、密码等信息

    实际情况下可能会不止有一个数据库连接信息,比如开发过程中我们一般会使用本地的数据库,而如果需要将项目上传到服务器或是防止其他人的电脑上运行时,我们可能就需要配置另一个数据库的信息

    environments标签上有一个default属性,来指定默认的环境

    也可以在创建工厂时选择环境:

    sqlSessionFactory = new SqlSessionFactoryBuilder()
            .build(new FileInputStream("mybatis-config.xml"), "环境ID");
    
    • 1
    • 2

    可以给类型起一个别名:

    
    <typeAliases>
        <typeAlias type="com.test.entity.Student" alias="Student"/>
    typeAliases>
    
    • 1
    • 2
    • 3
    • 4

    直接让Mybatis去扫描一个包,并将包下的所有类自动起别名(别名为首字母大小写都行的类名):

    <typeAliases>
        <package name="com.test.entity"/>
    typeAliases>
    
    • 1
    • 2
    • 3

    也可以为指定实体类添加一个注解,来指定别名:

    @Data
    @Alias("lbwnb")
    public class Student {
        private int sid;
        private String name;
        private String sex;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    增删改查

    resultType可以被映射到一个Map上:

    <select id="selectStudent" resultType="Map">
        select * from student
    select>
    
    • 1
    • 2
    • 3
    public interface TestMapper {
        List<Map> selectStudent();
    }
    
    • 1
    • 2
    • 3

    Map中就会以键值对的形式来存放这些结果了

    通过设定一个resultType属性,让Mybatis知道查询结果需要映射为哪个实体类,要求字段名称保持一致。

    自定义resultMap来设定映射规则:

    <resultMap id="Test" type="Student">
            <result column="id" property="id"/>
            <result column="name" property="name"/>
    resultMap>
    <select id="selectStudent" resultMap="Test">
        select * from student
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    column表示数据库字段名称,property表示实体类字段名称

    如果一个类中存在多个构造方法,那么很有可能会出现错误

    使用constructor标签来指定构造方法:

    <resultMap id="test" type="Student">
        <constructor>
            <arg column="sid" javaType="Integer"/>
            <arg column="name" javaType="String"/>
        constructor>
    resultMap>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    指定构造方法后,若此字段被填入了构造方法作为参数,将不会通过反射给字段单独赋值,而构造方法中没有传入的字段,依然会被反射赋值

    如果只有一个构造函数或者全量构造函数都会先调用一遍构造函数,然后使用反射进行字段单独赋值

    数据库中存在一个带下划线的字段,我们可以通过设置让其映射为以驼峰命名的字段,比如my_test映射为myTest

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    settings>
    
    • 1
    • 2
    • 3

    条件查询:想通过sid字段来通过学号查找信息

    Student getStudentBySid(int sid);
    
    • 1
    <select id="getStudentBySid" parameterType="int" resultType="Student">
        select * from student where sid = #{sid}
    select>
    
    • 1
    • 2
    • 3

    通过使用#{xxx}或是${xxx}来填入我们给定的属性,实际上Mybatis本质也是通过PreparedStatement首先进行一次预编译,有效地防止SQL注入问题,但是如果使用${xxx}就不再是通过预编译,而是直接传值,因此我们一般都使用#{xxx}来进行操作。

    使用parameterType属性来指定参数类型(非必须,可以不用,推荐不用)

    插入、更新和删除操作:

    <insert id="addStudent" parameterType="Student">
        insert into student(name, sex) values(#{name}, #{sex})
    insert>
    
    • 1
    • 2
    • 3
    int addStudent(Student student);
    
    • 1

    复杂查询

    @Data
    public class Teacher {
        int tid;
        String name;
        List<Student> studentList;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    一个老师可以教授多个学生,那么能否一次性将老师的学生全部映射给此老师的对象呢

    映射为Teacher对象时,同时将其教授的所有学生一并映射为List列表,显然这是一种一对多的查询,那么这时就需要进行复杂查询了。

    现在需要使用resultMap来自定义映射规则:

    <select id="getTeacherByTid" resultMap="asTeacher">
            select *, teacher.name as tname from student inner join teach on student.sid = teach.sid
                                  inner join teacher on teach.tid = teacher.tid where teach.tid = #{tid}
    select>
    
    <resultMap id="asTeacher" type="Teacher">
        <id column="tid" property="tid"/>
        <result column="tname" property="name"/>
        <collection property="studentList" ofType="Student">
            <id column="sid" property="sid"/>
            <result column="name" property="name"/>
            <result column="sex" property="sex"/>
        collection>
    resultMap>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    id标签用于在多条记录中辨别是否为同一个对象的数据

    通过使用collection来表示将得到的所有结果合并为一个集合,比如上面的数据中每个学生都有单独的一条记录,因此tid相同的全部学生的记录就可以最后合并为一个List,得到最终的映射结果,当然,为了区分,最好也设置一个id,只不过这个例子中可以当做普通的result使用。

    @Data
    @Accessors(chain = true)
    public class Student {
        private int sid;
        private String name;
        private String sex;
        private Teacher teacher;
    }
    
    @Data
    public class Teacher {
        int tid;
        String name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    可以使用resultMap来实现:

    <resultMap id="test2" type="Student">
        <id column="sid" property="sid"/>
        <result column="name" property="name"/>
        <result column="sex" property="sex"/>
        <association property="teacher" javaType="Teacher">
            <id column="tid" property="tid"/>
            <result column="tname" property="name"/>
        association>
    resultMap>
    <select id="selectStudent" resultMap="test2">
        select *, teacher.name as tname from student left join teach on student.sid = teach.sid
                                                     left join teacher on teach.tid = teacher.tid
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    事务操作

    获取SqlSession关闭自动提交来开启事务模式:

    public static void main(String[] args) {
        try (SqlSession sqlSession = MybatisUtil.getSession(false)){
            TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
            testMapper.addStudent(new Student().setSex("男").setName("小王"));
            testMapper.selectStudent().forEach(System.out::println);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    提交事务:

    sqlSession.commit();
    
    • 1

    事务回滚:

    sqlSession.rollback();
    
    • 1

    动态 SQL

    动态 SQL 是 MyBatis 的强大特性之一。可以根据不同条件拼接 SQL 语句

    if

    <select id="findActiveBlogWithTitleLike"
         resultType="Blog">
      SELECT * FROM BLOG
      WHERE state = ‘ACTIVE’
      <if test="title != null">
        AND title like #{title}
      if>
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果

    choose、when、otherwise

    有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

    <select id="findActiveBlogLike"
         resultType="Blog">
      SELECT * FROM BLOG WHERE state = ‘ACTIVE’
      <choose>
        <when test="title != null">
          AND title like #{title}
        when>
        <when test="author != null and author.name != null">
          AND author_name like #{author.name}
        when>
        <otherwise>
          AND featured = 1
        otherwise>
      choose>
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    缓存机制

    Mybatis内置了一个缓存机制,我们查询时,如果缓存中存在数据,那么我们就可以直接从缓存中获取,而不是再去向数据库进行请求。

    Mybatis存在一级缓存和二级缓存,默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存(一级缓存无法关闭,只能调整)

    image-20231025220821001

    一级缓存,在进行DML操作后,会使得缓存失效。

    当前会话结束后,也会清理全部的缓存,因为已经不会再用到了。

    一级缓存只针对于单个会话,多个会话之间不相通。

    public static void main(String[] args) throws InterruptedException {
        try (SqlSession sqlSession = MybatisUtil.getSession(true)){
            TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
            Student student1 = testMapper.getStudentBySid(1);
            Student student2 = testMapper.getStudentBySid(1);
            System.out.println(student1 == student2);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    一级缓存给我们提供了很高速的访问效率,但是它的作用范围实在是有限,如果一个会话结束,那么之前的缓存就全部失效了

    如果希望缓存能够扩展到所有会话都能使用,可以通过二级缓存来实现

    二级缓存默认是关闭状态,要开启二级缓存,需要在映射器XML文件中添加:

    <cache/>
    
    • 1
      //只读缓存
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果我不希望某个方法开启缓存呢?我们可以添加useCache属性来关闭缓存:

    <select id="getStudentBySid" resultType="Student" useCache="false">
        select * from student where sid = #{sid}
    select>
    
    • 1
    • 2
    • 3

    可以使用flushCache="false"在每次执行后都清空缓存,通过这这个我们还可以控制DML操作完成之后不清空缓存。

    <select id="getStudentBySid" resultType="Student" flushCache="true">
        select * from student where sid = #{sid}
    select>
    
    • 1
    • 2
    • 3

    添加了二级缓存之后,会先从二级缓存中查找数据,当二级缓存中没有时,才会从一级缓存中获取,当一级缓存中都还没有数据时,才会请求数据库

    如果存在多台服务器或者是多个程序都在使用Mybatis操作同一个数据库,并且都开启了缓存会存在缓存一致性问题,此时得关闭Mybatis的缓存来保证一致性:

    <settings>
        <setting name="cacheEnabled" value="false"/>
    settings>
    
    • 1
    • 2
    • 3
    <select id="getStudentBySid" resultType="Student" useCache="false" flushCache="true">
        select * from student where sid = #{sid}
    select>
    
    • 1
    • 2
    • 3

    注解开发

    直接使用注解来实现XML中定义映射规则和SQL语句并绑定到一个接口的方法定义上

    @Insert("insert into student(name, sex) values(#{name}, #{sex})")
    int addStudent(Student student);
    
    • 1
    • 2

    修改一下配置文件中的映射器注册:

    <mappers>
        <mapper class="com.test.mapper.MyMapper"/>
        
    mappers>
    
    • 1
    • 2
    • 3
    • 4

    进行自定义映射规则:

    @Results({
            @Result(id = true, column = "sid", property = "sid"),
            @Result(column = "sex", property = "name"),
            @Result(column = "name", property = "sex")
    })
    @Select("select * from student")
    List<Student> getAllStudent();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    @Result注解数组,每个@Result注解都都一个单独的字段配置

    复杂查询:

    @Results({
            @Result(id = true, column = "tid", property = "tid"),
            @Result(column = "name", property = "name"),
            @Result(column = "tid", property = "studentList", many =
                @Many(select = "getStudentByTid")
            )
    })
    @Select("select * from teacher where tid = #{tid}")
    Teacher getTeacherBySid(int tid);
    
    @Select("select * from student inner join teach on student.sid = teach.sid where tid = #{tid}")
    List<Student> getStudentByTid(int tid);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    多出了一个子查询,而这个子查询是单独查询该老师所属学生的信息,而子查询结果作为@Result注解的一个many结果,代表子查询的所有结果都归入此集合中(也就是之前的collection标签)

    @Result也提供了@One子注解来实现一对一的关系表示,类似于之前的assocation标签:

    @Results({
            @Result(id = true, column = "sid", property = "sid"),
            @Result(column = "sex", property = "name"),
            @Result(column = "name", property = "sex"),
            @Result(column = "sid", property = "teacher", one =
                @One(select = "getTeacherBySid")
            )
    })
    @Select("select * from student")
    List<Student> getAllStudent();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    直接使用注解编写SQL语句但是我希望映射规则依然使用XML来实现,提供了@ResultMap注解,直接指定ID即可:

    @ResultMap("test")
    @Select("select * from student")
    List<Student> getAllStudent();
    
    • 1
    • 2
    • 3

    通过@ConstructorArgs注解来指定构造方法:

    @ConstructorArgs({
            @Arg(column = "sid", javaType = int.class),
            @Arg(column = "name", javaType = String.class)
    })
    @Select("select * from student where sid = #{sid} and sex = #{sex}")
    Student getStudentBySidAndSex(@Param("sid") int sid, @Param("sex") String sex);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    当参数列表中出现两个以上的参数时,添加@Param来指定参数名称:

    @Select("select * from student where sid = #{sid} and sex = #{sex}")
    Student getStudentBySidAndSex(@Param("sid") int sid, @Param("sex") String sex);
    
    • 1
    • 2

    通过参数名称.属性的方式去让Mybatis知道我们要用的是哪个属性:

    @Insert("insert into student(sid, name, sex) values(#{sid}, #{student.name}, #{student.sex})")
    int addStudent(@Param("sid") int sid, @Param("student")  Student student);
    
    • 1
    • 2

    注解控制缓存机制:

    @CacheNamespace(readWrite = false)
    public interface MyMapper {
    
        @Select("select * from student")
        @Options(useCache = false)
        List<Student> getAllStudent();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Sex(@Param(“sid”) int sid, @Param(“sex”) String sex);

    
    当参数列表中出现两个以上的参数时,添加`@Param`来指定参数名称:
    
    ~~~java
    @Select("select * from student where sid = #{sid} and sex = #{sex}")
    Student getStudentBySidAndSex(@Param("sid") int sid, @Param("sex") String sex);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    通过参数名称.属性的方式去让Mybatis知道我们要用的是哪个属性:

    @Insert("insert into student(sid, name, sex) values(#{sid}, #{student.name}, #{student.sex})")
    int addStudent(@Param("sid") int sid, @Param("student")  Student student);
    
    • 1
    • 2

    注解控制缓存机制:

    @CacheNamespace(readWrite = false)
    public interface MyMapper {
    
        @Select("select * from student")
        @Options(useCache = false)
        List<Student> getAllStudent();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用@CacheNamespace注解直接定义在接口上即可,然后我们可以通过使用@Options来控制单个操作的缓存启用。

  • 相关阅读:
    一遍博客带你上手Servlet
    MATLAB R2023a完美激活版(附激活补丁)
    Edexcel A-Level化学真题讲解(2)
    使用Watchtower实现Docker容器自动更新
    Doris:使用表函数explode实现array字段列转行
    海康设备、LiveNVR等通过GB35114国密协议对接到LiveGBS GB28181/GB35114平台的详细操作说明
    大数据学习(10)-Explain详解
    算法通关村之堆结构(实战训练)经典问题:理解堆的构造、插入、删除过程,查找数组中第K大的元素、堆排序、合并K个有序链表
    [ValueError: not enough values to unpack (expected 3, got 2)]
    internship:术语了解及着手写接口
  • 原文地址:https://blog.csdn.net/CS_z_jun/article/details/134097721