Mybatis之插件

Mybatis插件

1、插件介绍

开源框架一般都会提供插件或其他形式的扩展点,供开发自行拓展。这样的好处显而易见的,一是增加了框架的灵活性,二是开发者可以结合实际需求,对框架进行扩展,使其能够更好的工作。以mybatis为例,我们可以基于mybatis插件机制实现分页、分表、监控等功能。由于插件和业务无关,业务也无法感知插件的存在。因此可以无感植入插件,在无形之中增强功能。

2、Mybatis插件介绍

Mybatis作为一个应用广泛的优秀的ORM开源框架,这个框架具有强大的灵活性,在四大组件(Executor,StatementHandler,ParameterHandler,ResultHandler)提供了简单易用的插件扩展机制。Mybatis对持久层的操作就是借助于四大核心对象。Mybatis支持用插件对四大核心对象进行拦截,对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说mybatis中的四大对象都是代理对象。


Mybatis所允许拦截的方法如下:

  • 执行器Executor(update、query、commit、rollback等方法)
  • SQL语法构建器StatementHandler(prepare、parameterize、batch、update、query等方法)
  • 参数处理器ParameterHandler(getParameterObject、setParameter方法)
  • 结果处理器ResultSetHandler(handleResultSets、handleOutputParameters等方法)

3、Mybatis插件的原理
在四大对象创建的时候

  • 1、每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler);
  • 2、获取到所有的Interceptor拦截器,调用interceptor.plugin(target);返回target包装后的对象
  • 3、插件机制,我们可以使用插件为目标对象创建一个代理对象,AOP面向切面,我们的插件可以为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行。

拦截

插件具体是如何拦截并附加额外的功能呢?以ParameterHandler来说

public ParameterHandler newParameterHandler(MappedStatement mappedStatement,
		Object object, BoundSql sql, InterceptorChain interceptorChain){
 	ParameterHandler parameterHandler =
	mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql);
 	parameterHandler = (ParameterHandler)
	interceptorChain.pluginAll(parameterHandler);
 	return parameterHandler;
}
public Object pluginAll(Object target) {
	for (Interceptor interceptor : interceptors) {
 		target = interceptor.plugin(target);
 	}
 	return target;
}

interceptorChain保存了所有的拦截器(interceptors),是mybatis初始化的时候创建的。调用拦截器链中的拦截器依次对目标进行拦截或者增强。interceptor.plugin(target)中的target就可以理解为mybatis中的四大对象。返回的target是被重重代理后的对象。
如果我们想要拦截某个Ex’ecutor的query方法,那么可以这样定义插件:

除此之外,我们还需要将插件配置到sqlMapConfig.xml中

<plugins>
 	<plugin interceptor="com.lagou.plugin.ExamplePlugin">
 	</plugin>
</plugins>

这样mybatis在启动时候可以加载插件,并保存插件实例到相关对象(interceptorChain,拦截器链)中,待准备工作做完后,mybatis处于就绪状态。我们执行SQL时候,需要先通过DefaultSqlSessionFactory创建session。Executor实例会创建SqlSession的过程中被创建,Executor实例被创建完毕后,mybatis会通过JDK动态代理为实例生成代理类。这样,插件逻辑即可在Executor相关方法被调用前执行。

4、自定义插件
4.1 插件接口
Mybatis插件接口-Interceptor

  • Interceptor方法,插件的核心方法
  • plugin方法,生成target的代理对象
  • setProperties方法,传递插件所需参数
    4.2 自定义插件
    设计实现一个自定义插件
@Intercepts({ // 注意这个大花括号,这里可以定义多个@Singature对多个地方拦截,都用这个拦截器
 @Signature(type = StatementHandler.class, // 指的是拦截哪个接口
 method = "prepare", // 接口内哪个方法名
 args = { Connection.class, Integer.class}), // 拦截方法的入参,按顺序写,不能多不能少,如果方法重载,可是要通过方法名和入参来确定唯一的

})
public class MyPlugin implements Interceptor {
 private final Logger logger = LoggerFactory.getLogger(this.getClass());
 // 这里是每次执行操作的时候,都会进行这个拦截器方法内
 @Override
 public Object intercept(Invocation invocation) throws Throwable {
 // 增强逻辑
 	System.out.println("对方法进行增强....");
 	return invocation.proceed(); // 执行原方法
 }
 /**
 * // 主要是为了把这个拦截器生成一个代理放到拦截器链中
 * @Description  包装目标对象 为目标对象创建代理对象
 * @Param target 要拦截的对象
 * @Return 代理对象
 */
 @Override
 public Object plugin(Object target) {
 	System.out.println("将包装的目标对象:"+target);
	 return Plugin.wrap(target,this);
 }
 /** 获取配置文件的属性 **/
 // 插件初始化的时候调用 只调一次,插件配置属性从这里设置出来
 @Override
 public void setProperties(Properties properties) {
 	System.out.println("插件的初始化参数:"+properties);
 }
}

sqlMapConfig.xml

<plugins>
 <plugin interceptor="com.my.plugin.MySqlPagingPlugin">
 <!--配置参数-->
 <property name="name" value="Bob"/>
 </plugin>
