Mybatis篇-(十)插件开发与扩展(MyBatis实用场景)
发布日期:2021-05-14 16:24:27 浏览次数:27 分类:精选文章

本文共 10287 字,大约阅读时间需要 34 分钟。

十、Mybatis-插件开发与扩展(MyBatis实用场景)

插件开发

  • MyBatis在四大对象的创建过程中,都会有插件进行 介入。插件可以利用动态代理机制一层层的包装目标 对象,而实现在目标对象执行目标方法之前进行拦截 的效果
  • MyBatis 允许在已映射语句执行过程中的某一点进行 拦截调用
  • 默认情况下,MyBatis 允许使用插件来拦截的方法调 用包括:
  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

插件原理

在四大对象创建的时候

  • 每个创建出来的对象不是直接返回的,而是
    要执行interceptorChain.pluginAll(parameterHandler);
public Object pluginAll(Object target) {   		    for (Interceptor interceptor : interceptors) {   		      target = interceptor.plugin(target);		    }		    return target;		  }
  • 获取到所有的Interceptor(拦截器)(插件需要实现的接口);
    调用interceptor.plugin(target);返回target包装后的对象
  • 插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP(面向切面)
    • 我们的插件可以为四大对象创建出代理对象
    • 代理对象就可以拦截到四大对象的每一个执行

插件的使用

  • 编写插件实现Interceptor接口,并使用 @Intercepts注解完成插件签名
