Mybatis(下)

第五章 缓存机制

第一节 简介

理解缓存的工作机制和缓存的用途。

1、缓存机制介绍

2、一级缓存和二级缓存

①使用顺序

查询的顺序是:

  • 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
  • 如果二级缓存没有命中,再查询一级缓存
  • 如果一级缓存也没有命中,则查询数据库
  • SqlSession关闭之前,一级缓存中的数据会写入二级缓存

②效用范围

  • 一级缓存:SqlSession级别(事务级别)
  • 二级缓存:SqlSessionFactory级别(整个web应用开启到关闭)

它们之间范围的大小参考下面图:

第二节 一级缓存

1、代码验证一级缓存


@Test
public void testFirstLevelCache() {
    
    EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
    
    // 1.第一次查询
    Employee employee1 = mapper.selectEmployeeById(2);
    
    System.out.println("employee1 = " + employee1);
    
    // 2.第二次查询
    Employee employee2 = mapper.selectEmployeeById(2);
    
    System.out.println("employee2 = " + employee2);
    
    // 3.经过验证发现,两次查询返回的其实是同一个对象
    System.out.println("(employee2 == employee1) = " + (employee2 == employee1));
    System.out.println("employee1.equals(employee2) = " + employee1.equals(employee2));
    System.out.println("employee1.hashCode() = " + employee1.hashCode());
    System.out.println("employee2.hashCode() = " + employee2.hashCode());
    
}

打印结果:

