天天看点

Mybatis框架:插件,PageHelper插件,BatchExecutor,存储过程

插件

简介

MyBatis在四大对象的创建过程中,都会有插件进行 介入。插件可以利用动态代理机制一层层的包装目标 对象,而实现在目标对象执行目标方法之前进行拦截的效果。就是在执行SQL之前的步骤偷偷干一些坏事

使用

/*
   * 插件原理:在创建四大对象的时候
   * 四大对象:
   *       Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 
   *       ParameterHandler (getParameterObject, setParameters) 
   *       ResultSetHandler (handleResultSets, handleOutputParameters) 
   *       StatementHandler (prepare, parameterize, batch, update, query) 
   * 原码里面,每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler);
   * 获取到所有的Interceptor(拦截器)(插件需要实现的接口);
   * 调用interceptor.plugin(target);返回target包装后的对象
   * 
   * 插件机制,我们可以使用插件为目标对象创建一个代理对象;是AOP(面向切面)的思想
   * 插件可以为四大对象创建出代理对象
   * 代理对象可以拦截四大对象的每一个执行
   * 
   * 创建编写步骤:
   * 1.编写Intercetor的实现类
   * 2.使用@Intercepts注解,完成插件签名
   * 3.还要将写好的插件注册到全局配置文件中
   *       <!-- plugins注册插件 -->
        <plugins>
          <plugin interceptor="bean.MyFirstPlugin"></plugin>
          <plugin interceptor="bean.MySecondPlugin"></plugin>
        </plugins>
   */      
package bean;

import java.util.Properties;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;

/*
 * 完成插件签名:告诉mybatis当前插件用来拦截那个对象的那个方法
 * @Intercepts里面只能写@Signature数组
 * @Signature有三个属性
 *     type:哪一个对象
 *     method这个对象的哪一个方法
 *     args:这个方法的参数列表,因为方法可能重载,需要参数来定位方法
 */
@Intercepts(
        { @Signature(type = StatementHandler.class,method = "parameterize",args = java.sql.Statement.class)}
      )
public class MyFirstPlugin implements Interceptor
{
  /*
   * intercept:拦截:拦截目标对象的目标方法的执行
   * invocation.proceed();是放行目标执行方法
   */
  @Override
  public Object intercept(Invocation invocation) throws Throwable
  {
    System.out.println("MyFirstPlugin--intercept"+invocation.getMethod());
    
    Object proceed = invocation.proceed();
    return proceed;
  }
  
  /*
   * plugin:包装目标对象:是为目标对象创建一个代理对象
   * Plugin.wrap(target, this);Mybatis为创建代理对象封装成了这个方法
   */
  @Override
  public Object plugin(Object target)
  {
    System.out.println("MyFirstPlugin--plugin将包装对象"+target);
    Object wrap = Plugin.wrap(target, this);
    return wrap;
  }

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

}      

PageHelper插件

简介

PageHelper是MyBatis中非常方便的第三方分页 插件。

官方文档:

​​​ https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md​​

使用

需要导入得jar包

​​

​jsqlparser-0.9.5.jar​

​​

​pagehelper-5.0.0-rc.jar​

@org.junit.Test
  public void test10() throws IOException
  {
    String resource = "mybatis.xml"; 
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession openSession = sqlSessionFactory.openSession();
    try
    {
      EmpMapper mapper = openSession.getMapper(EmpMapper.class);
      
      /*
       * 只需要在前面增加PageHelper.startPage(1, 3)就可以 拿到第一页,每一页三条数据
       * 根据返回值Page<Object> page还可以得到很多数据
       * 发送的sql:Preparing: select * from Emp where ename like ? LIMIT 6,3
       */
      Page<Object> page = PageHelper.startPage(3, 3);
      
      List<Emp> emps = mapper.getEmpByNames("%");
      for (Emp emp : emps)
      {
        System.out.println(emp);
      }
      
//      System.out.println("当前页码:"+page.getPageNum());
//      System.out.println("总记录数:"+page.getTotal());
//      System.out.println("总页码数:"+page.getPages());
//      System.out.println("每页数据量:"+page.getPageSize());
      
      /*
       * 还可以在PageHelper.startPage(3, 3);条件下使用PageInfo对象
       * 传入得数值是显示连续几页
       */
      PageInfo<Emp> info = new PageInfo<Emp>(emps, 3);
      System.out.println("当前页码:"+info.getPageNum());
      System.out.println("总记录数:"+info.getTotal());
      System.out.println("每页数据量:"+info.getPageSize());
      System.out.println("总页码量:"+info.getPages());
      System.out.println("是否第一页:"+info.isIsFirstPage());
      System.out.println("当前页码:"+info.getPageNum());
      System.out.println("连续显示得页码");
      int[] navigatepageNums = info.getNavigatepageNums();
      for (int i=0;i<navigatepageNums.length;i++ )
      {
        System.out.println(navigatepageNums[i]);
      }
      
    }finally
    {
      openSession.close();
    }
  }      

