Mybatis

Mybatis

1、Mybatis简介

1.1 什么是MyBatis

  • MyBatis 是一款优秀的持久层框架
  • MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的过程
  • MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 实体类 【Plain Old Java Objects,普通的 Java对象】映射成数据库中的记录。
  • MyBatis 本是apache的一个开源项目ibatis, 2010年这个项目由apache 迁移到了google code,并 且改名为MyBatis 。
  • 2013年11月迁移到Github .
  • Mybatis官方文档 : http://www.mybatis.org/mybatis-3/zh/index.html
  • GitHub : https://github.com/mybatis/mybatis-3

1.2 为什么需要Mybatis

  • Mybatis就是帮助程序猿将数据存入数据库中 , 和从数据库中取数据 .
  • 传统的jdbc操作 , 有很多重复代码块 .比如 : 数据取出时的封装 , 数据库的建立连接等等… , 通过框 架可以减少重复代码,提高开发效率 .
  • MyBatis 是一个半自动化的ORM框架 (Object Relationship Mapping) -->对象关系映射
  • 所有的事情,不用Mybatis依旧可以做到,只是用了它,所有实现会更加简单!技术没有高低之 分,只有使用这个技术的人有高低之别
  • MyBatis的优点
    • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个 sql映射文件就可以了,易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的 设计思路和实现。
    • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统 一管理和优化。通过sql语句可以满足操作数据库的所有需求。
    • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设 计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
    • 提供xml标签,支持编写动态sql。
    • 最重要的一点,使用的人多!公司需要!

2、Mybatis的简单使用