DEBUG 12-01 09:14:48,760 ==>  Preparing: select emp_id,emp_name,emp_salary,emp_gender,emp_age from t_emp where emp_id=?   (BaseJdbcLogger.java:145) 
DEBUG 12-01 09:14:48,804 ==> Parameters: 2(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 12-01 09:14:48,830 <==      Total: 1  (BaseJdbcLogger.java:145) 
employee1 = Employee{empId=2, empName='AAAAAA', empSalary=6666.66, empAge=20, empGender='male'}
employee2 = Employee{empId=2, empName='AAAAAA', empSalary=6666.66, empAge=20, empGender='male'}
(employee2 == employee1) = true
employee1.equals(employee2) = true
employee1.hashCode() = 1131645570
employee2.hashCode() = 1131645570

一共只打印了一条SQL语句,两个变量指向同一个对象。

2、一级缓存失效的情况

  • 不是同一个SqlSession
  • 同一个SqlSession但是查询条件发生了变化
  • 同一个SqlSession两次查询期间执行了任何一次增删改操作
  • 同一个SqlSession两次查询期间手动清空了缓存
  • 同一个SqlSession两次查询期间提交了事务

第三节 二级缓存

这里我们使用的是Mybatis自带的二级缓存。

1、代码测试二级缓存

①开启二级缓存功能

在想要使用二级缓存的Mapper配置文件中加入cache标签


<mapper namespace="com.atguigu.mybatis.EmployeeMapper">
    
    <!-- 加入cache标签启用二级缓存功能 -->
    <cache/>

②让实体类支持序列化

public class Employee implements Serializable {

③junit测试

这个功能的测试操作需要将SqlSessionFactory对象设置为成员变量


@Test
public void testSecondLevelCacheExists() {
    SqlSession session = factory.openSession();
    
    EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
    
    Employee employee = mapper.selectEmployeeById(2);
    
    System.out.println("employee = " + employee);
    
    // 在执行第二次查询前,关闭当前SqlSession
    session.close();
    
    // 开启一个新的SqlSession
    session = factory.openSession();
    
    mapper = session.getMapper(EmployeeMapper.class);
    
    employee = mapper.selectEmployeeById(2);
    
    System.out.println("employee = " + employee);
    
    session.close();
    
}

打印效果:


DEBUG 12-01 09:44:27,057 Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.0  (LoggingCache.java:62) 
DEBUG 12-01 09:44:27,459 ==>  Preparing: select emp_id,emp_name,emp_salary,emp_gender,emp_age from t_emp where emp_id=?   (BaseJdbcLogger.java:145) 
DEBUG 12-01 09:44:27,510 ==> Parameters: 2(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 12-01 09:44:27,536 <==      Total: 1  (BaseJdbcLogger.java:145) 
employee = Employee{empId=2, empName='AAAAAA', empSalary=6666.66, empAge=20, empGender='male'}
DEBUG 12-01 09:44:27,622 Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.5  (LoggingCache.java:62) 
employee = Employee{empId=2, empName='AAAAAA', empSalary=6666.66, empAge=20, empGender='male'}

④缓存命中率

日志中打印的Cache Hit Ratio叫做缓存命中率

 Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.0(0/1)
    Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.5(1/2)
    Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.6666666666666666(2/3)
    Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.75(3/4)
    Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.8(4/5)

缓存命中率=命中缓存的次数/查询的总次数

2、查询结果存入二级缓存的时机

结论:SqlSession关闭的时候,一级缓存中的内容会被存入二级缓存


@Test
    public void testSecondLevelCache() {

        // 测试二级缓存存在:使用两个不同SqlSession执行查询
        // 说明:SqlSession提交事务时才会将查询到的数据存入二级缓存
        // 所以本例并没有能够成功从二级缓存获取到数据
        SqlSession session01 = factory.openSession();
        SqlSession session02 = factory.openSession();

        EmployeeMapper mapper01 = session01.getMapper(EmployeeMapper.class);
        EmployeeMapper mapper02 = session02.getMapper(EmployeeMapper.class);

        Integer empId = 1;

        // 05-29 09:16:29,986 Cache Hit Ratio [com.atguigu.mybatis.dao.EmployeeMapper]: 0.0
        Emp emp01 = mapper01.selectEmpById(empId);

        // 05-29 09:16:30,470 Cache Hit Ratio [com.atguigu.mybatis.dao.EmployeeMapper]: 0.0
        Emp emp02 = mapper02.selectEmpById(empId);

        System.out.println("emp01 = " + emp01);
        System.out.println("emp02 = " + emp02);

        session01.commit();
        session01.close();
        session02.commit();
        session02.close();
    }
        EmployeeMapper mapper01 = session01.getMapper(EmployeeMapper.class);
        EmployeeMapper mapper02 = session02.getMapper(EmployeeMapper.class);

        Integer empId = 1;

        // 05-29 09:16:29,986 Cache Hit Ratio [com.atguigu.mybatis.dao.EmployeeMapper]: 0.0
        Emp emp01 = mapper01.selectEmpById(empId);

        // 05-29 09:16:30,470 Cache Hit Ratio [com.atguigu.mybatis.dao.EmployeeMapper]: 0.0
        Emp emp02 = mapper02.selectEmpById(empId);

        System.out.println("emp01 = " + emp01);
        System.out.println("emp02 = " + emp02);

        session01.commit();
        session01.close();
        session02.commit();
        session02.close();

上面代码打印的结果是:

DEBUG 12-01 10:10:32,209 Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.0  (LoggingCache.java:62) 
DEBUG 12-01 10:10:32,570 ==>  Preparing: select emp_id,emp_name,emp_salary,emp_gender,emp_age from t_emp where emp_id=?   (BaseJdbcLogger.java:145) 
DEBUG 12-01 10:10:32,624 ==> Parameters: 2(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 12-01 10:10:32,643 <==      Total: 1  (BaseJdbcLogger.java:145) 
DEBUG 12-01 10:10:32,644 Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.0  (LoggingCache.java:62) 
DEBUG 12-01 10:10:32,661 ==>  Preparing: select emp_id,emp_name,emp_salary,emp_gender,emp_age from t_emp where emp_id=?   (BaseJdbcLogger.java:145) 
DEBUG 12-01 10:10:32,662 ==> Parameters: 2(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 12-01 10:10:32,665 <==      Total: 1  (BaseJdbcLogger.java:145) 
employee02.equals(employee01) = false

修改代码:

 @Test
    public void testSecondLevelCacheWork() {
        SqlSession session = factory.openSession();

        EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);

        Integer empId = 1;

        // 第一次查询
        Emp emp01 = employeeMapper.selectEmpById(empId);

        System.out.println("emp01 = " + emp01);

        // 提交事务
        session.commit();

        // 关闭旧SqlSession
        session.close();

        // 开启新SqlSession
        session = factory.openSession();

        // 第二次查询
        employeeMapper = session.getMapper(EmployeeMapper.class);

        Emp emp02 = employeeMapper.selectEmpById(empId);

        System.out.println("emp02 = " + emp02);

        session.commit();
        session.close();
        session = factory.openSession();

        employeeMapper = session.getMapper(EmployeeMapper.class);

        employeeMapper.selectEmpById(empId);

        session.commit();
        session.close();
        session = factory.openSession();

        employeeMapper = session.getMapper(EmployeeMapper.class);

        employeeMapper.selectEmpById(empId);

        session.commit();
        session.close();
        session = factory.openSession();

        employeeMapper = session.getMapper(EmployeeMapper.class);

        employeeMapper.selectEmpById(empId);

        session.commit();
        session.close();
    }

打印结果:


DEBUG 12-01 10:14:06,804 Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.0  (LoggingCache.java:62) 
DEBUG 12-01 10:14:07,135 ==>  Preparing: select emp_id,emp_name,emp_salary,emp_gender,emp_age from t_emp where emp_id=?   (BaseJdbcLogger.java:145) 
DEBUG 12-01 10:14:07,202 ==> Parameters: 2(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 12-01 10:14:07,224 <==      Total: 1  (BaseJdbcLogger.java:145) 
DEBUG 12-01 10:14:07,308 Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.5  (LoggingCache.java:62) 
employee02.equals(employee01) = false

3、二级缓存相关配置

在Mapper配置文件中添加的cache标签可以设置一些属性:

  • eviction属性:缓存回收策略

    LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。

    FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。

    SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。

    WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

    默认的是 LRU。

  • flushInterval属性:刷新间隔,单位毫秒

    默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新

  • size属性:引用数目,正整数

    代表缓存最多可以存储多少个对象,太大容易导致内存溢出

  • readOnly属性:只读,true/false

    true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。

    false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。

第四节 整合EHCache

1、EHCache简介

官网地址:https://www.ehcache.org/

Ehcache is an open source, standards-based cache that boosts performance, offloads your database, and simplifies scalability. It's the most widely-used Java-based cache because it's robust, proven, full-featured, and integrates with other popular libraries and frameworks. Ehcache scales from in-process caching, all the way to mixed in-process/out-of-process deployments with terabyte-sized caches.

2、整合操作

①Mybatis环境

在Mybatis环境下整合EHCache,xxx前提当然是要先准备好Mybatis的环境

②添加依赖

[1]依赖信息
<!-- Mybatis EHCache整合包 -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>
<!-- slf4j日志门面的一个具体实现 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
[2]依赖传递情况

[3]各主要jar包作用
jar包名称 作用
mybatis-ehcache Mybatis和EHCache的整合包
ehcache EHCache核心包
slf4j-api SLF4J日志门面包
logback-classic 支持SLF4J门面接口的一个具体实现

③整合EHCache

[1]创建EHCache配置文件

ehcache.xml

[2]文件内容

<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!-- 磁盘保存路径 ,记得修改!!!!!!!!!!!-->
    <diskStore path="D:atguiguehcache"/>
    
    <defaultCache
            maxElementsInMemory="1000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

引入第三方框架或工具时,配置文件的文件名可以自定义吗?

  • 可以自定义:文件名是由我告诉其他环境
  • 不能自定义:文件名是框架内置的、约定好的,就不能自定义,以避免框架无法加载这个文件
[3]指定缓存管理器的具体类型

还是到查询操作所的Mapper配置文件中,找到之前设置的cache标签:


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

④加入logback日志

存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。

[1]各种Java日志框架简介

门面:

名称 说明
JCL(Jakarta Commons Logging) 陈旧
SLF4J(Simple Logging Facade for Java)★ 适合
jboss-logging 特殊专业领域使用

实现:

名称 说明
log4j★ 最初版
JUL(java.util.logging) JDK自带
log4j2 Apache收购log4j后全面重构,内部实现和log4j完全不同
logback★ 优雅、强大

注:标记★的技术是同一作者。

[2]logback配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 指定日志输出的位置 -->
    <appender name="STDOUT"
        class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- 日志输出的格式 -->
            <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
            <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
        </encoder>
    </appender>
    
    <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
    <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
    <root level="DEBUG">
        <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
        <appender-ref ref="STDOUT" />
    </root>
    
    <!-- 根据特殊需求指定局部日志级别 -->
    <logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
    
</configuration>

⑤junit测试

正常按照二级缓存的方式测试即可。因为整合EHCache后,其实就是使用EHCache代替了Mybatis自带的二级缓存。

⑥EHCache配置文件说明

当借助CacheManager.add("缓存名称")创建Cache时,EhCache便会采用指定的的管理策略。

defaultCache标签各属性说明:

属性名 是否必须 作用
maxElementsInMemory 在内存中缓存的element的最大数目
maxElementsOnDisk 在磁盘上缓存的element的最大数目,若是0表示无穷大
eternal 设定缓存的elements是否永远不过期。 如果为true,则缓存的数据始终有效, 如果为false那么还要根据timeToIdleSeconds、timeToLiveSeconds判断
overflowToDisk 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
timeToIdleSeconds 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时, 这些数据便会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
diskPersistent 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
diskExpiryThreadIntervalSeconds 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s, 相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。 默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)

第五节 缓存的基本原理

待补充

1、Cache接口

①Cache接口的重要地位

org.apache.ibatis.cache.Cache接口:所有缓存都必须实现的顶级接口

②Cache接口中的方法

方法名 作用
putObject() 将对象存入缓存
getObject() 从缓存中取出对象
removeObject() 从缓存中删除对象

③缓存的本质

根据Cache接口中方法的声明我们能够看到,缓存的本质是一个Map

2、PerpetualCache

org.apache.ibatis.cache.impl.PerpetualCache是Mybatis的默认缓存,也是Cache接口的默认实现。Mybatis一级缓存和自带的二级缓存都是通过PerpetualCache来操作缓存数据的。但是这就奇怪了,同样是PerpetualCache这个类,怎么能区分出来两种不同级别的缓存呢?

其实很简单,调用者不同。

  • 一级缓存:由BaseExecutor调用PerpetualCache
  • 二级缓存:由CachingExecutor调用PerpetualCache,而CachingExecutor可以看做是对BaseExecutor的装饰

3、一级缓存机制

org.apache.ibatis.executor.BaseExecutor类中的关键方法:

①query()方法

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    List<E> list;
    try {
        queryStack++;
        
        // 尝试从本地缓存中获取数据
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
        
        if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {
            
            // 如果本地缓存中没有查询到数据,则查询数据库
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
        queryStack--;
    }
    if (queryStack == 0) {
        for (org.apache.ibatis.executor.BaseExecutor.DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        // issue #601
        deferredLoads.clear();
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
        }
    }
    return list;
}

②queryFromDatabase()方法

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        
        // 从数据库中查询数据
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        localCache.removeObject(key);
    }
    
    // 将数据存入本地缓存
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

4、二级缓存机制

下面我们来看看CachingExecutor类中的query()方法在不同情况下使用的具体缓存对象:

①未开启二级缓存

②使用自带二级缓存

③使用EHCache

第六章 逆向工程

第一节 概念与机制

1、概念

  • 正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的。
  • 逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
    • Java实体类
    • Mapper接口
    • Mapper配置文件

2、基本原理

第二节 操作

1、配置POM

<!-- 依赖MyBatis核心包 -->
<dependencies>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>
</dependencies>
    
<!-- 控制Maven在构建过程中相关配置 -->
<build>
        
    <!-- 构建过程中用到的插件 -->
    <plugins>
        
        <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.0</version>
    
            <!-- 插件的依赖 -->
            <dependencies>
                
                <!-- 逆向工程的核心依赖 -->
                <dependency>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-core</artifactId>
                    <version>1.3.2</version>
                </dependency>
                    
                <!-- 数据库连接池 -->
                <dependency>
                    <groupId>com.mchange</groupId>
                    <artifactId>c3p0</artifactId>
                    <version>0.9.2</version>
                </dependency>
                    
                <!-- MySQL驱动 -->
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>5.1.8</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>

2、MBG配置文件

文件名必须是:generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--
            targetRuntime: 执行生成的逆向工程的版本
                    MyBatis3Simple: 生成基本的CRUD(清新简洁版)
                    MyBatis3: 生成带条件的CRUD(奢华尊享版)
     -->
    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!-- 数据库的连接信息 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis-example"
                        userId="root"
                        password="r">
        </jdbcConnection>
        <!-- javaBean的生成策略-->
        <javaModelGenerator targetPackage="com.atguigu.mybatis.entity" targetProject=".srcmainjava">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="com.atguigu.mybatis.mapper"  targetProject=".srcmainjava">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mybatis.mapper"  targetProject=".srcmainjava">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名 -->
        <table tableName="t_emp" domainObjectName="Employee"/>
        <table tableName="t_customer" domainObjectName="Customer"/>
        <table tableName="t_order" domainObjectName="Order"/>
    </context>
</generatorConfiguration>

3、执行MBG插件的generate目标

4、效果

生成XxxExample文件:

第三节 QBC查询

1、概念

QBC:Query By Criteria

QBC查询最大的特点就是将SQL语句中的WHERE子句进行了组件化的封装,让我们可以通过调用Criteria对象的方法自由的拼装查询条件。

2、例子

 // 1.创建EmployeeExample对象
        EmployeeExample example = new EmployeeExample();

        // 2.通过example对象创建Criteria对象
        EmployeeExample.Criteria criteria01 = example.createCriteria();
        EmployeeExample.Criteria criteria02 = example.or();

        // 3.在Criteria对象中封装查询条件
        criteria01
            .andEmpAgeBetween(9, 99)
            .andEmpNameLike("%o%")
            .andEmpGenderEqualTo("male")
            .andEmpSalaryGreaterThan(500.55);

        criteria02
                .andEmpAgeBetween(9, 99)
                .andEmpNameLike("%o%")
                .andEmpGenderEqualTo("male")
                .andEmpSalaryGreaterThan(500.55);

        SqlSession session = factory.openSession();

        EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);

        // 4.基于Criteria对象进行查询
        List<Employee> employeeList = mapper.selectByExample(example);

        for (Employee employee : employeeList) {
            System.out.println("employee = " + employee);
        }

        session.close();

        // 最终SQL的效果:
        // WHERE ( emp_age between ? and ? and emp_name like ? and emp_gender = ? and emp_salary > ? ) or( emp_age between ? and ? and emp_name like ? and emp_gender = ? and emp_salary > ? )

第七章 其它

第一节 实体类类型别名

1、目标

让Mapper配置文件中使用的实体类类型名称更简洁。

2、操作

①Mybatis全局配置文件

<!-- 配置类型的别名 -->
<typeAliases>
    <!-- 声明了实体类所在的包之后,在Mapper配置文件中,只需要指定这个包下的简单类名即可 -->
    <package name="com.atguigu.mybatis.entity"/>
</typeAliases>

②Mapper配置文件

<!-- Employee selectEmployeeById(Integer empId); -->
<select id="selectEmployeeById" resultType="Employee">
    select emp_id,emp_name,emp_salary,emp_gender,emp_age from t_emp
    where emp_id=#{empId}
</select>

第二节 类型处理器

1、Mybatis内置类型处理器

无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。

Mybatis提供的内置类型处理器:

2、日期时间处理

日期和时间的处理,JDK1.8以前一直是个头疼的问题。我们通常使用 JSR310 规范领导者 Stephen Colebourne 创建的 Joda-Time 来操作。JDK1.8已经实现全部的JSR310 规范了。

Mybatis在日期时间处理的问题上,提供了基于 JSR310(Date and Time API)编写的各种日期时间类型处理器。

MyBatis3.4以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的。

如需注册,需要下载mybatistypehandlers-jsr310,并通过如下方式注册

3、自定义类型处理器

当某个具体类型Mybatis靠内置的类型处理器无法识别时,可以使用Mybatis提供的自定义类型处理器机制。

  • 第一步:实现 org.apache.ibatis.type.TypeHandler 接口或者继承 org.apache.ibatis.type.BaseTypeHandler 类。
  • 第二步:指定其映射某个JDBC类型(可选操作)。
  • 第三步:在Mybatis全局配置文件中注册。

①创建自定义类型转换器类


@MappedTypes(value = Address.class)
@MappedJdbcTypes(JdbcType.CHAR)
public class AddressTypeHandler extends BaseTypeHandler<Address> {
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, Address address, JdbcType jdbcType) throws SQLException {

    }

    @Override
    public Address getNullableResult(ResultSet resultSet, String columnName) throws SQLException {

        // 1.从结果集中获取原始的地址数据
        String addressOriginalValue = resultSet.getString(columnName);

        // 2.判断原始数据是否有效
        if (addressOriginalValue == null || "".equals(addressOriginalValue))
            return null;

        // 3.如果原始数据有效则执行拆分
        String[] split = addressOriginalValue.split(",");
        String province = split[0];
        String city = split[1];
        String street = split[2];

        // 4.创建Address对象
        Address address = new Address();
        address.setCity(city);
        address.setProvince(province);
        address.setStreet(street);

        return address;
    }

    @Override
    public Address getNullableResult(ResultSet resultSet, int i) throws SQLException {
        return null;
    }

    @Override
    public Address getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        return null;
    }
}

