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>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章