BatchExecutor

@org.junit.Test
  public void test11() throws IOException
  {
    String resource = "mybatis.xml"; 
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    
    /*
     * 之前使用的sqlSession都是ExecutorType.SIMPLE的,不能进行批量操作的sqlSession
     * 我们可以去mybatis的全局配置文件里面进行修改,
     * 但是这样就是创建的所有sqlSession都是batch的,这样浪费批量执行的资源
     * 所以我们就创建sqlSession的时候传进参数sqlSessionFactory.openSession(ExecutorType.SIMPLE);
     * 这样创建的是批量执行的sqlSession
     * 批量操作和非批量操作的区别:
     * 
     * 批量操作:预编译sql一次-->设置参数N次,执行sql1次
     * 非批量操作:预编译sqlN次,设置参数N次,执行sqlN次
     * 
     * 这时候你可能想在mybatis整合spring的时候怎么创建批量执行的sqlSession
     * 因为在spring中我们都将sqlSession给spring管理了
     * 我们可以这样:
     * 在spring的配置文件里面配置
     * SqlSessionTemplate是实现了sqlSession的类
     *   <!--配置一个可以进行批量执行的sqlSession  -->
        <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
          <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg>
          <constructor-arg name="executorType" value="BATCH"></constructor-arg>
        </bean>
      然后在service层里创建一个自动注入的SqlSession
      @Autowired
      private SqlSession sqlsession;
      然后调用的时候使用的是批量执行的sqlSession就行
      EmpMapper mapper=sqlSession.getMapper(Emp.class)
      mapper.addEmp(emp);
     */
    SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);
    try
    {
      EmpMapper mapper = openSession.getMapper(EmpMapper.class);
      for(int x=0;x<1000;x++)
      {
        mapper.addEmp(new Emp(null, UUID.randomUUID().toString().substring(0, 6), "email", "女"));        
      }
      openSession.commit();
    }finally
    {
      openSession.close();
    }
  }      

存储过程

实际开发中,我们通常也会写一些存储过程, MyBatis也支持对存储过程的调用

simple

delimiter $$ 
  create procedure test() 
  begin 
    select 'hello'; 
  end $$ 
delimiter ;      

