插件原理
- 在MyBatis运行流程中,四大对象被创建的时候,都会进行.interceptorChain.pluginAll(executor)操作
编写插件
- 编写Interceptor实现类
- 插件签名:告诉MyBatis当前插件拦截那个对象的哪个方法.
- 将写好的插件注册到全局配置文件中
- 示例:编写一个简单的插件.
package mao.shu.interceptor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.util.Properties;
//@Intercepts:编写插件签名,指明要拦截哪个对象的哪个方法
@Intercepts({
@Signature(type= StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
})
public class FirstPlugin implements Interceptor {
/**
* 拦截目标对象的目标方法
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
//输出拦截的方法
System.out.println("FirstPlugin interceptor"+invocation.getMethod());
//输出拦截到的对象
System.out.println("FirstPlugin interceptor"+invocation.getTarget());
//执行目标方法
Object obj = invocation.proceed();
//返回返回值
return obj;
}
/**
* 包装目标对象,为目标对象创建一个代理对象
* @param o
* @return
*/
@Override
public Object plugin(Object o) {
//使用Plugin的warp()方法来替我们包装对象
Object obj = Plugin.wrap(o,this);
return obj;
}
/**
* 获取注册插件时传入的参数
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("插件注册信息: "+properties);
}
}
- 注册插件
<plugins>
<plugin interceptor="mao.shu.interceptor.FirstPlugin">
<!--传入插件参数-->
<property name="test" value="测试数据"/>
</plugin>
</plugins>
- 使用MyBatis访问数据库是拦截器就会被调用
多个插件运行流程
- 当由多个插件的时候,会产生多层代理.创建动态代理的时候是按照插件顺序创建层层代理对象,执行目标方法的时候按照你想顺序执行.
开发插件
- 实现动态改变sql运行的参数:
- 当执行MyBatis的增删改查时,sql语句会存放在ParameterHandler对象之中,只要能够取得这个对象,就可以修改sql语句
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyFirstPlugin...intercept:"+invocation.getMethod());
//动态的改变一下sql运行的参数:以前1号员工,实际从数据库查询3号员工
Object target = invocation.getTarget();
System.out.println("当前拦截到的对象:"+target);
//拿到:StatementHandler==>ParameterHandler===>parameterObject
//拿到target的元数据
MetaObject metaObject = SystemMetaObject.forObject(target);
Object value = metaObject.getValue("parameterHandler.parameterObject");
System.out.println("sql语句用的参数是:"+value);
//修改完sql语句要用的参数
metaObject.setValue("parameterHandler.parameterObject", 11);
//执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
}
PageHelper插件进行分页
- 插件主页:https://github.com/pagehelper/Mybatis-PageHelper
- 主页提供参考文档
- 要导入的jar包
- maven
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.8</version>
</dependency>
- 配置拦截器
- 拦截器可以在MyBatis配置文件中配置也可以在spring配置文件中配置
<!--分页插件-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
</plugin>
</plugins>
- 简单使用
- 在接口调用查询方法之前,使用PageHelper对象,设置开始页面和每页显示数据个数
- PageHelper只会对第一个查询的方法有效
public List<Emp> getEmpSplit(int currentPage,int pageSize){
PageHelper.startPage(currentPage,pageSize);
return this.empMapper.selectByExample(null);
}
- 请求分页链接
<a href="empSplit?cp=1&ps=2">雇员列表分页</a>
- 获取分页的跟多信息
- 使用PageInfo类包装查询结果获取分页信息
public List<Emp> getEmpSplit(int currentPage,int pageSize){
PageHelper.startPage(currentPage,pageSize);
List<Emp> allEmps = this.empMapper.selectByExample(null);
PageInfo info = new PageInfo(allEmps);
//获取开始行
System.out.println(info.getStartRow());
//获取总数量
System.out.println(info.getSize());
//取得总页数
System.out.println(info.getPages());
//取得前一页
System.out.println(info.getPrePage());
//取得下一页
System.out.println(info.getNextPage());
return allEmps;
}
批量操作
- 使用批量操作比单个执行每条sql语句执行速度更快
- 如果不使用批量操作,则每条sql语句都会访问一次数据库,多次访问数据库会导致速度变低
- 而使用批量操作只会访问一次数据库,将要访问的sql语句预编译到数据库中,一次性执行,速度会大大提高
- 示例:获取可批量操作的sqlSession
- 在使用sqlSessionFactory.openSession()的时候,传入一个ExecutoryType.BATH对象表示开启一个可以批量操作的sqlSession.
public void addBath(){
//开启一个可执行批量操作的sqlSession
SqlSession bathSqlSession = this.sqlSessionFactory.openSession(ExecutorType.BATCH);
EmpMapper bathEmpMapper = bathSqlSession.getMapper(EmpMapper.class);
for (int i = 0; i < 10000; i++) {
Emp temp = new Emp();
temp.setEname(i+"批量添加测试");
bathEmpMapper.insert(temp);
}
}
- Spring添加批量sqlSession
- 如果整合Spring,可以在Spring的IOC容器中配置一个可移植性批量操作的sqlSession对象,在需要的时候使用@Autowired自动注入即可
<!--配置sqlSession
可以批量执行sql
-->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"/>
<constructor-arg name="executorType" value="BATCH"/>
</bean>
创建存储过程
- 对于一些比较复杂的操作,旺旺会在数据库中定义一个存储过程,让这个储存过程能够被重复调用.
- 例如:oracle分页的操作需要使用一个 rownum 列,并使用自查询的方式进行分页操作,可以将分页操作定义为一个存储过程.
- 示例:定义oracle的存储过程
- 定义了一个名为:"hello_test"的存储过程
- 这个存储过程需要接收两个参数
- p_start:开始行号
- p_end:结束行号
- 这个存储过程将进行两个查询操作
- select count(*) into p_count from emp;统计查询emp表数据的个数,保存到"e_count"参数中
- 查询出从 p_start 到 p_end 之间的数据,保存到 sys_refcursor 中
create or replace procedure
hello_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 hello_test;
- 使用navicat运行
- 点击函数查看已有的存储过程
MyBatis调用存储过程
- 将存储过程所需要的数据,编写为一个实体类
public class Page {
private Integer start;//保存开始行数
private Integer end;//保存结束行数
private Integer count;//保存总记录数
private List<Emp> emps;//保存查询出来的数据
...getter和setter方法
}
- 定义接口查询方法
public void selectByPage(Page page);
- 定义对应的sql查询语句
- 调用存储过程使用<select>标签表示
<!--
使用statementType="CALLABLE":表示调用存储过程
使用{call 过程名称()}:调用存储过程,
使用#{参数名称,mode=参数类型,jdbcType=对应的jdbc类型}设置参数
IN:表示是传递给过程的
OUT:表示是返回的参数
jdbcType:指定jdbc类型,cursor表示为该参数是一个游标
-->
<select id="selectByPage" statementType="CALLABLE" >
{call HELLO_TEST(
#{start,mode=IN,jdbcType=INTEGER},
#{end,mode=IN,jdbcType=INTEGER},
#{count,mode=OUT,jdbcType=INTEGER},
#{emps,mode=OUT,jdbcType=CURSOR,javaType=ResultSet,resultMap=pageMap}
)}
</select>
<resultMap id="pageMap" type="mao.shu.pojo.Emp">
<id column="empno" property="empno"/>
<result column="ename" property="ename"/>
</resultMap>
- 调用该方法
public Page getPage(Integer start, Integer end){
Page page = new Page();
page.setStart(start);
page.setEnd(end);
this.empMapper.selectByPage(page);
return page;
}
- 控制器
@RequestMapping("/page")
public String page(Map<String,Object> map){
Page temp = this.empService.getPage(1,5);
map.put("allEmps",temp.getEmps());
return "emp_list";
}
- 访问链接http://localhost:8080/SSM1_war_exploded/page
- 数据表1-5个数据
MyBatis枚举处理器
- 默认情况下,MyBatis存储枚举数据,会保存枚举中的名,可以通过修改配置文件中的枚举处理器来改变枚举数据存储行为.
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
javaType="指定要处理的枚举类型"/>
</typeHandlers>
自定义枚举处理器
-
一般情况下,枚举类中会为每一个枚举类成员定义一个自定义状态码和一个枚举信息.
-
当给数据库保存信息的时候,往往会保存枚举的状态码
-
所以默认的枚举处理器无法保存自定义的内容,所以可以自定义类型处理器
- 示例:定义一个EmpStatus枚举类
package mao.shu.pojo;
public enum EmpStatus {
LOGIN(100, "用户登录"),
LOGOUT(200, "用户登出"),
REMOVE(300, "用户不存在");
private Integer code;//保存用户的状态码
private String info;//保存提示信息
private EmpStatus(Integer code, String info) {
this.code = code;
this.info = info;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
- 现在希望在保存数据的时候,保存枚举类中的code信息,而后可以根据 code 值获取对应的枚举类对象.
- 例如:一个雇员保存的状态码是:200则在查询该雇员的时候,希望将200对应的"LOGIN"枚举对象保存到雇员类对象中.
- 自定义一个枚举类型转换器
package mao.shu.typehandler;
import mao.shu.pojo.EmpStatus;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class EmpStatusTypeHandler implements TypeHandler<EmpStatus> {
/**
* 定义数据如何保存到数据库之中
* @param preparedStatement sql语句预处理对象
* @param i 枚举类型数据要保存在sql语句中的位置
* @param empStatus 要保存的枚举类对象
* @param jdbcType
* @throws SQLException
*/
@Override
public void setParameter(PreparedStatement preparedStatement, int i, EmpStatus empStatus, JdbcType jdbcType) throws SQLException {
//将枚举类中的状态码保存到数据库
preparedStatement.setInt(i,empStatus.getCode());
}
/**
* 从数据库获取数据时,枚举类型的转换方式
* @param resultSet 查询结果集合
* @param s 枚举数据所保存的数据列名称
* @return
* @throws SQLException
*/
@Override
public EmpStatus getResult(ResultSet resultSet, String s) throws SQLException {
//获取保存的状态码
int code = resultSet.getInt(s);
for(EmpStatus empStatus:EmpStatus.values()){
if(empStatus.getCode() == code){
return empStatus;
}
}
return null;
}
/**
* 从数据库获取数据时,枚举类型的转换方式
* @param resultSet
* @param i 保存的枚举数据所在索引
* @return
* @throws SQLException
*/
@Override
public EmpStatus getResult(ResultSet resultSet, int i) throws SQLException {
//获取保存的状态码
int code = resultSet.getInt(i);
for(EmpStatus empStatus:EmpStatus.values()){
if(empStatus.getCode() == code){
return empStatus;
}
}
return null;
}
/**
* 从数据库获取数据时,枚举类型的转换方式
* @param callableStatement
* @param i
* @return
* @throws SQLException
*/
@Override
public EmpStatus getResult(CallableStatement callableStatement, int i) throws SQLException {
//获取保存的状态码
int code = callableStatement.getInt(i);
for(EmpStatus empStatus:EmpStatus.values()){
if(empStatus.getCode() == code){
return empStatus;
}
}
return null;
}
}
- 如何使用
- 全局配置文件
- 使用<typeHandler>指定对应的枚举类使用哪个转换器
<typeHandlers>
<!--1、配置我们自定义的TypeHandler -->
<typeHandler handler="mao.shu.typehandler.EmpStatusTypeHandler"
javaType="mao.shu.pojo.EmpStatus"/>
</typeHandlers>
- 在定义映射sql语句的时候,在<sql>标签中使用
- 保存:
<insert id="insert" parameterType="mao.shu.pojo.Emp">
insert into emp (empStatus)
values (empStatus,typeHandler=自定义类型转换器)
</insert>
- 查询:
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmp">
<id column="id" property="id"/>
<result column="empStatus" property="empStatus" typeHandler="自定义类型转换器"/>
</resultMap>
- 注意:如果在参数位置修改TypeHandler,应该保证保存数据和查询数据用的TypeHandler是一样的