01-mybatis

2022-07-18

01-mybatis

Mybatis概述

一. mybatis参数处理

1. 单个参数

mybatis不会做特殊处理,#{参数名}

2. 多个参数

会做特殊处理

多个参数会被封装成一个map,#{}就是从map中获取指定的key的值

Key: param1…paramN, 或者参数的索引也可以

Value:传入的参数值
  • 因此建议使用命名参数:

​ 明确的指出封装参数时map的key, 使用@Param注解指定的值

Employee getEmpByIdAndLastname(@Param("id") Integer id, @Param("lastName") String lastName);
  • 如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo

    #{属性名},取出传入的pojo属性值
    
  • 如果多个参数不是业务模型中的数据,没有对应的pojo,为了方便,我们也可以传入map

    #{key}: 取出map中对应的值
    
  • 如果多个参数不是业务模型中的数据,没有对应的pojo,但是经常使用, 推荐来编写一个TO(Transfer Object)

  • 特别注意:如果是Collection类型(List, Set)或者是数组,也会特殊处理。也是把传入的list或者数组封装在map中。

    Key: Collection(collection) 如果是List还可以使用这个key(list),数组(array)

    public Employee getEmpById(List<Integer> ids);
    
    取值:取出第一个id的值:#{list[0]}
    

3. 如何处理参数

在org.apache.ibatis.reflection.ParamNameResolver类中

public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
  //参数为null,直接返回
    if (args == null || paramCount == 0) {
      return null;
      //如果只有一个元素,并且没有Param注解;arg[0]:单个参数返回
    } else if (!hasParamAnnotation && paramCount == 1) {
      return args[names.firstKey()];
      //多个元素或者有Param标注
    } else {
      final Map<String, Object> param = new ParamMap<Object>();
      int i = 0;
      //遍历names集合
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }
  1. names

    public ParamNameResolver(Configuration config, Method method) {
        final Class<?>[] paramTypes = method.getParameterTypes();
        final Annotation[][] paramAnnotations = method.getParameterAnnotations();
        final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
        int paramCount = paramAnnotations.length;
        // get names from @Param annotations
        for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
          if (isSpecialParameter(paramTypes[paramIndex])) {
            // skip special parameters
            continue;
          }
          String name = null;
          for (Annotation annotation : paramAnnotations[paramIndex]) {
            if (annotation instanceof Param) {
              hasParamAnnotation = true;
              name = ((Param) annotation).value();
              break;
            }
          }
          if (name == null) {
            // @Param was not specified.
            if (config.isUseActualParamName()) {
              name = getActualParamName(method, paramIndex);
            }
            if (name == null) {
              // use the parameter index as the name ("0", "1", ...)
              // gcode issue #71
              name = String.valueOf(map.size());
            }
          }
          map.put(paramIndex, name);
        }
        names = Collections.unmodifiableSortedMap(map);
      }
    
    

    获取每个标了param注解的参数的@Param的值,赋值给name

    每次解析一个参数给map中保存信息:(key:参数索引,value:name的值)

    name的值:

    ​ 标注了param的注解:注解的值

    没有标注:

    ​ 全局配置:userActualParamName:name=参数名

    ​ name=map.size():相当于当前元素的索引

4. 参数的取值

  • #{}

    是以预编译的形式,将参数设置到sql语句中,可以防止sql注入

    1. 可以规定参数的一些规则

      jdbcType通常需要在某种特定的条件下设置:

      ​ 在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理.

  • ${}

    取出的值直接拼装在sql语句中,会有安全问题

    原生jdbc不支持占位符的地方我们就可以使用${}进行取值,比如说:

    分表,排序

    select * from tb1 order by ${salary} ${order} 
    

二. 映射文件标签