2.1 使用流程

  1. 搭建数据库环境

    CREATE DATABASE `mybatis`;
    
    USE `mybatis`;
    
    DROP TABLE IF EXISTS `user`;
    
    CREATE TABLE `user` (
    	`id` int(20) NOT NULL,
        `name` varchar(30) DEFAULT NULL,
    	`pwd` varchar(30) DEFAULT NULL,
    	PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    insert into `user`(`id`,`name`,`pwd`) values (1,'狂神','123456'),(2,'张
    三','abcdef'),(3,'李四','987654');
    
    
  2. 导入Mybatis相关依赖(或者直接导入jar包)

    <!--mysql驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.25</version>
            </dependency>
    <!--mybatis-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.2</version>
            </dependency>
    
  3. resources目录下编写Mybatis核心配置文件mybatis-config.xml(参考官方帮助文档)

    <?xml version="1.0" encoding="UTF-8" ?>
    <!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="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
    </configuration>
    
  4. 编写Mybatis工具类(将一些通用方法抽取出来,提高编程效率)

    package com.yirui.utils;
    
    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 java.io.IOException;
    import java.io.InputStream;
    
    public class MybatisUtils {
        private static SqlSessionFactory sqlSessionFactory;
    
        static {
            try {
                String resource = "mybatis-config.xml";
                InputStream inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        //通过工厂获取sqlSession
        public static SqlSession getSession(){
            return sqlSessionFactory.openSession();
        }
    }
    
  5. 创建实体类

    package com.yirui.pojo;
    
    public class User {
        private int id;
        private String name;
        private String pwd;
    
        public User() {
        }
    
        public User(int id, String name, String pwd) {
            this.id = id;
            this.name = name;
            this.pwd = pwd;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", pwd='" + pwd + '\'' +
                    '}';
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getPwd() {
            return pwd;
        }
    
        public void setPwd(String pwd) {
            this.pwd = pwd;
        }
    }
    
    
  6. 编写对应Mapper接口类

    package com.yirui.mapper.user;
    
    import com.yirui.pojo.User;
    
    import java.util.List;
    
    public interface UserMapper {
        List<User> selectAllUser();//查询所有用户
        //根据ID查询用户
        User selectUserById(int id);
        //增加用户
        int addUser(User user);
        //删除用户
        int deleteUser(int id);
        //修改用户
        int updateUser(User user);
    }
    
  7. 编写对应Mapper.xml配置文件

    • 建议与Mapper接口同路径,同名称,后续可以减少许多不必要的麻烦
    • namespace用来绑定他所对应的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.yirui.mapper.user.UserMapper">
        <select id="selectAllUser" resultType="com.yirui.pojo.User">
            select * from user
        </select>
    <!--查询用户-->
        <select id="selectUserById" parameterType="int" resultType="com.yirui.pojo.User">
            select * from user where id = #{id}
        </select>
    <!--增加用户-->
        <insert id="addUser" parameterType="com.yirui.pojo.User">
            insert into user(id,name,pwd) values (#{id},#{name},#{pwd})
        </insert>
    <!--删除用户-->
        <delete id="deleteUser" parameterType="int">
            delete from user where id = #{id}
        </delete>
    <!--修改用户-->
        <update id="updateUser" parameterType="com.yirui.pojo.User">
            update user set name = #{name},pwd = #{pwd} where id = #{id}
        </update>
    </mapper>
    
  8. 在Mybatis核心配置文件中注册

    <!--    每一个mapper.xml都需要在mybatis核心配置文件中注册-->
        <mappers>
            <mapper resource="com/yirui/mapper/user/UserMapper.xml"/>
        </mappers>
    
  9. 编写测试类测试(使用Test注解前记得导入Junit依赖)

    1. 通过工具类获取SqlSession

    2. 通过SqlSession获取对应的Mapper

    3. 调用Mapper中的方法

    4. 如果是增删改操作则需要提交事务,或者将SqlSession设置为自动提交事务

      //获取SqlSession连接
      public static SqlSession getSession(){
      return getSession(true); //事务自动提交
      }
      public static SqlSession getSession(boolean flag){
      return sqlSessionFactory.openSession(flag);
      

    }

    
    5. 关闭SqlSession
    
    ```xml
    <!--Junit-->
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <version>4.12</version>
             <scope>test</scope>
         </dependency>
    
    package com.yirui.mapper;
    
    import com.yirui.mapper.user.UserMapper;
    import com.yirui.pojo.User;
    import com.yirui.utils.MybatisUtils;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    import java.util.List;
    
    public class UserMapperTest {
        @Test
        public void selectAllUser(){
            //1.获取SqlSession
            SqlSession session = MybatisUtils.getSession();
            //2.通过SqlSession获取Mapper
            UserMapper mapper = session.getMapper(UserMapper.class);
            //3.调用Mapper方法
            List<User> userList = mapper.selectAllUser();
            for (User user : userList) {
                System.out.println(user.toString());
            }
            //4.关闭SqlSession
            session.close();
        }
    
        @Test
        public void selectUserById(){
            //1.获取SqlSession
            SqlSession session = MybatisUtils.getSession();
            //2.通过SqlSession获取Mapper
            UserMapper mapper = session.getMapper(UserMapper.class);
            //3.调用Mapper方法
            User user = mapper.selectUserById(1);
            System.out.println(user.toString());
            //4.关闭SqlSession
            session.close();
        }
    
        @Test
        public void addUser(){
            //1.获取SqlSession
            SqlSession session = MybatisUtils.getSession();
            //2.通过SqlSession获取Mapper
            UserMapper mapper = session.getMapper(UserMapper.class);
            //3.调用Mapper方法
            int flag = mapper.addUser(new User(6,"熊六","111111"));
            if (flag > 0){
                System.out.println("插入成功");
            }
            //4.提交事务
            session.commit();
            //5.关闭SqlSession
            session.close();
        }
    
        @Test
        public void deleteUser(){
            //1.获取SqlSession
            SqlSession session = MybatisUtils.getSession();
            //2.通过SqlSession获取Mapper
            UserMapper mapper = session.getMapper(UserMapper.class);
            //3.调用Mapper方法
            int flag = mapper.deleteUser(6);
            if (flag > 0){
                System.out.println("删除成功");
            }
            //4.提交事务
            session.commit();
            //5.关闭SqlSession
            session.close();
        }
    
        @Test
        public void updateUser(){
            //1.获取SqlSession
         SqlSession session = MybatisUtils.getSession();
            //2.通过SqlSession获取Mapper
         UserMapper mapper = session.getMapper(UserMapper.class);
            //3.调用Mapper方法
            int flag = mapper.updateUser(new User(6,"熊六","123456"));
            if (flag > 0){
                System.out.println("修改成功");
            }
            //4.提交事务
            session.commit();
            //5.关闭SqlSession
            session.close();
        }
    }
    

    可能出现的问题说明:Maven静态资源过滤问题(参照JavaWeb中的Pom文件)

        <build>
            <resources>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>true</filtering>
                </resource>
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>true</filtering>
                </resource>
            </resources>
        </build>
    

2.2 CRUD操作

所有的代码在上述使用流程中均已给出,这里主要对其进行解释

select标签

  • select标签是mybatis中最常用的标签之一
  • select语句有很多属性,可以详细配置每一条SQL语句
    • id
    • 命名空间中唯一的标识符
    • Mapper接口中的方法名与Mapper.xml文件中的SQL语句ID 相对应
    • parameterType
      • 传入SQL语句的参数类型 。
    • resultType
      • SQL语句返回值类型。【完整的类名或者别名】

insert标签、update标签、delete标签的属性同select标签一样,但是不需要指定resltType

扩展用法:万能的Map

根据密码和名字查询用户(需要往sql语句中传入多个参数怎么处理)

  1. 直接在接口方法中的参数加上@Param注解,在进行sql语句编写时直接取@Param中设置的值即可,不用指定parameterType

    //通过密码和名字查询用户
    User selectUserByNP(@Param("username") String username,@Param("pwd") String
    pwd);
    /*
    <select id="selectUserByNP" resultType="com.yirui.pojo.User">
    	select * from user where name = #{username} and pwd = #{pwd}
    </select>
    */
    
  2. 使用万能的Map,在接口方法中直接传入Map参数,在编写sql语句时之间按照Map中的Key取对应的值即可,这时需要指定parameterType=“map”

    User selectUserByNP2(Map<String,Object> map);
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("username","小明");
    map.put("pwd","123456");
    User user = mapper.selectUserByNP2(map);
    
    <select id="selectUserByNP2" parameterType="map"
    resultType="com.yirui.pojo.User">
    	select * from user where name = #{username} and pwd = #{pwd}
    </select>
    

3、常用配置解析

3.1 核心配置文件

  • mybatis-config.xml 系统核心配置文件
  • MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。
  • 能配置的内容如下(配置的顺序一定是按照以下顺序,前后不能颠倒,不然一定报错):
configuration(配置)
    properties(属性)
    settings(设置)
    typeAliases(类型别名)
    typeHandlers(类型处理器)
    objectFactory(对象工厂)
    plugins(插件)
    environments(环境配置)
        environment(环境变量)
            transactionManager(事务管理器)
            dataSource(数据源)
    databaseIdProvider(数据库厂商标识)
    mappers(映射器)

3.2 environments元素

    <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/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
  • 可以配置MyBatis的多套运行环境,将SQL映射到多个不同的数据库上,但是必须指定其中一个为默认运行 环境(通过default指定)

  • 子元素节点:environment

    • 具体的一套环境,通过设置id进行区别,id保证唯一!
  • 子元素节点:transactionManager - [ 事务管理器JDBC和MANAGED二选一 ]

    <!-- 语法 -->
    <transactionManager type="[ JDBC | MANAGED ]"/>
    
  • 子元素节点:数据源(dataSource)

    • dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
    • 数据源是必须配置的。
    • 有三种内建的数据源类型:
      • unpooled: 这个数据源的实现只是每次被请求时打开和关闭连接。
      • pooled: 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来 , 这是一种使得 并发 Web 应用快速响应请求的流行处理方式。
      • jndi:这个数据源的实现是为了能在如 Spring 或应用服务器这类容器中使用,容器可以 集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
    • 数据源也有很多第三方的实现,比如dbcp,c3p0,druid等等…

3.3 mappers元素

​ 映射器用来定义映射sql语句的文件,通俗的来说就是告诉mybatis应该去哪里找映射文件,你可以使用相对于类路径的资源引用, 或完 全限定资源定位符(包括 file:/// 的 URL),或类名和包名等。映射器是MyBatis中最核心 的组件之一,在MyBatis 3之前,只支持xml映射器,即:所有的SQL语句都必须在xml文件中配 置。而从MyBatis 3开始,还支持接口映射器,这种映射器方式允许以Java代码的方式注解定义SQL 语句,非常简洁。

  • 引入资源方式

    <!-- 使用相对于类路径的资源引用 -->
    <mappers>
    <mapper resource="org/mybatis/builder/PostMapper.xml"/>
    </mappers>
    <!-- 使用完全限定资源定位符(URL) -->
    <mappers>
    <mapper url="file:///var/mappers/AuthorMapper.xml"/>
    </mappers>
    <!--
    使用映射器接口实现类的完全限定类名
    需要配置文件名称和接口名称一致,并且位于同一目录下
    -->
    <mappers>
    <mapper class="org.mybatis.builder.AuthorMapper"/>
    </mappers>
    <!--
    将包内的映射器接口实现全部注册为映射器
    但是需要配置文件名称和接口名称一致,并且位于同一目录下
    -->
    <mappers>
    <package name="org.mybatis.builder"/>
    </mappers>
    

3.4 Properties优化

数据库这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递。具体的官方文档

  1. 在资源目录下新建一个db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?
useSSL=true&useUnicode=true&characterEncoding=utf8
username=root
password=123456
  1. 将文件导入properties 配置文件
<properties resource="db.properties"/>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

3.5 typeAliases优化

类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全 限定名的冗余。

<!--配置别名,注意顺序-->
<typeAliases>
	<typeAlias type="com.yirui.pojo.User" alias="User"/>
</typeAliases>

当这样配置时, User可以用在任何使用 com.yirui.pojo.User的地方。 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases>
	<package name="com.yirui.yirui"/>
</typeAliases>

每一个在包 com.yirui.pojo 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的 非限定类名来作为它的类型别名。 若有注解,则别名为其注解值。见下面的例子:

@Alias("user")
public class User {
	...
}

其他不常用配置自行查阅官方文档

4、生命周期和作用域

Mybatis执行过程:

image-20220312174545058

作用域理解:

  • SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后, SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中, 而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是 局部方法变量)
  • SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。
  • 由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导 致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。
  • 因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。所以 说 SqlSessionFactory 的最佳作用域是应用作用域
  • 如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接 (Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这 条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭。 所以SqlSession 的最佳的作用域是请求或方法作用域==。

5、ResultMap

要解决的问题:属性名和字段不一致

5.1 查询为Null问题

​ 当数据库的字段名和Java中实体类设计时指定的字段名不一致时,会导致不一致的字段查询结果为空。这和mybatis的自动映射机制有关:

  • select * from user where id = #{id} 可以看做 select id,name,pwd from user where id = #{id}
  • mybatis会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 去对应的实体类中查找 相应列名的set方法设值 , 由于找不到setPwd() , 所以password返回null ; 【自动映射】

5.2 解决方案

  1. 为列名指定别名 , 别名和java实体类的属性名一致 .
<select id="selectUserById" resultType="User">
	select id , name , pwd as password from user where id = #{id}
</select>
  1. 使用结果集映射->ResultMap 【推荐】
<resultMap id="UserMap" type="User">
<!-- id为主键 -->
	<id column="id" property="id"/>
<!-- column是数据库表的列名 , property是对应实体类的属性名 -->
	<result column="name" property="name"/>
	<result column="pwd" property="password"/>
</resultMap>
<select id="selectUserById" resultMap="UserMap">
	select id , name , pwd from user where id = #{id}
</select>

5.3 ResultMap

5.3.1 自动映射
  • resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来。
  • 实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同 等功能的长达数千行的代码。
  • ResultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语 句只需要描述它们的关系就行了。

你已经见过简单映射语句的示例了,但并没有显式指定 resultMap 。比如:

<select id="selectUserById" resultType="map">
	select id , name , pwd
	from user
	where id = #{id}
</select>

上述语句只是简单地将所有的列映射到 HashMap 的键上,这由 resultType 属性指定。虽然在 大部分情况下都够用,但是 HashMap 不是一个很好的模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为模型。 ResultMap 最优秀的地方在于,虽然你已经对它相当了解了,但是根本就不需要显式地用到他们。

5.3.2 手动映射
  1. 设置返回值类型为resultMap
  2. 编写resultMap,实现手动映射(同5.2的第二种解决方案

如果世界总是这么简单就好了。但是肯定不是的,数据库中,存在一对多,多对一的情况,我们之后会 使用到一些高级的结果集映射,association,collection这些,我们将在之后讲解。

6、使用注解开发

  • mybatis最初配置信息是基于 XML ,映射语句(SQL)也是定义在 XML 中的。而到MyBatis 3提供了 新的基于注解的配置。不幸的是,Java 注解的的表达力和灵活性十分有限。最强大的 MyBatis 映 射并不能用注解来构建

  • sql 类型主要分成 :

    • @select ()
    • @update ()
    • @Insert ()
    • @delete ()

【注意】利用注解开发就不需要mapper.xml映射文件了 ,但仍然需要在mybatis核心配置文件中绑定mapper.

  1. 添加注解

    //查询全部用户
    @Select("select id,name,pwd password from user")
    public List<User> getAllUser();
    
  2. mapper绑定

    <!--使用class绑定接口-->
    <mappers>
    	<mapper class="com.yirui.mapper.UserMapper"/>
    </mappers>
    
    
  3. 测试结果

    @Test
    public void testGetAllUser() {
        SqlSession session = MybatisUtils.getSession();
        //本质上利用了jvm的动态代理机制
        UserMapper mapper = session.getMapper(UserMapper.class);
        List<User> users = mapper.getAllUser();
        for (User user : users){
        	System.out.println(user);
        }
    	session.close();
    }
    
    
  4. debug查看本质

image-20220312214127005

本质上利用了jvm的动态代理机制

image-20220312214204363

Mybatis详细的执行流程

image-20220312214438552

7、一些细节补充

7.1 关于@Param

@Param注解用于给方法参数起一个名字。以下是总结的使用原则:

  • 在方法只接受一个参数的情况下,可以不使用@Param。
  • 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
  • 如果参数是 JavaBean , 则不能使用@Param。
  • 不使用@Param注解时,参数只能有一个,并且是Javabean。

7.2 #与$的区别

  1. #{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】

    INSERT INTO user (name) VALUES (#{name});
    INSERT INTO user (name) VALUES (?);
    
  2. ${} 的作用是直接进行字符串替换.

    INSERT INTO user (name) VALUES ('${name}');
    INSERT INTO user (name) VALUES ('yirui');
    

8、多对一的处理(难点)

多对一的理解:

  • 多个学生对应一个老师
  • 如果对于学生这边,就是一个多对一的现象,即从学生这边关联一个老师!

8.1 环境搭建

  1. 搭建数据库

    image-20220312220627645

    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');
    
    
  2. 编写实体类

    • 导入Lombok依赖(偷懒用)

      <!-- 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;
      }
      
      
  3. 编写Mapper接口(无论有没有需求,都应该写上,保持代码规范的同时也以备后来之需)

    public interface StudentMapper {
    }
    
    public interface TeacherMapper {
    }
    
  4. 编写Mapper接口对印的Mapper.xml配置文件(无论有没有需求,都应该写上,保持代码规范的同时也以备后来之需)

    <?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.yirui.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.yirui.mapper.TeacherMapper">
    </mapper>
    

8.2 按嵌套查询方式处理

  1. 给StudentMapper接口增加方法

    //获取所有学生及对应老师的信息
    public List<Student> getStudents();
    
  2. 编写对应的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.yirui.mapper.StudentMapper">
    <!--
    需求:获取所有学生及对应老师的信息
    思路:
    1. 获取所有学生的信息
    2. 根据获取的学生信息的老师ID->获取该老师的信息
    3. 思考问题,这样学生的结果集中应该包含老师,该如何处理呢,数据库中我们一般
    使用关联查询?
    1. 做一个结果集映射:StudentTeacher
    2. StudentTeacher结果集的类型为 Student
    3. 学生中老师的属性为teacher,对应数据库中为tid。
    多个 [1,...)学生关联一个老师=> 一对一,一对多
    4. 查看官网找到:association – 一个复杂类型的关联;使用它来处理关联查
    询
    -->
    <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>
    <!--
    这里传递过来的id,只有一个属性的时候,下面可以写任何值
    association中column多参数配置:
    column="{key=value,key=value}"
    其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的
    字段名。
    -->
    <select id="getTeacher" resultType="teacher">
    select * from teacher where id = #{id}
    </select>
    </mapper>
    
  3. Mybatis配置文件中注册

8.3 按照结果嵌套处理

步骤同8.2一致,只有mapper.xml配置文件有序别

<!--
按查询结果嵌套处理
思路:
1. 直接查询出结果,进行结果集的映射
-->
<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>

8.4 小结

  • 按照查询进行嵌套处理就像SQL中的子查询
  • 按照结果进行嵌套处理就像SQL中的联表查询

9、一对多的处理(难点)

一对多的理解:

  • 一个老师拥有多个学生
  • 如果对于老师这边,就是一个一对多的现象,即从一个老师下面拥有一群学生(集合)!

9.1 环境搭建

注意:步骤同8.1,只有实体类发生了改变

@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;
}

9.2 按结果嵌套处理

多余的步骤同8,这里只列出mapper.xml

<mapper namespace="com.kuang.mapper.TeacherMapper">
    <!--
    思路:
    1. 从学生表和老师表中查出学生id,学生姓名,老师姓名
    2. 对查询出来的操作做结果集映射
        1. 集合的话,使用collection!
            JavaType和ofType都是用来指定对象类型的
            JavaType是用来指定pojo中属性的类型
            ofType指定的是映射到list集合属性中pojo的类型。
    -->
    <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>

9.3 按查询嵌套处理

<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>

9.4 小结

  1. 关联-association
  2. 集合-collection
  3. 所以association是用于一对一和多对一,而collection是用于一对多的关系
  4. JavaType和ofType都是用来指定对象类型的
    • JavaType是用来指定pojo中属性的类型
    • ofType指定的是映射到list集合属性中pojo的类型。

10、动态SQL

10.1 介绍

什么是动态SQL:动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句.

官网描述:
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你
就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意
去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语
句中的强大的动态 SQL 语言得以改进这种情形。
动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有
很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。
MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。
-------------------------------
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
-------------------------------

​ 我们之前写的 SQL 语句都比较简单,如果有比较复杂的业务,我们需要写复杂的 SQL 语句,往往需 要拼接,而拼接 SQL ,稍微不注意,由于引号,空格等缺失可能都会导致错误。

​ 那么怎么去解决这个问题呢?这就要使用 mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同 时,也大大提高了开发人员的效率。

10.2 环境搭建

新建一个数据库表:blog

字段:id,title,author,create_time,views

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

  1. 创建mybatis基础工程

  2. IDutil工具类

    public class IDUtil {
        public static String genId(){
        	return UUID.randomUUID().toString().replaceAll("-","");
        }
    }
    
  3. 实体类编写

    import java.util.Date;
    @Data
    public class Blog {
        private String id;
        private String title;
        private String author;
        private Date createTime;
        private int views;
        //set,get....
    }
    
    
  4. 编写Mapper接口及xml文件

    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.kuang.mapper.BlogMapper">
    </mapper>
    
  5. mybatis核心配置文件,下划线驼峰自动转换

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--注册Mapper.xml-->
<mappers>
	<mapper resource="mapper/BlogMapper.xml"/>
</mappers>
  1. 插入初始数据

10.3 if语句

需求:根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则 根据作者名来查询

  1. 编写接口类

    //需求1
    List<Blog> queryBlogIf(Map map);
    
  2. 编写sql语句

    <!--需求1:
    根据作者名字和博客名字来查询博客!
    如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
    select * from blog where title = #{title} and author = #{author}
    -->
    <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>
    
  3. 测试

    @Test
    public void testQueryBlogIf(){
        SqlSession session = MybatisUtils.getSession();
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("title","Mybatis如此简单");
        map.put("author","狂神说");
        List<Blog> blogs = mapper.queryBlogIf(map);
        
        System.out.println(blogs);
        session.close();
    }
    

这样写我们可以看到,如果 author 等于 null,那么查询语句为 select * from user where title=#{title}, 但是如果title为空呢?那么查询语句为 select * from user where and author=#{author},这是错误的 SQL 语句,如何解决呢?请看下面的 where 语句!

10.4 where

修改上面的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>

这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返 回的内容是以AND 或OR 开头的,则它会剔除掉。【这是我们使用的最多的案例】

10.5 set

同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词, 我们怎么处理呢?

  1. 编写接口方法

    int updateBlog(Map map);
    
  2. sql配置文件

    <!--注意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. 测试

    @Test
    public void testUpdateBlog(){
        SqlSession session = MybatisUtils.getSession();
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("title","动态SQL");
        map.put("author","秦疆");
        map.put("id","9d6a763f5e1347cebda43e2a32687a77");
        mapper.updateBlog(map);
        session.close();
    }
    
    

10.6 choose

有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句

  1. 编写接口方法

    List<Blog> queryBlogChoose(Map map);
    
  2. sql配置文件

    <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>
    
  3. 测试类

    @Test
    public void testQueryBlogChoose(){
        SqlSession session = MybatisUtils.getSession();
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("title","Java如此简单");
        map.put("author","狂神说");
        map.put("views",9999);
        List<Blog> blogs = mapper.queryBlogChoose(map);
        System.out.println(blogs);
        session.close();
    }
    
    

10.7 sql片段

有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽 取出来,然后使用时直接调用。

提取SQL片段:

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

引用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

10.8 Foreach

将数据库中前三个数据的id修改为1,2,3; 需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息

  1. 编写接口

    List<Blog> queryBlogForeach(Map map);
    
  2. 编写sql语句

    <select id="queryBlogForeach" parameterType="map" resultType="blog">
        select * from blog
        <where>
        <!--
        collection:指定输入对象中的集合属性
        item:每次遍历生成的对象
        open:开始遍历时的拼接字符串
        close:结束时拼接的字符串
        separator:遍历对象之间需要拼接的字符串
        select * from blog where 1=1 and (id=1 or id=2 or id=3)
        -->
            <foreach collection="ids" item="id" open="and (" close=")"
            separator="or">
            	id=#{id}
            </foreach>
        </where>
    </select>
    
  3. 测试

    @Test
    public void testQueryBlogForeach(){
        SqlSession session = MybatisUtils.getSession();
        BlogMapper mapper = session.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);
        List<Blog> blogs = mapper.queryBlogForeach(map);
        System.out.println(blogs);
        session.close();
    }
    

小结:其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生 的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。多在实践中使用才是熟练掌握它的技巧

11、缓存

11.1 简介

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

11.2 Mybatis缓存

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

11.3 一级缓存

  • 一级缓存也叫本地缓存:
    • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
    • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
  1. 在mybatis中加入日志,方便测试结果

  2. 编写接口方法

    //根据id查询用户
    User queryUserById(@Param("id") int id);
    
  3. 接口对应的Mapper.xml

    <select id="queryUserById" resultType="user">
    	select * from user where id = #{id}
    </select>
    
  4. 全局配置中注册

  5. 测试

    @Test
    public void testQueryUserById(){
        SqlSession session = MybatisUtils.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = mapper.queryUserById(1);
        System.out.println(user);
        User user2 = mapper.queryUserById(1);
        System.out.println(user2);
        System.out.println(user==user2);
        session.close();
    }
    
  6. 结果分析

    image-20220312231030345

  7. 一级缓存失效的四种情况

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

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

    • sqlSession不同

      @Test
      public void testQueryUserById(){
          SqlSession session = MybatisUtils.getSession();
          SqlSession session2 = MybatisUtils.getSession();
          UserMapper mapper = session.getMapper(UserMapper.class);
          UserMapper mapper2 = session2.getMapper(UserMapper.class);
          User user = mapper.queryUserById(1);
          System.out.println(user);
          User user2 = mapper2.queryUserById(1);
          System.out.println(user2);
          System.out.println(user==user2);
          session.close();
          session2.close();
      }
      

      观察结果:发现发送了两条SQL语句!

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

    • sqlSession相同,查询条件不同

      @Test
      public void testQueryUserById(){
          SqlSession session = MybatisUtils.getSession();
          UserMapper mapper = session.getMapper(UserMapper.class);
          UserMapper mapper2 = session.getMapper(UserMapper.class);
          User user = mapper.queryUserById(1);
          System.out.println(user);
          User user2 = mapper2.queryUserById(2);
          System.out.println(user2);
          System.out.println(user==user2);
          session.close();
      }
      

      观察结果:发现发送了两条SQL语句!很正常的理解

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

    • sqlSession相同,查询条件也相同,但是两次查询之间执行了增删改操作

      @Test
      public void testQueryUserById(){
          SqlSession session = MybatisUtils.getSession();
          UserMapper mapper = session.getMapper(UserMapper.class);
          User user = mapper.queryUserById(1);
          System.out.println(user);
          HashMap map = new HashMap();
          map.put("name","kuangshen");
          map.put("id",4);
          mapper.updateUser(map);
          User user2 = mapper.queryUserById(1);
          System.out.println(user2);
          System.out.println(user==user2);
          session.close();
      }
      
      

      观察结果:查询在中间执行了增删改操作后,重新执行了

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

    • 手动清除了一级缓存

      @Test
      public void testQueryUserById(){
          SqlSession session = MybatisUtils.getSession();
          UserMapper mapper = session.getMapper(UserMapper.class);
          User user = mapper.queryUserById(1);
          System.out.println(user);
          session.clearCache();//手动清除缓存
          User user2 = mapper.queryUserById(1);
          System.out.println(user2);
          System.out.println(user==user2);
          session.close();
      }
      

11.4 二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;

  • 工作机制

    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一 级缓存中的数据被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取内容;
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中;
    使用步骤
    1. 在mybatis全局配置文件中开启全局缓存

      <setting name="cacheEnabled" value="true"/>
      
    2. 去每个mapper.xml中配置使用二级缓存

      <cache/>
      官方示例=====>查看官方文档
      <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
      这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的
      512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者
      产生冲突。
      
    3. 代码测试

      • 所有实体类都先实现序列化接口

      • 测试代码

        @Test
        public void testQueryUserById(){
            SqlSession session = MybatisUtils.getSession();
            SqlSession session2 = MybatisUtils.getSession();
            UserMapper mapper = session.getMapper(UserMapper.class);
            UserMapper mapper2 = session2.getMapper(UserMapper.class);
            User user = mapper.queryUserById(1);
            System.out.println(user);
            session.close();
            User user2 = mapper2.queryUserById(1);
            System.out.println(user2);
            System.out.println(user==user2);
            session2.close();
        }
        
    4. 结论

      • 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
      • 查出的数据都会被默认先放在一级缓存中
      • 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中

11.5 缓存原理

image-20220312232015961

11.6 Ehcache(了解即可)

第三方缓存实现–EhCache: 查看百度百科

  • 官方文档

  • Ehcache是一种广泛使用的java分布式缓存,用于通用缓存;

  • 要在应用程序中使用Ehcache,需要引入依赖的jar包

    <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatisehcache -->
    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.1.0</version>
    </dependency>
    
    
  • 在mapper.xml中使用对应的缓存即可

    <mapper namespace = “org.acme.FooMapper” >
    	<cache type = “org.mybatis.caches.ehcache.EhcacheCache” />
    </mapper>
    
  • 编写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">
    <!--
    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>
    
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值