调用

  1. select标签中statementType=“CALLABLE”
  2. 标签体中调用语法:

    ​​

    ​{call procedure_name(#{param1_info},#{param2_info})}​

Oracle的游标处理的存储过程

/*
   * 现在需求是这样:做一个Oracle的分页
   * Oracle的分页是挺麻烦的,它需要借助rownum
   * 就是行号,每一条数据查出来后都会有自己的行号,
   * 但是Oracle又不能像Mysql那样直接在sql语句后面加limit进行分页查询
   * 我们可以利用rownum,比如第一到第三条的数据,可以rownum小于3大于1
   * 但是这些rownum有时候是变化的,比如2-5条数据,大于2的数据后第五条数据就是第三条了
   * 所以上面的方法不可行:
   * 我们就写一个分页的存储过程,使用的是子查询进行分页
   * 
   * 创建存储过程:
   * p_start:第几条数据开始
   * p_end:第几条数据结束
   * p_emps:游标
   * 
   * create or replace procedure
   *        oracel_test
   *       (
   *         p_start in int,p_end in int,p_count out int,p_emps out sys_refcursor
   *       ) as
   * begin
   *     select count(*) into p_count from emp;
   *     open p_emps for
   *       select * from (select rownum rn,e.* from emp e where rownum<=p_end)
   *         where rn>=p_start;
   * end oracle_test;
   * 
   * 然后我们写一个封装查出的数据的类
   * //封装分页查询的数据
    public class Page
    {
      private int start;
      private int end;
      private int count;
      private List<Emp> emps;
   * 
   * 
   * 然后是对应的调用方法:
   *   <!-- public void getPageByProcedure(Page page);
      1.使用select标签定义调用存储过程
      2.statementType="CALLABLE":表示要调用存储过程
      3.#{start,mode=IN,jdbcType=INTEGER}:
            表示第一个参数是start,mode告诉mybatis这是输入参数,类型是INTEGER
        #{emps,mode=OUT,jdbcType=CURSOR,javaType=ResultSet,resultMap=PageEmp}
              jdbcType=CURSOR:表示这是一个游标
              javaType=ResultSet:根据游标得到的数据应该封装成Emp对象,所以返回类型给ResultSet处理
              resultMap=PageEmp:是自定义怎么封装结果
     -->
    <select id="getPageByProcedure" statementType="CALLABLE">
      {call oracle_test
        (
          #{start,mode=IN,jdbcType=INTEGER},
          #{end,mode=IN,jdbcType=INTEGER},
          #{count,mode=OUT,jdbcType=INTEGER},
          #{emps,mode=OUT,jdbcType=CURSOR,javaType=ResultSet.resultMap=PageEmp}
        )
      }
    </select>
    <resultMap type="bean.Emp" id="PageEmp">
      <id column="eid" property="eid"/>
      <result column="ename" property="ename"/>
      <result column="email" property="email"/>
    </resultMap>

   */
  @org.junit.Test
  public void testProcedure() throws IOException
  {
    String resource = "mybatis.xml"; 
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession openSession = sqlSessionFactory.openSession();
    try
    {
      EmpMapper mapper = openSession.getMapper(EmpMapper.class);
      bean.Page page = new bean.Page();
      page.setStart(2);
      page.setEnd(5);
      mapper.getPageByProcedure(page);
      System.out.println("符合查出的数据量"+page.getEmps().size());
      System.out.println("数据库总记录"+page.getCount());
      System.out.println("查出的数据"+page.getEmps());
      
    }finally
    {
      openSession.close();
    }
  }      

类型处理器处理枚举类型

Emp类的属性

public class Emp implements Serializable
{
  private static final long serialVersionUID = 1L;
  private Integer eid;
  private String ename;
  private String email;
  private String gender;
  private Dept dept;
  private EmpStatus empstatus=EmpStatus.LOGOUT;      

枚举的类

package bean;

public enum EmpStatus
{
  LOGIN,LOGOUT,REMOVE
}      
/*
   * 默认mybatis在处理枚举对象的时候保存的是枚举的名字,
   * 使用的是EnumTypeHandler这个类型处理器,保存的是枚举的名字
   * 我们也可以设置成保存枚举的索引
   * 在全局配置文件里面定义
   *     <!-- 配置自定义的类型处理器 -->
      <typeHandlers>
        <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
               javaType="bean.EmpStatus"
        />
      </typeHandlers>
      
   */
  @org.junit.Test
  public void Enum() 
  {
    EmpStatus login =EmpStatus.LOGIN;
    System.out.println("枚举的索引"+login.ordinal());
    System.out.println("枚举的名字"+login.name());
  }      
package bean;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

/*
   * 自定义类型处理器
   * 1.实现TypeHandler接口,或者继承BaseTypeHandler
   */
public class MyEnumEmpStatusTypeHandler implements TypeHandler<EmpStatus>
{
  /*
   * 自定义当前数据如何保存在数据库中
   */
  @Override
  public void setParameter(PreparedStatement ps, int i, EmpStatus parameter, JdbcType jdbcType) throws SQLException
  {
    ps.setString(i, parameter.getCode().toString());
  }

  @Override
  public EmpStatus getResult(ResultSet rs, String columnName) throws SQLException
  {
    int code = rs.getInt(columnName);
    System.out.println("从数据得到的code"+code);
    EmpStatus status = EmpStatus.getEmpStatusByCode(code);
    return status;
  }

  @Override
  public EmpStatus getResult(ResultSet rs, int columnIndex) throws SQLException
  {
    int code = rs.getInt(columnIndex);
    System.out.println("从数据得到的code"+code);
    EmpStatus status = EmpStatus.getEmpStatusByCode(code);
    return status;

  }

  @Override
  public EmpStatus getResult(CallableStatement cs, int columnIndex) throws SQLException
  {
    int code = cs.getInt(columnIndex);
    System.out.println("从数据得到的code"+code);
    EmpStatus status = EmpStatus.getEmpStatusByCode(code);
    return status;
  }

}      
package bean;

public enum EmpStatus
{
  /*
   * 我们希望数据库保存的是code状态码
   */
  LOGIN(101,"登录"),LOGOUT(102,"登出"),REMOVE(103,"移除");
  private Integer code;
  private String msg;
  
  public static EmpStatus getEmpStatusByCode(Integer code)
  {
    switch (code)
    {
    case 101:
      return LOGIN;
    case 102:
      return LOGOUT;
    case 103:
      return REMOVE;
    default:
      return LOGOUT;
    }
  }
  
  private EmpStatus(Integer code, String msg)
  {
    this.code = code;
    this.msg = msg;
  }
  public Integer getCode()
  {
    return code;
  }
  public void setCode(Integer code)
  {
    this.code = code;
  }
  public String getMsg()
  {
    return msg;
  }
  public void setMsg(String msg)
  {
    this.msg = msg;
  }
  
}      
<!-- 配置自定义的类型处理器 -->
  <typeHandlers>
    <!-- 1. 配置自定义的类型处理器 -->
    <typeHandler handler="bean.EmpStatus"
           javaType="bean.EmpStatus"
    />
    <!-- 2.也可以在某个字段的时候告诉mybatis使用什么类型处理器
        保存的时候,在参数获取的时候指定类型处理器
            #{empStatus,typeHandler=xxxx}
        查询的时候,创建自己的封装方式,
            <resultMap type="bean.Emp" id="myempstatus">
              <id column="eid" property="eid"/>
              <result column="empStatus" property="empStatus" typeHandler="bean.EmpStatus"/>
            </resultMap>
     -->
  </typeHandlers>