1. select标签

  • 返回list,resultType写集合中元素的类型

  • 返回一条记录的map;key’是列名,值就是对应的值

    /**
         * key为主键,value为Employee对象key
         * 告诉mybatis封装这个map 的时候使用哪个属性作为map 的
         * @param id
         * @return
         */
        @MapKey("id")
        Map<Integer, Employee> getEmpByLastNameLikeReturnMap(Integer id);
    
    
    <!--getEmpByLastNameLikeReturnMap-->
    	<select id="getEmpByLastNameLikeReturnMap" resultType="emp">
    		select * from tb1_employee where last_name like #{lastName}
    	</select>
    
  • resultMap

    • 自定义结果集映射规则
    <!--type:自定义规则的java类型
    	id: 唯一id方便引用
    	-->
    	<resultMap id="MyEmp" type="emp">
    		<!--column:指定那一列
    		property:指定对应的javaBean属性
    		-->
    		<id column="id" property="id"/>
    		<result column="last_name" property="lastName"/>
    		<!--其他不指定的列会自动封装:我们只要写resultMap就把全部的映射规则都写上-->
    		<result column="email" property="email"/>
    		<result column="gender" property="gender"/>
    	</resultMap>
    
    • 联合查询

      • 直接以属性.的方式级联

        <resultMap id="MyEmp2" type="emp">
        		<id column="id" property="id"/>
        		<result column="last_name" property="lastName"/>
        		<result column="email" property="email"/>
        		<result column="gender" property="gender"/>
        		<result column="did" property="dept.id"/>
        		<result column="dept_name" property="dept.departmentName"/>
        	</resultMap>
        
      • 使用association标签

        <resultMap id="MyEmp2" type="emp">
        		<id column="id" property="id"/>
        		<result column="last_name" property="lastName"/>
        		<result column="email" property="email"/>
        		<result column="gender" property="gender"/>
        		<!-- property:指定那个属性是联合的对象
        		javaType:指定这个属性对象的类型[不能省略]
        	-->  
        		<association property="dept" javaType="com.mahaonan.bean.Department">
        			<id column="did" property="id"/>
        			<result column="dept_name" property="departmentName"/>
        		</association>
        
      
      - 使用association进行分步查询
      
      ```xml
      <resultMap id="MyEmp2" type="emp">
      		<id column="id" property="id"/>
      		<result column="last_name" property="lastName"/>
      		<result column="email" property="email"/>
      		<result column="gender" property="gender"/>
      		<!--select: 表明当前属性是调用select指定的方法查出的结果
      			column:指定那一列的值传给这个方法,即将column的值作为查询条件进行二次查询
      			即:使用select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property
      		-->
      		<association property="dept" select="com.mahaonan.mapper.DepartmentMapper.getDeptById" column="d_id">
      		</association>
      	</resultMap>
      
      • 使用延迟加载

        在分步查询的基础上加上两个配置:

        在全局配置文件中:

        <settings>
        		<!--懒加载开启-->
        		<setting name="lazyLoadingEnabled" value="true"/>
        		<setting name="aggressiveLazyLoading" value="false"/>
        	</settings>
        
    • 集合对象

      <resultMap id="MyEmp2" type="emp">
      		<collection property="" ofType=""/>
      </resultMap>
      

      ofType集合里面的元素类型

三. 动态sql

1. <if>

2. <where>

3. <sql>

​ 抽取可重用的sql片段。方便后面引用

<include>引用sql片段

四. mybatis缓存

1. 一级缓存

本地缓存

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

  • sqlSession级别的缓存。一级缓存是一直开启的;

  • 失效情况(没有使用到一级缓存)

    1. sqlSession不同

    2. sqlSession相同,查询条件不同

    3. sqlSession相同,两次查询期间执行了增删改操作

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

      sqlSession.clearCache();
      

2. 二级缓存

基于namespace级别的缓存,一个namespace可以对应一个二级缓存

  • 工作机制

    1. 一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中
    2. 如果会话关闭,一级缓存的数据会被保存到二级缓存中,新的会话查询信息,就可以参照二级缓存的信息
    3. 不同namespace查出的数据会放在自己对应的缓存map中
  • 使用

    1. 开启全局二级缓存配置

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

      <!--eviction 缓存的回收策略
      		flushInterval 缓存刷新间隔,缓存多长时间清空依次,默认不清空,毫秒单位
      		readOnly 是否只读,
      			true 只读 mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据,mybatis为了加速获取速度,直接就会将数据
      		在缓存中的引用交给用户。不安全,速度快
      			false 非只读	mybatis会利用序列化&反序列化克隆一份新的数据给你。安全,速度慢,默认为这种
      		size: 缓存中存放多少元素
      		type: 指定自定义缓存的全类名
      				自定义实现Cache接口
      	-->
      <cache eviction="" flushInterval="" readOnly="" size="" type=""></cach>
      	<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"/>
      
    3. 我们的POJO需要实现序列化接口

  • 注意事项

    • 查出的数据都会被默认放在一级缓存中,只有会话提交或者关闭后,一级缓存中的数据才会被放到二级缓存中

3. 和缓存有关的属性和设置

  • cacheEnabled=true;

    false关闭,首先关闭二级缓存,一级缓存一直可用

  • select标签都有useCache="true"属性

    false关闭,一级缓存一直可用,关闭二级缓存

  • 每个增删改标签:flushCache="true"

    增删改执行完成后就会清空缓存(一二级都清空)

    查询标签也有:flushCache="false"

  • sqlSession.clearCache()

    只会清空一级缓存

  • localCacheScope

    本地缓存作用域(一级缓存)

    Session: 当前会话的所有数据保存在会话缓存中

    Statement:可以禁用一级缓存

4. 缓存原理图示

缓存的顺序:

二级缓存->一级缓存->数据库

5. mybatis整合ehcache缓存

  1. 导入第三方缓存包即可
  2. 导入与第三方缓存整合的适配包
  3. mapper.xml中使用自定义缓存
  • 在mapper配置文件中加标签

    	<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
    
  • 其他的mapper可以引用这个

    <cache-ref namespace="com.mahaonan.mapper.EmployeeMapper"/>
    
  • ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="conf/ehcache.xsd">
 <!-- 磁盘保存路径 -->
 <diskStore path="ehcache" />
 
 <defaultCache 
   maxElementsInMemory="1" 
   maxElementsOnDisk="10000000"
   eternal="false" 
   overflowToDisk="true" 
   timeToIdleSeconds="120"
   timeToLiveSeconds="120" 
   diskExpiryThreadIntervalSeconds="120"
   memoryStoreEvictionPolicy="LRU">
 </defaultCache>

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

五. mybatis整合spring

1. 整合目的

  • spring管理所有组件。mapper的实现类。@Autowired
  • spring用来管理事务

2. 配置文件

<!--希望管理所有的业务逻辑组件-->
    <context:component-scan base-package="com.mahaonan">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--spring用来控制业务逻辑。数据源,事务控制,aop-->

    <!--引入数据库配置文件-->
    <context:property-placeholder location="jdbc.properties"/>

    <!--配置数据源-->
    <bean id="dataSource" destroy-method="close"
          class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 基本属性:数据库驱动类、连接字符串、用户名、密码 -->
        <property name="driverClass" value="${jdbc.driver}" />
        <property name="jdbcUrl" value="${jdbc.url}" />
        <property name="user" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <!-- 连接数、最小连接数、最大连接数、最大空闲时间 -->
        <property name="initialPoolSize" value="200"/>
        <property name="minPoolSize" value="50"/>
        <property name="maxPoolSize" value="300"/>
        <property name="maxIdleTime" value="60"/>
    </bean>

    <!--spring的事务管理器-->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
    <!--整合mybatis
    目的:1. spring管理所有组件
    -->
    <!--创建SqlSessionFactory-->
    <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--指定全局配置文件的位置-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--指定mapper文件的位置-->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <!--扫描所有mapper接口的实现,让这些mapper能够自动注入-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.mahaonan.mapper" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean" />
    </bean>

六. mybatis逆向工程

1. 配置文件

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

    <context id="DB2Tables" targetRuntime="MyBatis3">

        <!--jdbcConnection: 指定如何连接到目标数据库-->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://mahaonan.com:3306/test"
                        userId="root"
                        password="mhn939510">
        </jdbcConnection>

        <!--java类型解析器-->
        <javaTypeResolver >
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!--指定javaBean的生成策略-->
        <!--targetPackage目标包名,即要生成bean的存放包位置
        targetProject 目标工程,即所在的工程目录
        -->
        <javaModelGenerator targetPackage="com.mahaonan.mbg.bean" targetProject="src/main/java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <!--sqlMapGenerator sql映射生成策略-->
        <sqlMapGenerator targetPackage="com.mahaonan.mbg.mapper"  targetProject="src/main/java">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>

        <!--javaClientGenerator: 指定mapper接口所在的位置-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.mahaonan.mbg.mapper"  targetProject="src/main/java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>

        <!--指定要逆向分析哪些表-->
        <table tableName="tb1_employee" domainObjectName="Employee"/>
    </context>

</generatorConfiguration>

2. 测试类

@Test
    public void test5() throws Exception {
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        InputStream inputStream = MyBatisTest.class.getClassLoader().getResourceAsStream("mybatis_generator.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(inputStream);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
    }

3. maven插件

			<plugins>	
			<plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.2</version>
                <configuration>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                    <configurationFile>${basedir}/src/main/resources/mybatis/mybatis_generator.xml</configurationFile>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.18</version>
                    </dependency>
                </dependencies>
            </plugin>
 			</plugins>


				<resources>
            <!--编译src/main/java目录下的xml文件-->
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <!--指定资源位置-->
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>*.xml</include>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                    <include>**/*.ini</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>

七. Mybatis运行原理

1. 框架分层图

2. 运行原理

2.1 创建SqlSessionFactory

Configuration:封装所有配置信息的详细信息(包括全局配置文件和mapper文件)

MappedStatement: 代表一个增删改查的详细信息

2.2 获取SqlSession对象

返回一个DefaultSqlSession对象,包含Executor和Configuration

2.3 获取Mapper代理对象

使用MapperProxyFactory创建一个MapperProxy的代理对象

MapperProxy中包含了Excutor

2.4 查询实现

2.5 查询总结

2.6 总结

  1. 根据配置文件(全局, sql映射)初始化Configuration对象

  2. 创建一个DefaultSqlSession对象,它里面包含Configuration一级Executor(根据全局配置文件中的defaultExecutorType创建出对应的Executor)

  3. DefaultSqlSession.getMapper(),拿到Mapper接口对应的MapperProxy

  4. MapperProxy里面有(DefaultSqlSession)

  5. 执行增删改查方法:

    1. 调用DefaultSqlSession的增删改查(Executor)

    2. 会创建一个StatementHandler对象,同时也会创建ParameterHandler和ResultSetHandler

    3. 调用StatementHandler预编译参数以及设置参数值;

      使用ParameterHandler来给sql设置参数

    4. 调用StatementHandler的增删改查方法

    5. ResultSetHandler封装结果

四大对象每个创建的时候都有一个interceptorChain.pluginAll(statementHandler)

八. Mybatis插件开发

1. 插件原理

/**
 * 四大对象创建的时候
 * 1. 每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(statementHandler)
 * 2. 获取到所有的Interceptor(拦截器)(插件需要实现的接口)
 *      调用interceptor.plugin(target);返回target包装后的对象
 * 3. 插件机制,我们可以使用插件为目标对象创建出一个代理对象;类似AOP
 *      我们的插件可以为四大对象创建出代理对象
 *      代理对象就可以拦截到四大对象的每一个执行;
 */

2. 插件编写

  1. 编写Interceptor的实现类

    package com.mahaonan.interceptor;
    
    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.plugin.*;
    
    import java.util.Properties;
    
    /**
     * @Author: M˚Haonan
     * @Date: 2019-07-03 16:45
     * @Description:
     */
    
    /**
     * 插件签名
     *  告诉mybatis当前插件拦截哪个对象的哪个方法
     */
    @Intercepts({
            @Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
    })
    public class MyFirstInterceptor implements Interceptor {
    
        /**
         * 拦截目标对象的目标方法的执行
         * 这个方法实际上就是动态代理invoke方法,invocation.proceed()调用了原方法的执行,可以在原方法之		 * 前之后添加自己的业务逻辑,介入到mybatis的实现
         * @param invocation
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            System.out.println("MyFirstInterceptor的 intercept方法");
            Object proceed = invocation.proceed();
            return proceed;
        }
    
        /**
         * 包装目标对象
         * 为目标对象创建一个代理对象
         * @param target  即需要介入的对象
         * @return
         */
        @Override
        public Object plugin(Object target) {
            //我们可以借助Plugin.wrap()来使用当前Interceptor包装我们目标对象
            System.out.println("MyFirstInterceptor的 plugin 方法:mybatis将要包装的对象" + target);
            Object wrap = Plugin.wrap(target, this);
            //为当前target创建的动态代理
            return wrap;
        }
    
        /**
         * 将插件注册时的property属性设置进来
         * @param properties
         */
        @Override
        public void setProperties(Properties properties) {
            System.out.println("插件配置的信息" + properties);
        }
    }
    
    
  2. 使用@Intercepts注解完成插件签名

    @Intercepts({
            @Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
    })
    
  3. 注册到全局配置文件中

    <!--注册插件-->
    	<plugins>
    		<plugin interceptor="com.mahaonan.interceptor.MyFirstInterceptor">
          <!--属性的值到时候会被注入到setProperties(Properties properties)方法中-->
    			<property name="username" value="root"/>
    			<property name="password" value="123456"/>
    		</plugin>
    	</plugins>
    

3. 多个插件

层层代理,多层代理

  • 创建动态代理的时候,是按照插件配置顺序创建层层代理对象。
  • 执行目标方法的时候,按照逆向顺序执行

4. 开发实例

package com.mahaonan.interceptor;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.util.Properties;

/**
 * @Author: M˚Haonan
 * @Date: 2019-07-03 16:45
 * @Description:
 */

/**
 * 插件签名
 *  告诉mybatis当前插件拦截哪个对象的哪个方法
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
})
public class MyFirstInterceptor implements Interceptor {

    /**
     * 拦截目标对象的目标方法的执行
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("MyFirstInterceptor的 intercept方法");
        Object target = invocation.getTarget();
        //拿到target的元数据
        MetaObject metaObject = SystemMetaObject.forObject(target);
        //修改sql语句要用到的参数
        metaObject.setValue("parameterHandler.parameterObject", 2);
        Object proceed = invocation.proceed();
        return proceed;
    }

    /**
     * 包装目标对象
     * 为目标对象创建一个代理对象
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        //我们可以借助Plugin.wrap()来使用当前Interceptor包装我们目标对象
        System.out.println("MyFirstInterceptor的 plugin 方法:mybatis将要包装的对象" + target);
        Object wrap = Plugin.wrap(target, this);
        //为当前target创建的动态代理
        return wrap;
    }

    /**
     * 将插件注册时的property属性设置进来
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("插件配置的信息" + properties);
    }
}

九. Mybatis扩展

1. 分页插件PageHelper

  • maven依赖

    <dependency>
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper</artifactId>
                <version>5.0.0</version>
     </dependency>
    
  • mybatis配置文件

    <plugins>
    		<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
    </plugins>
    
  • 测试

    @Test
        public void test7() {
            SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
            SqlSession sqlSession = sqlSessionFactory.openSession();
            try {
                EmpolyeeMapper empolyeeMapper = sqlSession.getMapper(EmpolyeeMapper.class);
                PageHelper.startPage(2, 1);
                List<Employee> emps = empolyeeMapper.getEmps();
                emps.forEach(System.out::println);
            }finally {
                sqlSession.close();
            }
        }