插件
简介
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>