MyBatis从入门到小懂

272 阅读21分钟

简介

什么是Mybatis

来自官网文档:

  • MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。

  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

来自百度百科:

  • MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。

  • iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)

持久化

  • 内存具有断点即失的特点
  • 数据需要存放在数据库(JDBC)或文件(IO)中

持久层

Dao层,Service层,Controller层······

  • 完成持久化工作的代码块
  • 界限十分明显

获取Mybatis

  • Maven

    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>
    
  • Github

第一个Mybatis程序

环境搭建

  • Java

  • Maven

  • Mysql

    DROP TABLE IF EXISTS `user`;
    CREATE TABLE `user`  (
      `id` int(11) NOT NULL,
      `username` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `password` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
    
    -- ----------------------------
    -- Records of user
    -- ----------------------------
    INSERT INTO `user` VALUES (1, 'volcano', '123321');
    INSERT INTO `user` VALUES (2, 'candashuai', '123123');
    INSERT INTO `user` VALUES (3, '张三', '123456');
    
    SET FOREIGN_KEY_CHECKS = 1;
    

创建项目

  • 创建项目

    image.png

    image.png

  • 删除src目录

  • 导入依赖

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>
    <!-- mysql驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.46</version>
    </dependency>
    <!-- 单元测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
    </dependency>
</dependencies>
  • 创建新module,可以使用主项目的依赖包

    image.png

开始使用

编写配置文件

​ 在src/main/resources下新建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.jdbc.Driver"/>
                <!-- 数据库连接,&符号要用&amp;替代,mysql8及以上需要添加时区配置 -->
                <property name="url" value="jdbc:mysql://localhost:3306/smbms?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <!-- 用户名和密码 -->
                <property name="username" value="root"/>
                <property name="password" value="1q2w3e4r5t"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 这里配置对应Mapper文件,相对路径是要对应在打包好的target -->
    <mappers>
        <mapper resource="com/volcano/dao/UserMapper.xml"/>
    </mappers>
</configuration>

编写工具类

src/main/java/com/volcano/utils中新建MybatisUtils.java

package com.volcano.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 {
            //官网文档写死的三句代码,封装成工具类
            //此路径改为自己配置文件所在的路径,默认就是resources目录下
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static SqlSession getSqlSession(){
        //true自动提交事务,默认为false,每次增删改都需要commit
        return SqlSessionFactory.openSession();
    }
}

创建实体类

src/main/java/com/volcano/pojo下创建User.java

package com.volcano.pojo;

public class User {
    private int id;
    private String username;
    private String password;

    public User() {
    }

    public User(int id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getId() {
        return id;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }
}

创建Dao

src/main/java/com/volcano/dao下创建UserDao.javaUserMapper.xml

package com.volcano.dao;

import com.volcano.pojo.User;

import java.util.List;
// 后续的Dao会改名成Mapper
public interface UserDao {
    //定义一个返回用户列表的方法
    List<User> getUserList();
}
<?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">
<!-- 绑定命名空间为UserDao才能找到其中的方法 -->
<mapper namespace="com.volcano.dao.UserDao">
    <!-- id为方法名和UserDao中对应 resultType为返回类型 -->
    <select id="getUserList" resultType="com.volcano.pojo.User">
        select * from mybatis.user
    </select>
</mapper>

测试

src/test/java下创建与main中相同的包结构(非必须),创建UserDaoTest.java

package com.volcano.dao;

import com.volcano.pojo.User;
import com.volcano.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserDaoTest {
    @Test
    public void test(){
        //1.获取SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //方式一:getMapper
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> userList = mapper.getUserList();

        //方式二:老方法不推荐
//        List<User> userList = sqlSession.selectList("com.volcano.dao.UserDao.getUserList");
        for (User user : userList) {
            System.out.println(user);
        }
        //最好是放在finally中关闭
        sqlSession.close();
    }
}

image.png

项目结构

image.png

实现CRUD

​ 先将之前的的UserDao改为UserMapper,并修改所有的引用。

  • 修改UserMapper.java

    package com.volcano.dao;
    
    import com.volcano.pojo.User;
    
    import java.util.List;
    public interface UserMapper {
        //定义一个返回用户列表的方法
        List<User> getUserList();
        //通过id查询用户
        User getUserById(int id);
        //添加用户
        int insertUser(User user);
        //删除用户
        int deleteUser(int id);
        //修改用户信息
        int updateUser(int id);
    }
    
  • 修改UserMapper.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">
    <!-- 绑定命名空间为UserDao才能找到其中的方法 -->
    <mapper namespace="com.volcano.dao.UserMapper">
    
        <!-- id为方法名和UserDao中对应 resultType为返回类型 -->
        <select id="getUserList" resultType="com.volcano.pojo.User">
            select * from mybatis.user
        </select>
    
        <!-- parameterType为int,getUserById(int id)参数类型 -->
        <!-- #{}为占位符,其中填写getUserById(int id)的形参名 -->
        <select id="getUserById" resultType="com.volcano.pojo.User" parameterType="int">
            select * from mybatis.user where id = #{id}
        </select>
    
        <!-- 参数类型为User,在sql语句中直接使用User类中的属性,一一对应关系 -->
        <insert id="insertUser" parameterType="com.volcano.pojo.User">
            insert into mybatis.user values (#{id},#{username},#{password})
        </insert>
    
        <delete id="deleteUser" parameterType="int">
            delete from mybatis.user where id = #{id}
        </delete>
    
        <update id="updateUser" parameterType="int">
            update `user` set username = "candashuai222",password="999999" where id = #{id}
        </update>
    </mapper>
    
  • 测试

    package com.volcano.dao;
    
    import com.volcano.pojo.User;
    import com.volcano.utils.MybatisUtils;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    import java.util.List;
    
    public class UserDaoTest {
        @Test
        public void test(){
            //1.获取SqlSession对象
            SqlSession sqlSession = MybatisUtils.getSqlSession();
    
            //方式一:getMapper
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> userList = mapper.getUserList();
    
            //方式二:老方法不推荐
    //        List<User> userList = sqlSession.selectList("com.volcano.dao.UserDao.getUserList");
            for (User user : userList) {
                System.out.println(user);
            }
            //最好是放在finally中关闭
            sqlSession.close();
        }
        @Test
        public void testSelect(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            User user = mapper.getUserById(1);
            System.out.println(user);
            sqlSession.close();
        }
        @Test
        public void testInsert(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            //传入true设置默认提交事务为true,就不用在下面手动sqlSession.commit()了
            //SqlSession sqlSession = MybatisUtils.getSqlSession(true);
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            mapper.insertUser(new User(4,"Jack Chen","666666"));
            //提交事务才会生效
            sqlSession.commit();
            sqlSession.close();
        }
        @Test
        public void testDelete(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            mapper.deleteUser(3);
            sqlSession.commit();
            sqlSession.close();
        }
        @Test
        public void testUpdate(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            mapper.updateUser(2);
            sqlSession.commit();
            sqlSession.close();
        }
    }
    

image.png

可能遇到的问题

  1. 标签不要匹配错

  2. mybatis-config.xml中要绑定mapper

  3. 项目运行打包时,xml文件没包含其中,修改pom.xml中的配置,可以放在主工程的pom.xml中

    <build>
        <resources>
            <resource>
                <!-- 允许src/main/resources包括includes节点中的文件类型 -->
                <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>
    

Map的使用

​ 当传递的参数是一个实体类对象时,若实体类的属性少,则实例化方便,若实体类的属性很多,实例化一个对象很复杂,因为构造方法需要很多参数,然而有些方法并不需要这么多参数。

  • UserMapper.java

    //添加用户(Map)
    int insertUserMap(Map map);
    
  • UserMapper.xml

    <!-- 参数类型为map的话,sql语句中的参数可以自定义,但是需要和传入的map中的key一一对应 -->
    <insert id="insertUserMap" parameterType="map">
        insert into mybatis.user values (#{uid},#{uname},#{pwd})
    </insert>
    
  • TestUserDao.java

    @Test
    public void testInsertMap(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        HashMap<String, Object> map = new HashMap<String, Object>();
        //map中的key与xml文件中的占位符一一对应
        map.put("uid",5);
        map.put("uname","Map5");
        map.put("pwd","555555");
        mapper.insertUserMap(map);
        sqlSession.commit();
        sqlSession.close();
    }
    

配置解析

  • mybatis-config.xml,可以不取这个名字(官方推荐)
  • 配置结构(==必须按照顺序配置==
    • configuration(配置)
      • properties(属性)
      • settings(设置)
      • typeAliases(类型别名)
      • typeHandlers(类型处理器)
      • objectFactory(对象工厂)
      • plugins(插件)
      • environments(环境配置)
        • environment(环境变量)
          • transactionManager(事务管理器)
          • dataSource(数据源)
      • databaseIdProvider(数据库厂商标识)
      • mappers(映射器)

properties

​ 用途:引用外部的配置属性信息。

实例

  1. 在resources下新建一个db.properties文件

    driver=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
    username=root
    password=123456
    
  2. 在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>
        <!-- 顺序第一位 -->
        <!-- 情况1:没有需要额外配置的参数可以直接自闭合 -->
        <properties resource="db.properties"/>
        <!-- 情况2:需要额外补充的配置(这边已经把db.properties的password删除)-->
        <!--<properties resource="db.properties">-->
            <!--<property name="password" value="1q2w3e4r5t"/>-->
        <!--</properties>-->
        <!-- 情况3:额外参数与外部参数重复,外部参数覆盖额外参数 -->
        <!--<properties resource="db.properties">-->
            <!-- 如果外部有相同参数,使用外部的 -->
            <!--<property name="password" value="5555"/>-->
        <!--</properties>-->
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <!-- ${}获取properties文件中的属性 -->
                    <property name="driver" value="${driver}"/>
                    <property name="url" value="${url}"/>
                    <property name="username" value="${username}"/>
                    <property name="password" value="${password}"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <mapper resource="com/volcano/mapper/UserMapper.xml"/>
        </mappers>
    </configuration>
    

settings

​ 这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。

​ 需要记住以下三个,需要用到别的可以回到官网文档查看:

设置名描述有效值默认值
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true | falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。true | falsefalse
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING未设置

typeAliases(类型别名)

​ 类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

<typeAliases>
    <!-- XML文件中的com.volcano.pojo.User都可以替换为User -->
    <!--<typeAlias type="com.volcano.pojo.User" alias="User"/>-->
    
    <!-- 为整个包下的所有实体类设置别名,别名为类名首字母小写(实测大写也能用) -->
    <!-- 这种情况下可以使用注解自定义别名,在实体类上使用@Alias("DIY")设置别名 -->
    <package name="com.volcano.pojo"/>
</typeAliases>
//注解指定别名(为整个包指定别名的时候)
@Alias("User")
public class User {
    ...
}
<!-- UserMapper.xml中的resultType可以指定为别名  -->
<select id="getUserList" resultType="User">
    select * from mybatis.user
</select>

environments

​ MyBatis 可以配置成适应多种环境,例如,开发、测试和生产环境需要有不同的配置。

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

<!-- id指定选择的环境 -->
<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>
    <environment id="oracle">
        <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>

事务管理器transactionManager

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
  • MANAGED – 这个配置几乎没做什么。

数据源dataSource

  • POOLED——使用数据库连接池
  • UNPOOLED——不使用数据库连接池
  • JNDI

mappers

​ 映射器,映射自定义的mapper文件。

  • 方式一:

    <!-- 使用相对于类路径的资源引用 -->
    <mappers>
      <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
      <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
      <mapper resource="org/mybatis/builder/PostMapper.xml"/>
    </mappers>
    
  • 方式二:不推荐

    <!-- 使用完全限定资源定位符(URL) -->
    <mappers>
      <mapper url="file:///var/mappers/AuthorMapper.xml"/>
      <mapper url="file:///var/mappers/BlogMapper.xml"/>
      <mapper url="file:///var/mappers/PostMapper.xml"/>
    </mappers>
    
  • 方式三:必须要同名.xml在同目录下

    <!-- 使用映射器接口实现类的完全限定类名 -->
    <mappers>
      <mapper class="org.mybatis.builder.AuthorMapper"/>
      <mapper class="org.mybatis.builder.BlogMapper"/>
      <mapper class="org.mybatis.builder.PostMapper"/>
    </mappers>
    
  • 方式四:注意点与方法三相同

    <!-- 将包内的映射器接口实现全部注册为映射器 -->
    <mappers>
      <package name="org.mybatis.builder"/>
    </mappers>
    

拓展:如果想要分离mapper.java和mapper.xml,分别放在java和resources下相同的包名即可,打包后依然在同一目录下。

其他配置

  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
    • MyBatis Generator Core
    • MyBatis Plus
  • 数据库厂商标识(databaseIdProvider)
    • 根据不同的数据库厂商执行不同的语句

生命周期和作用域

image.png

  • SqlSessionFactoryBuilder

    • 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了
    • 最佳作用域是方法作用域
  • SqlSessionFactory

    • 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
    • 应用运行期间不要重复创建多次
    • 最佳作用域是应用作用域
    • 使用单例模式或者静态单例模式
  • SqlSession

    • SqlSession 的实例不是线程安全的,因此是不能被共享的
    • 最佳作用域是请求方法作用域
    • 确保每次使用完都要关闭

ResultMap

​ 当数据库表的字段名和实体类中的属性名不一致时,可能会出现查询为null的情况。

id		username 	password	user表字段
iden	uname		pwd			User实体类属性

解决方式:

  1. 修改SQL语句,使用别名,select id as iden from user;

  2. 在UserMapper.xml使用ResultMap

    <!-- id标识 type实体类类名 -->
    <resultMap id="UserMap" type="User">
        <!-- column表的字段名 property实体类的属性名 -->
        <id column="id" property="iden"/>
        <id column="username" property="uname"/>
        <id column="password" property="pwd"/>
    </resultMap>
    <!-- resultMap指定resultMap的id -->
    <select id="getUserById" resultMap="UserMap" parameterType="int">
        select * from mybatis.user where id = #{id}
    </select>
    

日志

​ 日志可以详细的展现出程序的执行步骤和过程,提高排错能力。

​ 曾经常用的手段:sout、debug等

日志工厂

​ 前面提到Mybatis中settings属性用logImpl可以指定日志的具体实现。name和value一定要避免空格

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
  • SLF4J
  • LOG4J【掌握】
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING【掌握】
  • NO_LOGGING

STDOUT_LOGGING的日志效果

image.png

Log4j

​ 与STDOUT_LOGGING不同的是,Log4j需要导包后才能使用。

  • Log4j是Apache的一个开源项目,可以指定日志输出的位置是控制台、文件甚至GUI组件
  • 可以控制每一条日志的输出格式
  • 定义每一条日志信息的级别
  • 这些可以通过一个配置文件来灵活地进行配置

配置

  1. 导入包

    <!-- https://mvnrepository.com/artifact/log4j/log4j -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    
  2. resources下创建log4j.properties

    #将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
    log4j.rootLogger=DEBUG,console,file
    
    #控制台输出的相关设置
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    log4j.appender.console.Target = System.out
    log4j.appender.console.Threshold=DEBUG
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
    
    #文件输出的相关设置
    log4j.appender.file = org.apache.log4j.RollingFileAppender
    log4j.appender.file.File=./log/volcano.log
    log4j.appender.file.MaxFileSize=10mb
    log4j.appender.file.Threshold=DEBUG
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
    
    #日志输出级别
    log4j.logger.org.mybatis=DEBUG
    log4j.logger.java.sql=DEBUG
    log4j.logger.java.sql.Statement=DEBUG
    log4j.logger.java.sql.ResultSet=DEBUG
    log4j.logger.java.sql.PreparedStatement=DEBUG
    
  3. 配置log4j为Mybatis日志的实现

    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    
  4. 运行:仅仅多了一些前缀

    image.png

简单使用

package com.volcano.mapper;

import org.apache.log4j.Logger;
import org.junit.Test;

public class MapperTest {
    //获得日志对象,参数为当前类class
    //导入的包确定是org.apache.log4j.Logger
    static Logger logger = Logger.getLogger(MapperTest.class);
    @Test
    public void testLog4j(){
        logger.info("info:testLog4j");
        logger.debug("debug:testLog4j");
        logger.error("error:testLog4j");
    }
}
[com.volcano.mapper.MapperTest]-info:testLog4j
[com.volcano.mapper.MapperTest]-debug:testLog4j
[com.volcano.mapper.MapperTest]-error:testLog4j

PS:由于在配置文件中配置了log4j.appender.file.File=./log/volcano.log

所以在当前项目根目录下的log文件夹中的volcano.log日志文件中也会追加以上内容

分页

Limit实现

  1. UserMapper.java

    List<User> getUsersByLimit(Map<String,Integer> limit);
    
  2. UserMapper.xml

    <!-- map为Mybatis为Map内置的别名 -->
    <select id="getUsersByLimit" parameterType="map" resultMap="UserMap">
        select * from mybatis.user limit #{start},#{pageSize}
    </select>
    
  3. MapperTest.java

    @Test
    public void testLimit(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
        Map<String, Integer> map = new HashMap<String, Integer>();
        map.put("start",0);
        map.put("pageSize",2);
        List<User> userList = mapper.getUsersByLimit(map);
        for (User user : userList) {
            System.out.println(user);
        }
    
        sqlSession.close();
    }
    

RowBounds实现

​ Limit是基于Sql语句的实现,RowBounds是基于Java面向对象的实现,效率不如Sql高,不推荐使用,可能会用于维护老项目。

​ RowBounds说白了就是,查询所有的数据,然后截断。

  1. UserMapper.java

    List<User> getUserByRowBounds();
    
  2. UserMapper.xml

    <select id="getUserByRowBounds" resultMap="UserMap">
        select * from mybatis.user
    </select>
    
  3. MapperTest.java

    @Test
    public void testRowBounds(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        RowBounds rowBounds = new RowBounds(1, 2);
        //看到这么长的代码就领悟一点不推荐这种方法的原因
        List<User> userList = sqlSession.selectList("com.volcano.mapper.UserMapper.getUserByRowBounds", null, rowBounds);
        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }
    

分页插件

Mybatis PageHelper

使用注解开发

  1. UserMapper.java

    @Select("select * from user")
    List<User> getUserList();
    //通过id和name查询用户,多个参数使用注解Param,单个可加可不加(建议加上)
    //基本类型和String类型可以使用Param,引用类型不用
    @Select("select * from user where id = #{uid} and username = #{uname}")
    User getUserByIdAndName(@Param("uid") int id,@Param("uname") String name);
    
  2. mybatis-config.xml

    <mappers>
        <!-- 指定class而不是xml -->
        <mapper class="com.volcano.mapper.UserMapper"/>
    </mappers>
    
  3. TestMapper.java

    @Test
    public void testAnnotation(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }
        User user = mapper.getUserByIdAndName(1, "volcano");
        System.out.println(user);
        sqlSession.close();
    }
    

缺点:无法用于复杂sql查询,比如返回的类型为对象,注解中无法使用ResultMap。

开发原则:简单查询使用注解,复杂查询依旧使用XML文件配置。在mybatis-config.xml中mappers可以同时配置xml和class

本质:通过反射实现

底层:动态代理

Lombok

​ 利用注解来简化创建实体类的操作的工具。

  1. IDEA安装Lombok插件,有则不必(按提示重启IDEA)

    image.png

  2. 导入jar包(这里使用Maven)

    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.16</version>
    </dependency>
    
  3. 开始使用,在实体类上添加@Data注解

    image.png

常用注解

//所有注解
@Getter
@Setter

@FieldNameConstants
@ToString
@EqualsAndHashCode

@AllArgsConstructor
@RequiredArgsConstructor

@NoArgsConstructor

@Log
@Log4j
@Log4j2
@Slf4j
@XSlf4j
@CommonsLog
@JBossLog
@Flogger
@CustomLog

@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass

常用的有:

  • @Data
    • 添加在实体类上,自动生成所有属性的getter和setter、无参构造器、equals()、toString()、hashCode()
  • @Getter/@Setter
    • 对实体类使用,生成类中所有属性的getter/setter
    • 对某个属性使用,生成该属性的getter/setter
  • @ToString/@EqualsAndHashCode
    • 生成对应方法

总结

​ 少用,团队用就可以用,自己用无所谓。

复杂查询结果处理

​ 之前的查询都是简单查询,实体类中的属性都是String或者基本类型,这些只用resultMap中的result或id即可完成结果映射,而在项目中经常遇到,一个对象的属性是另一个对象,这时候的映射根据情况就需要associationcollection

​ 这里复杂查询我们分为,一对多和多对一。

环境搭建

​ 实体关系:师生关系

  • 老师——>学生(一对多)
  • 学生——>老师(多对一)

数据库设计

-- 切记分批运行,执行速度过快会导致,数据表没创建完就开始插入数据
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');

项目结构

image.png

这些文件中的源码之前笔记都有,这里就放一下实体类的代码

//篇幅原因这里省去构造器和getter/setter,实际项目有
public class Student {
    private int id;
    private String name;
    //这里teacher是一个需要查询的实体类
    private Teacher teacher;
}

未处理的复杂查询

​ 这里以多对一示例,通过学生查询对应的老师。

  1. StudentMapper.java

    List<Student> getStudentList0();
    
  2. StudentMapper.xml

    <select id="getStudentList0" resultType="com.volcano.pojo.Student">
        select * from mybatis.student
    </select>
    
  3. 测试

    @Test
    public void test01(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    
        List<Student> studentList = mapper.getStudentList0();
        for (Student student : studentList) {
            System.out.println(student);
        }
    
        sqlSession.close();
    }
    
  4. 结果

    image.png

多对一

​ 出现以上问题,是因为没有用ResultMap进行正确的映射。

  1. StudentMapper.java

    List<Student> getStudentList1();
    List<Student> getStudentList2();
    
  2. StudentMapper.xml

    <!-- 按照查询嵌套 -->
    <select id="getStudentList1" resultMap="StudentAndTeacher">
        select * from mybatis.student
    </select>
    <!-- 查询返回的结果本就是Student -->
    <resultMap id="StudentAndTeacher" type="com.volcano.pojo.Student">
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        <!-- association用多对一 -->
        <!-- 这里相当于用了子查询 -->
        <association column="tid" property="teacher" javaType="com.volcano.pojo.Teacher" select="getTeacher"/>
    </resultMap>
    <!-- 子查询 -->
    <select id="getTeacher" resultType="com.volcano.pojo.Teacher">
        select * from mybatis.teacher where id = #{tid}
    </select>
    
    <!-- 按照结果嵌套 -->
    <!-- 使用sql查询出结果,用别名作为列名 sid sname tid tname -->
    <select id="getStudentList2" resultMap="StudentAndTeacher2">
        select s.id sid, s.name sname, t.id tid,t.name tname
        from mybatis.student s, mybatis.teacher t
        where s.tid=t.id
    </select>
    <resultMap id="StudentAndTeacher2" type="com.volcano.pojo.Student">
        <result column="sid" property="id"/>
        <result column="sname" property="name"/>
        <!-- 把tid tname映射到Teacher -->
        <association property="teacher" javaType="com.volcano.pojo.Teacher">
            <result column="tid" property="id"/>
            <result column="tname" property="name"/>
        </association>
    </resultMap>
    
  3. 测试(结果相同)

    image.png

一对多

​ 新建项目(module),除了Student和Teacher实体类变化,XXXMapper.java/xml清空,其余不变。

public class Student {
    private int id;
    private String name;
    //改为tid
    private int tid;
}
public class Teacher {
    private int id;
    private String name;
    //一对多个学生
    private List<Student> students;
}
  1. TeacherMapper.java

    Teacher getTeacherById(@Param("tid") int id);
    Teacher getTeacherById2(@Param("tid") int id);
    
  2. TeacherMapper.xml

    <!-- 按查询嵌套 -->
    <select id="getTeacherById" resultMap="TeacherAndStudents">
        select * from mybatis.teacher where id = #{tid}
    </select>
    <resultMap id="TeacherAndStudents" type="com.volcano.pojo.Teacher">
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        <!-- 一对多使用collection column把自己的id传入子查询 -->
        <!-- ofType为List<E>中的E也就是泛型对象 -->
        <collection column="id" javaType="List"  property="students" ofType="Student" select="getStudentByTeacher"/>
    </resultMap>
    <select id="getStudentByTeacher" resultType="Student">
        select * from mybatis.student where tid = #{id}
    </select>
    
    <!-- 按结果嵌套 -->
    <select id="getTeacherById2" resultMap="TeacherAndStudents2">
        select t.id tid, t.name tname, s.id sid, s.name sname,s.tid stid
        from mybatis.teacher t,mybatis.student s
        where s.tid = t.id and t.id = #{tid}
    </select>
    <resultMap id="TeacherAndStudents2" type="com.volcano.pojo.Teacher">
        <result column="tid" property="id"/>
        <result column="tname" property="name"/>
        <collection property="students" ofType="com.volcano.pojo.Student">
            <result column="sid" property="id"/>
            <result column="sname" property="name"/>
            <result column="stid" property="tid"/>
        </collection>
    </resultMap>
    
  3. 测试(结果相同,单行过长,文本表示)

    Teacher{id=1, name='秦老师', students=[Student{id=1, name='小明', tid=1}, Student{id=2, name='小红', tid=1}, Student{id=3, name='小张', tid=1}, Student{id=4, name='小李', tid=1}, Student{id=5, name='小王', tid=1}]}
    

总结

  • 多对一
    • association
  • 一对多
    • collection
    • javaType:真正返回的数据类型
    • ofType:泛型
  • 更推荐用按结果嵌套
    • 优化SQL是重点

动态SQL

​ 动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

​ 所谓的动态SQL,本质还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

环境搭建

  • 数据库表

    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
    
  • 项目结构

    image.png

  • IDUtils.java

    import java.util.UUID;
    
    public class IDUtils {
        public static String getId(){
            //通用唯一识别码(Universally Unique Identifier)
            return UUID.randomUUID().toString().replace("-","");
        }
    }
    
  • Blog.java

    public class Blog {
        private String id;
        private String title;
        private String author;
        //在mybatis-config.xml设置驼峰命名映射mapUnderscoreToCamelCase
        //Date都用java.util下的
        private Date createTime;
        private int views;
    }
    
  • BlogMapper.java

    void insertBlog(Blog blog);
    
  • BlogMapper.xml

    <insert id="insertBlog" parameterType="com.volcano.pojo.Blog">
        insert into mybatis.blog(id,title,author,create_time,views)
        values (#{id},#{title},#{author},#{createTime},#{views})
    </insert>
    
  • MyTest.java,自己多插入几条数据

    @Test
    public void test01(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    
        //插入30条数据
        for (int i=0;i<30;i++){
            mapper.insertBlog(new Blog(IDUtils.getId(),"标题"+i,"volcano_"+i/10,new Date(),i*20));
        }
        sqlSession.commit();
        sqlSession.close();
    }
    

if

​ 可以用if来判断需不需要拼接Sql语句。

  • BlogMapper.java

    List<Blog> getBlogIf(Map map);
    
  • BlogMapper.xml

    <select id="getBlogIf" parameterType="map" resultType="blog">
        -- where 1=1 保证无条件的时候能进行下去,有条件则能顺利拼接
        select * from mybatis.blog where 1=1
        -- 根据map中是否有title键值对来判断是否拼接
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </select>
    
  • 测试

    @Test
    public void test02(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        //map存放条件,为空查找所有
        HashMap map = new HashMap();
        map.put("author","volcano_1");
        List<Blog> blogList = mapper.getBlogIf(map);
        for (Blog blog : blogList) {
            System.out.println(blog);
        }
        sqlSession.close();
    }
    

where

​ 在IF的示例中,为了成功拼接条件,在条件起始部分加了一句where 1=1,其实这么写是不安全的,但是不写拼接后又会发生如下情况:

SELECT * FROM BLOG
WHERE
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’

​ MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

  • 改造

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

choose、when、otherwise

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

choose——switch

when——case

otherw——default

  • BlogMapper.java

    List<Blog> getBlogChoose(Map map);
    
  • BlogMapper.xml

    <select id="getBlogChoose" parameterType="map" resultType="blog">
        select * from mybatis.blog
        <where>
            <choose>
                <when test="author != null">
                    author = #{author}
                </when>
                <when test="title != null">
                    title = #{title}
                </when>
                <when test="views != null">
                    views > #{views}
                </when>
                <otherwise>
                    -- 这里的思路是,如果用户啥也没传就搜索,就给一些访问量高的博客
                    views > 500
                </otherwise>
            </choose>
        </where>
    </select>
    
  • 测试

trim、set

​ 用于动态更新语句的类似解决方案叫做 setset元素可以用于动态包含需要更新的列,忽略其它不更新的列。

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

  • BlogMapper.java

    void updateBlog(Map map);
    
  • BlogMapper.xml

    <update id="updateBlog" parameterType="map">
        update mybatis.blog
        <set>
            <if test="title != null">
                title = #{title},
            </if>
            <if test="author != null">
                author = #{author},
            </if>
            <if test="views != null">
                views = #{views},
            </if>
        </set>
        <where>
            <choose>
                <when test="id != null">
                    id = #{id}
                </when>
                <otherwise>
                    1 = 2
                    -- 不给id不让修改数据
                </otherwise>
            </choose>
        </where>
    </update>
    
  • 测试

    @Test
    public void test04(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        //map存放条件,修改id符合的数据,不给id不会修改
        HashMap map = new HashMap();
        map.put("id","eb7cc29b63164cf0922ded8c4e67a924");
        map.put("author","vip_9999");
        map.put("title","标题1——vip专享小尾巴");
        mapper.updateBlog(map);
        sqlSession.commit();
        sqlSession.close();
    }
    

其实这里Mybatis中的whereset,都可以用trim元素设置属性实现相同效果。

<!-- where的等价实现 -->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>
<!-- set的等价实现 -->
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>
<!--
prefix:前缀
prefixOverrides:删除前缀
suffixOverrides:删除后缀
-->

foreach

​ 动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。

  • BlogMapper.java

    List<Blog> getBlogInAuthors(Map map);
    
  • BlogMapper.xml

    <select id="getBlogInAuthors" parameterType="map" resultType="com.volcano.pojo.Blog">
        select * from mybatis.blog
        <where>
            <foreach collection="authors" open="author in (" separator="," close=")" item="author" index="index">
                #{author}
            </foreach>
        </where>
    </select>
    
  • 测试

    @Test
    public void test05(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    
        ArrayList<String> authors = new ArrayList<String>();
        authors.add("volcano_1");
        HashMap map = new HashMap();
        map.put("authors",authors);
        List<Blog> blogList = mapper.getBlogInAuthors(map);
        for (Blog blog : blogList) {
            System.out.println(blog);
        }
        sqlSession.close();
    }
    

SQL片段

​ 说白了就是把可以复用的SQL语句抽取出来,作为公共部分,哪里需要哪里搬。

  1. 抽取公共部分 sql

    <sql id="if-title-author">
        <if test="title != null">
            title = #{title},
        </if>
        <if test="author != null">
            author = #{author},
        </if>
    </sql>
    
  2. 引用 include

    <update id="updateBlog" parameterType="map">
        update mybatis.blog
        <set>
            <include refid="if-title-author"/>
            <if test="views != null">
                views = #{views},
            </if>
        </set>
        <where>
            <choose>
                <when test="id != null">
                    id = #{id}
                </when>
                <otherwise>
                    1 = 2
                </otherwise>
            </choose>
        </where>
    </update>
    

缓存

简介

  1. 什么是缓存 [Cache ]?

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

    • 减少和数据库的交互次数,减少系统开销,提高系统效率。
  3. 什么样的数据能使用缓存?

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

Mybatis缓存

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

​ MyBatis系统中默认定义了两级缓存:一级缓存二级缓存

  • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存,生命周期:getSession()—close())

  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。

  • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

一级缓存

​ 一级缓存也叫本地缓存:

  • SqlSession与数据库同一次会话期间查询到的数据会放在本地缓存中。
  • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库。

示例

@Test
public void testCache01(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
	//两次获取相同数据
    Blog blog = mapper.getBlogById("eb7cc29b63164cf0922ded8c4e67a924");
    System.out.println(blog);
    blog = mapper.getBlogById("eb7cc29b63164cf0922ded8c4e67a924");
    System.out.println(blog);
    sqlSession.close();
}

image.png

手动清除缓存

@Test
public void testCache01(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    //两次获取相同数据
    Blog blog = mapper.getBlogById("eb7cc29b63164cf0922ded8c4e67a924");
    System.out.println(blog);
    sqlSession.clearCache();
    blog = mapper.getBlogById("eb7cc29b63164cf0922ded8c4e67a924");
    System.out.println(blog);
    sqlSession.close();
}

image.png

二级缓存

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

示例

  1. 显式开启全局缓存cacheEnabled,默认开启,这里只是显式让开发者看到

    <settings>
        ...
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
  2. 在需要二级缓存的mapper.xml中开启,可以配置缓存策略、时间等参数,参考官网

    <cache/>
    <!-- 如果实体类未实现Serializable 需要加上readonly="true" -->
    
  3. 测试(切记Sql语句要在XML里配置,用注解不会被缓存!!!

    @Test
    public void testCache02(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
    
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        Blog blog = mapper.getBlogById("eb7cc29b63164cf0922ded8c4e67a924");
        System.out.println(blog);
        //第一个sqlSession关闭后,第二个还能使用数据,说明二级缓存有效果
        sqlSession.close();
        System.out.println("=================");
    
        SqlSession sqlSession2 =MybatisUtils.getSqlSession();
        BlogMapper mapper2 = sqlSession2.getMapper(BlogMapper.class);
        Blog blog2 = mapper2.getBlogById("eb7cc29b63164cf0922ded8c4e67a924");
        System.out.println(blog2);
        //判断是否为同一个引用
        System.out.println(blog==blog2);
        sqlSession2.close();
    }
    
  4. 结果

    image.png

缓存流程

image.png

自定义缓存

​ EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是一种广泛使用的开源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"
    />
  • resources下新建配置文件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">

  <!-- 磁盘缓存位置 -->
  <diskStore path="java.io.tmpdir/ehcache"/>

  <!-- 默认缓存 -->
  <defaultCache
          maxEntriesLocalHeap="10000"
          eternal="false"
          timeToIdleSeconds="120"
          timeToLiveSeconds="120"
          maxEntriesLocalDisk="10000000"
          diskExpiryThreadIntervalSeconds="120"
          memoryStoreEvictionPolicy="LRU">
    <persistence strategy="localTempSwap"/>
  </defaultCache>

  <!-- helloworld缓存 -->
  <cache name="HelloWorldCache"
         maxElementsInMemory="1000"
         eternal="false"
         timeToIdleSeconds="5"
         timeToLiveSeconds="5"
         overflowToDisk="false"
         memoryStoreEvictionPolicy="LRU"/>
</ehcache>
  • 解释
diskStore : ehcache支持内存和磁盘两种存储

path :指定磁盘存储的位置
defaultCache : 默认的缓存

maxEntriesLocalHeap=“10000eternal=“falsetimeToIdleSeconds=“120timeToLiveSeconds=“120maxEntriesLocalDisk=“10000000diskExpiryThreadIntervalSeconds=“120memoryStoreEvictionPolicy=“LRU”
cache :自定的缓存,当自定的配置不满足实际情况时可以通过自定义(可以包含多个cache节点)

name : 缓存的名称,可以通过指定名称获取指定的某个Cache对象

maxElementsInMemory :内存中允许存储的最大的元素个数,0代表无限个

clearOnFlush:内存数量最大时是否清除。

eternal :设置缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。根据存储数据的不同,例如一些静态不变的数据如省市区等可以设置为永不过时

timeToIdleSeconds : 设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。

timeToLiveSeconds :缓存数据的生存时间(TTL),也就是一个元素从构建到消亡的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。

overflowToDisk :内存不足时,是否启用磁盘缓存。

maxEntriesLocalDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。

maxElementsOnDisk:硬盘最大缓存个数。

diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。

diskPersistent:是否在VM重启时存储硬盘的缓存数据。默认值是false。

diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。

ehcache和Redis

​ ehcache直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便。

​ redis是通过socket访问到缓存服务,效率比Ehcache低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。如果是单个应用或者对缓存访问要求很高的应用,用ehcache。如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。

​ ehcache也有缓存共享方案,不过是通过RMI或者Jgroup多播方式进行广播缓存通知更新,缓存共享复杂,维护不方便;简单的共享可以,但是涉及到缓存恢复,大数据缓存,则不合适。

自定义缓存

只要实现Mybatis的cache接口,就可以在<cache type=""/>中指定自定义缓存,实现部分过于高级,不好说。