Mybaits(15)-Mybatis插件

 

目录

 插件原理

项目DEMO

        Mybatis全局配置文件

         Mybatis映射文件

        实体类

        Mapper接口

编写一个Mybatis插件

        插件编写步骤  

        新建自定插件类

        测试类

        测试结果

        源码分析

​多个插件运行流程

         新建MySecondPlugin插件

        注册MySecondPlugin插件

        测试结果

开发插件


 插件原理

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

项目DEMO

        Mybatis全局配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
 PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

	<!--plugins:注册插件  -->
	<plugins>
		<plugin interceptor="com.atguigu.mybatis.dao.MyFirstPlugin">
			<property name="username" value="root"/>
			<property name="password" value="123456"/>
		</plugin>

	</plugins>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" />
				<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
				<property name="username" value="root" />
				<property name="password" value="123456" />
			</dataSource>
		</environment>
	</environments>
	<!-- 将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 -->
	<mappers>
		<mapper resource="EmployeeMapper.xml" />
	</mappers>
</configuration>

         Mybatis映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">
	<select id="getEmpById" resultType="com.atguigu.mybatis.bean.Employee">
		select id,last_name lastName,email,gender from tbl_employee where id = #{id}
	</select>
</mapper>

        实体类

package com.atguigu.mybatis.bean;

public class Employee {
	
	private Integer id;
	private String lastName;
	private String email;
	private String gender;
	
	
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	@Override
	public String toString() {
		return "Employee [id=" + id + ", lastName=" + lastName + ", email="
				+ email + ", gender=" + gender + "]";
	}
}

        Mapper接口

package com.atguigu.mybatis.dao;

import com.atguigu.mybatis.bean.Employee;

public interface EmployeeMapper {
	public Employee getEmpById(Integer id);
}

编写一个Mybatis插件

        插件编写步骤  

1、编写Interceptor的实现类
2、使用@Intercepts注解完成插件签名
3、将写好的插件注册到全局配置文件中

        新建自定插件类

package com.atguigu.mybatis.dao;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.sql.Statement;
import java.util.Properties;

/**
 * @Intercepts 插件签名
 * 告诉Mybatis用当前插件来拦截那个对象的哪个方法
 */
@Intercepts({@Signature(type= StatementHandler.class,method="parameterize",args= Statement.class)})
public class MyFirstPlugin implements Interceptor {
    /**
     * 拦截目标方法的执行
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //执行目标方法 直接被放行
        System.out.println("MyFirstPlugin......intercept:"+invocation.getMethod());
        Object proceed = invocation.proceed();
        return proceed;
    }

    /**
     * 包装目标对象,也就是为目标对象创建一个代理对象
     * @param o
     * @return
     */
    @Override
    public Object plugin(Object o) {
        //借助plugin的wrap方法来使用当前Interceptor包装目标对象
        System.out.println("MyFirstPlugin......plugin : Mybatis将要包装的对象" + o);
        Object wrap = Plugin.wrap(o, this);
        //返回创建的动态代理
        return wrap;
    }

    /**
     * 将插件注册时的properties的属性设置进来
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("插件的配置信息:"+properties);
    }
}

        测试类

package com.atguigu.mybatis.test;

import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.atguigu.mybatis.bean.Employee;
import com.atguigu.mybatis.dao.EmployeeMapper;
import org.junit.Test;

public class MyBatisTest {

	public SqlSessionFactory getSqlSessionFactory() throws IOException {
		String resource = "mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		return new SqlSessionFactoryBuilder().build(inputStream);
	}
	@Test
	public void test01() throws IOException {
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		SqlSession openSession = sqlSessionFactory.openSession();
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			Employee employee = mapper.getEmpById(1);
			System.out.println(mapper);
			System.out.println(employee);
		} finally {
			openSession.close();
		}
	}
}

        测试结果

        源码分析

        在我们编写的插件测试类中进行Debug

        变量signatureMap中存放的我们定义的插件签名和对应的方法,也就是使用当前插件拦截哪个类,哪个方法。变量type:目标对象的类型,当前是CacheExecutor对象。getAllInterfaces()判断type对象是不是在插件签名中。当前插件签名只拦截StatementHandler对象,所以当前不会反悔目标对象,直接返回目标对象。

        同样继续往下执行到DefaultParameterHandler对象,不在插件签名内,返回目标对象,不返回代理对象。

         同样继续往下执行到DefaultResultHandler对象,不在插件签名中,不返回代理对象,返回目标对象。

         执行到StatementHandler对象,确定是插件签名,返回目标对象的代理对象。最后再newStatementHandler(),创建StatementHandler对象时,实际返回的是RoutingStatementHandler对象。

         返回了目前的代理对象,然后执行目标方法:handler.parameterize(stmt);进入该方法,进入Plugins类中的invoke方法,最后就会进入MyFirstPlugin类的intercept方法。只有调用这个invocation.proceed()方法,才能对四大对象真正调用的方法进行拦截。所以通过这个我们可以在目标方法执行前后将参数修改一下。这样就可以介入mybatis的内部流程。

多个插件运行流程

        再定义一个MySecondPlugin插件,同样拦截的是StatementHandler的parameterize方法。

         新建MySecondPlugin插件

package com.atguigu.mybatis.dao;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Statement;
import java.util.Properties;

@Intercepts({@Signature(type= StatementHandler.class,method="parameterize",args= Statement.class)})
public class MySecondPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("MySecondPlugin.......intercept:");
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object o) {
        System.out.println("MySecondPlugin......plugin : Mybatis将要包装的对象" + o);
        Object wrap = Plugin.wrap(o, this);
        return wrap;
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

        注册MySecondPlugin插件

<!--plugins:注册插件  -->
	<plugins>
		<plugin interceptor="com.atguigu.mybatis.dao.MyFirstPlugin">
			<property name="username" value="root"/>
			<property name="password" value="123456"/>
		</plugin>

		<plugin interceptor="com.atguigu.mybatis.dao.MySecondPlugin">
		</plugin>
	</plugins>

        测试结果

                可以看出:创建动态代理的时候,按照注册顺序创建层层代理对象。执行目标方法时,按照逆序方法执行。创建动态代理的时候,先创建StatementHandler的代理对象MyFirstPlugin,然后MySecondPlugin对象又对其进行包装代理。

 

 

开发插件

                场景:动态改变SQL的运行参数 。查询1号员工,实际从数据库中查询的是3号员工。

                修改MyFirstPlugin的intercept()方法:

 @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //执行目标方法 直接被放行
        System.out.println("MyFirstPlugin......intercept:"+invocation.getMethod());
        System.out.println("当前拦截的对象:"+invocation.getArgs());
        //拿到StatementHandler->ParameterHandler===>parameterObjet
        //拿到target的元数据
        MetaObject metaObject = SystemMetaObject.forObject(invocation.getTarget());
        Object value = metaObject.getValue("parameterHandler.parameterObject");
        System.out.println("sql语句用的参数是"+value);
        metaObject.setValue("parameterHandler.parameterObject",3);
        Object proceed = invocation.proceed();
        return proceed;
    }

        测试结果:sql语句用的参数是1 ,最后查询到的实际是3。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值