/** * 完成插件签名: *		告诉MyBatis当前插件用来拦截哪个对象的哪个方法 */@Intercepts(		{   			@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)		})public class MyFirstPlugin implements Interceptor{   	/**	 * intercept:拦截:	 * 		拦截目标对象的目标方法的执行;	 */	@Override	public Object intercept(Invocation invocation) throws Throwable {   		// TODO Auto-generated method stub		System.out.println("MyFirstPlugin...intercept:"+invocation.getMethod());		//执行目标方法		Object proceed = invocation.proceed();		//返回执行后的返回值		return proceed;	}	/**	 * plugin:	 * 		包装目标对象的:包装:为目标对象创建一个代理对象	 */	@Override	public Object plugin(Object target) {   		// TODO Auto-generated method stub		//我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象		System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象"+target);		Object wrap = Plugin.wrap(target, this);		//返回为当前target创建的动态代理		return wrap;	}	/**	 * setProperties:	 * 		将插件注册时 的property属性设置进来	 */	@Override	public void setProperties(Properties properties) {   		// TODO Auto-generated method stub		System.out.println("插件配置的信息:"+properties);	}}
  • 在全局配置文件中注册插件
  • 运行结果
    在这里插入图片描述

若配置了多个拦截器拦截同一个对象方法

在这里插入图片描述

小案例

动态的改变一下sql运行的参数:以前1号员工,实际从数据库查询11号员工

只需要修改MyFirstPlugin中的intercept方法

/**	 * intercept:拦截:	 * 		拦截目标对象的目标方法的执行;	 */	@Override	public Object intercept(Invocation invocation) throws Throwable {   		// TODO Auto-generated method stub		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;	}

运行结果

在这里插入图片描述

扩展( MyBatis实用场景 )

分页插件PageHelper

说明

使用步骤

  • 导入jar包

    在这里插入图片描述

  • 在MyBatis全局配置文件中配置分页插件

  • 使用PageHelper提供的方法进行分页
@Test	public void test01() throws IOException {   		// 1、获取sqlSessionFactory对象		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();		// 2、获取sqlSession对象		SqlSession openSession = sqlSessionFactory.openSession();		try {   			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);            //第一个参数 pageNum(显示第几页)            //第二个参数 pageSize(每页显示多少页)			Page page = PageHelper.startPage(1, 1000);			List
emps = mapper.getEmps(); for (Employee employee : emps) { System.out.println(employee); } System.out.println("当前页码:"+page.getPageNum()); System.out.println("总记录数:"+page.getTotal()); System.out.println("每页的记录数:"+page.getPageSize()); System.out.println("总页码:"+page.getPages()); } finally { openSession.close(); } }

运行效果

在这里插入图片描述

  • 可以使用更强大的PageInfo封装返回结果
@Test	public void test02() throws IOException {   		// 1、获取sqlSessionFactory对象		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();		// 2、获取sqlSession对象		SqlSession openSession = sqlSessionFactory.openSession();		try {   			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);			Page page = PageHelper.startPage(5, 1000);			List
emps = mapper.getEmps(); //第二个参数 传入要连续显示的页数 如 2,3,4,5,6 PageInfo
info = new PageInfo<>(emps, 5); for (Employee employee : emps) { System.out.println(employee); } 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("连续显示的页码:"); int[] nums = info.getNavigatepageNums(); for (int i = 0; i < nums.length; i++) { System.out.println(nums[i]); } } finally { openSession.close(); } }

运行效果

在这里插入图片描述

批量处理

概述

默认的 openSession() 方法没有参数,它会创建有如下特性的

  • 会开启一个事务(也就是不自动提交)
  • 连接对象会从由活动环境配置的数据源实例得到
  • 事务隔离级别将会使用驱动或数据源的默认设置
  • 预处理语句不会被复用,也不会批量处理更新

openSession 方法的 ExecutorType 类型的参数,枚举类型

  • ExecutorType.SIMPLE: 这个执行器类型不做特殊的事情(这是默认装配 的)
  • 它为每个语句的执行创建一个新的预处理语句
  • ExecutorType.REUSE: 这个执行器类型会复用预处理语句
  • ExecutorType.BATCH: 这个执行器会批量执行所有更新语句

批量处理的使用

批量操作我们是使用MyBatis提供的BatchExecutor进行的, 他的底层就是通过jdbc攒sql的方式进行的。我们可以让他 攒够一定数量后发给数据库一次

测试

核心代码就是

SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
@Test	public void testBatch() throws IOException{   		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();		//可以执行批量操作的sqlSession		SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);		try{   			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);			for (int i = 0; i < 10000; i++) {   				mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1"));			}			openSession.commit();		}finally{   			openSession.close();		}	}

总结:

批量处理:(预编译sql一次==>设置参数===>10000次===>执行(1次)) 耗时4598

非批量:(预编译sql=设置参数=执行)==》10000次 耗时10200

存储过程

以Oracle为例

使用案例

xml

JavaBean

public class PageEmp {       private int start;    private int end;    private int count;    private List
emps; }

创建存储过程

在这里插入图片描述

测试

/**	 * oracle分页:	 * 		借助rownum:行号;子查询;	 * 存储过程包装分页逻辑	 * @throws IOException 	 */	@Test	public void testProcedure() throws IOException{   		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();		SqlSession openSession = sqlSessionFactory.openSession();		try{   			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);			OraclePage page = new OraclePage();			page.setStart(1);			page.setEnd(5);			mapper.getPageByProcedure(page);						System.out.println("总记录数:"+page.getCount());			System.out.println("查出的数据:"+page.getEmps().size());			System.out.println("查出的数据:"+page.getEmps());		}finally{   			openSession.close();		}			}

自定义类型处理器(枚举为例)

我们可以通过自定义TypeHandler的形式来在设置参数或 者取出结果集的时候自定义参数封装策略

测试

枚举类

/** * 希望数据库保存的是100,200这些状态码,而不是默认0,1或者枚举的名 * @author lfy * */public enum EmpStatus {   	LOGIN(100,"用户登录"),LOGOUT(200,"用户登出"),REMOVE(300,"用户不存在");	private Integer code;	private String msg;	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;	}		//按照状态码返回枚举对象	public static EmpStatus getEmpStatusByCode(Integer code){   		switch (code) {   			case 100:				return LOGIN;			case 200:				return LOGOUT;				case 300:				return REMOVE;			default:				return LOGOUT;		}	}}

JavaBean

public class Employee {       private Integer id;    private String lastName;    private String email;    private String gender;    //员工状态    private EmpStatus empStatus=EmpStatus.LOGOUT;    ...}

自定义类型处理器

/** * 1、实现TypeHandler接口。或者继承BaseTypeHandler * @author lfy * */public class MyEnumEmpStatusTypeHandler implements TypeHandler
{ /** * 定义当前数据如何保存到数据库中 */ @Override public void setParameter(PreparedStatement ps, int i, EmpStatus parameter, JdbcType jdbcType) throws SQLException { // TODO Auto-generated method stub System.out.println("要保存的状态码:"+parameter.getCode()); ps.setString(i, parameter.getCode().toString()); } @Override public EmpStatus getResult(ResultSet rs, String columnName) throws SQLException { // TODO Auto-generated method stub //需要根据从数据库中拿到的枚举的状态码返回一个枚举对象 int code = rs.getInt(columnName); System.out.println("从数据库中获取的状态码:"+code); EmpStatus status = EmpStatus.getEmpStatusByCode(code); return status; } @Override public EmpStatus getResult(ResultSet rs, int columnIndex) throws SQLException { // TODO Auto-generated method stub int code = rs.getInt(columnIndex); System.out.println("从数据库中获取的状态码:"+code); EmpStatus status = EmpStatus.getEmpStatusByCode(code); return status; } @Override public EmpStatus getResult(CallableStatement cs, int columnIndex) throws SQLException { // TODO Auto-generated method stub int code = cs.getInt(columnIndex); System.out.println("从数据库中获取的状态码:"+code); EmpStatus status = EmpStatus.getEmpStatusByCode(code); return status; }}

全局配置文件配置类型转换器

测试插入

@Test	public void testEnum() throws IOException{   		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();		SqlSession openSession = sqlSessionFactory.openSession();		try{   			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);			Employee employee = new Employee("test_enum", "enum@atguigu.com","1");			mapper.addEmp(employee);			System.out.println("保存成功"+employee.getId());			openSession.commit();		}finally{   			openSession.close();		}	}

运行结果

在这里插入图片描述

测试查询

@Test	public void testEnum() throws IOException{   		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();		SqlSession openSession = sqlSessionFactory.openSession();		try{   			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);			Employee employee = new Employee("test_enum", "enum@atguigu.com","1");			Employee empById = mapper.getEmpById(10012);			System.out.println(empById.getEmpStatus());		}finally{   			openSession.close();		}	}

运行结果

在这里插入图片描述

手动修改数据库中的状态码再查询

在这里插入图片描述

上一篇:牛客-链表中环的入口节点(Java)
下一篇:Mybatis篇-(九)Mybatis工作原理的初理解(图解)

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2025年04月30日 17时42分54秒