②注册自定义类型转换器

在Mybatis全局配置文件中配置:


<!-- 注册自定义类型转换器 -->
<typeHandlers>
    <typeHandler 
                 jdbcType="CHAR" 
                 javaType="com.atguigu.mybatis.entity.Address" 
                 handler="com.atguigu.mybatis.type.handler.AddressTypeHandler"/>
</typeHandlers>

第三节 Mapper映射

Mybatis允许在指定Mapper映射文件时,只指定其所在的包:


<mappers>
        <package name="com.atguigu.mybatis.dao"/>
</mappers>

此时这个包下的所有Mapper配置文件将被自动加载、注册,比较方便。

但是,要求是:

  • Mapper接口和Mapper配置文件名称一致
  • Mapper配置文件放在Mapper接口所在的包内

如果工程是Maven工程,那么Mapper配置文件还是要放在resources目录下:

第四节 插件机制

1、Mybatis四大对象

①Executor

②ParameterHandler

③ResultSetHandler

④StatementHandler

2、Mybatis插件机制

插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。著名的Mybatis插件包括 PageHelper(分页插件)、通用 Mapper(SQL生成插件)等。

如果想编写自己的Mybatis插件可以通过实现org.apache.ibatis.plugin.Interceptor接口来完成,表示对Mybatis常规操作进行拦截,加入自定义逻辑。

但是由于插件涉及到Mybatis底层工作机制,在没有足够把握时不要轻易尝试。

原文地址:https://www.cnblogs.com/tianwenxin/p/14960178.html