插件
簡介
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 ;
調用
- select标簽中statementType=“CALLABLE”
-
标簽體中調用文法:
{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>