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>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ReflectMirroring

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值