天天看點

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>