</plugins>

mapper.xml

<mapper namespace="com.my.mapper.UserMapper">
 <select id="selectUser" resultType="com.my.pojo.User">
 	SELECT
 		id,username
 	FROM user
 </select>
</mapper>

5、源码分析
执行插件逻辑

plugin实现了InvocationHandler接口,因此它的invoke方法会拦截所有的调用。invoke方法会拦截的方法进行检测,以决定是否执行插件逻辑。该方法的逻辑如下:

invoke方法的代码比较少,逻辑不难理解。首先,invoke方法会检测被拦截是否配置在插件的@Signature注解中,若是,则执行插件逻辑,否则执行被拦截方法。插件逻辑是封装在interceptor中,该方法的参数类型为Invocation。其中主要用于存放目标类,方法以及参数列表。下面简单看一下该类的定义

6、PageHelper分页插件
Mybatis可以使用第三方的插件来对此功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据
开发步骤:

①导入通用PageHelper插件坐标

②在mybatis核心配置文件中配置Pagehelper插件

③测试分页数据获取

①导入通用Pagehelper坐标

<!-- 分页助手 -->
<dependency>
 <groupId>com.github.pagehelper</groupId>
 <artifactId>pagehelper</artifactId>
 <version>3.7.5</version>
</dependency>
<dependency>
 <groupId>com.github.jsqlparser</groupId>
 <artifactId>jsqlparser</artifactId>
 <version>0.9.1</version>
</dependency>

②在mybatis核心配置文件中配置Pagehelper插件

<!-- 注意:分页助手的插件  配置在通用mapper之前 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
 <!-- 指定方言 -->
 <property name="dialect" value="mysql"/>
</plugin>

③测试分页代码实现

@Test
public void testPageHelper(){
 // 设置分页参数
 PageHelper.startPage(1,2);
 List<User> select = userMapper2.select(null);
 for(User user : select){
 System.out.println(user);
 }
}

获得分页相关的其他参数

// 其他分页数据
PageInfo<User> pageInfo = new PageInfo<User>(select);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示长度:"+pageInfo.getPageSize());
System.out.println("是否第一页:"+pageInfo.isIsFirstPage());
System.out.println("是否最后一页:"+pageInfo.isIsLastPage());

7、通用mapper
什么是通用Mapper
通用mapper就是为了解决单表增删改查,基于mybatis的插件机制。开发人员不需要编写SQL,不需要在DAO中增加方法,只要写好实体类,就能支持相应的增删改查方法
如何使用
1、首先在maven项目中,在pom.xml文件中引入mapper的依赖

<dependency>
 <groupId>tk.mybatis</groupId>
 <artifactId>mapper</artifactId>
 <version>3.1.2</version>
</dependency>

2、Mybatis配置文件中完成配置

<plugins>
 <!-- 分页插件:如果有分页插件,要排在通用mapper之前
 <plugin interceptor="com.github.pagehelper.PageHelper">
 <property name="dialect" value="mysql"/>
 </plugin>
-->
 <plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
 <!-- 通用Mapper接口,多个通用接口用逗号隔开 -->
 <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
 </plugin>
 </plugins>

3、实体类设置主键

@Table(name = "t_user")
public class User {
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Integer id;
 private String username;
}

4、定义通用mapper

import com.lagou.domain.User;
import tk.mybatis.mapper.common.Mapper;
public interface UserMapper extends Mapper<User> {
}

5、测试

public class UserTest {

 @Test
 public void test1() throws IOException {
	InputStream resourceAsStream = Resources.getResourceAsStream("mybatismapper-			 config.xml");
 	SqlSessionFactory build = new
	SqlSessionFactoryBuilder().build(resourceAsStream);
 	SqlSession sqlSession = build.openSession();
 	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
 	User user = new User();
 	user.setId(4);
 	// mapper基础接口
 	// select接口
	User user1 = userMapper.selectOne(user); // 根据实体中的属性进行查询,只能有一个返回值
 	List<User> users = userMapper.select(null); //ັ 查询全部结果
 	userMapper.selectByPrimaryKey(1);  // 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
	userMapper.selectCount(user); // 根据实体中的属性查询总数,查询条件使用等号
	 // insert接口 
	int insert = userMapper.insert(user); // 保存一个实体,null值也会被保存,不使用数据库默认值
 	int i = userMapper.insertSelective(user); // 保存实体,null值不会被保存,使用数据库默认值
 	// update 接口
	 int i1 = userMapper.updateByPrimaryKey(user); // 根据主键更新实体全部字段,null值被更新
 	// delete接口 
 	int delete = userMapper.delete(user);// 根据实体属性作为条件进行删除,查询条件使用等号
	 userMapper.deleteByPrimaryKey(1); // 根据主键进行删除,方法参数必须包含完整主键属性

	 // example方法
 	Example example = new Example(User.class);
 	example.createCriteria().andEqualTo("id",1);
 	example.createCriteria().andLike("val", "1");
 	// 自定义查询
 	List<User> users1 = userMapper.selectByExample(example);
 }
}

版权声明:本文为qq_37589292原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。