插件简介
一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展。这样的好处是显而易见的,一是增加了框架的灵活性。二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作。以MyBatis
为例,我们可基于MyBatis
插件机制实现分页、分表,监控等功能。由于插件和业务无关,业务也无法感知插件的存在。因此可以无感植入插件,在无形中增强功能。
MyBatis插件介绍
MyBatis
作为一个应用广泛的优秀的ORM
开源框架,具有强大的灵活性。在四大组件(Executor
、StatementHandler
、ParameterHandler
、ResultSetHandler
)处提供了简单易用的插件扩
展机制。Mybatis
对持久层的操作就是借助于四大核心对象。MyBatis
支持用插件对四大核心对象进行拦截,对MyBatis
来说插件就是拦截器,用来增强核心对象的功能。增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis
中的四大对象都是代理对象。

MyBatis
所允许拦截的方法如下:
- 执行器
Executor
(update
、query
、commit
、rollback
等方法)。 SQL
语法构建器StatementHandler
(prepare
、parameterize
、batch
、updates
、query
等方法)。- 参数处理器
ParameterHandler
(getParameterObject
、setParameters
方法)。 - 结果集处理器
ResultSetHandler
(handleResultSets
、handleOutputParameters
等方法)。
MyBatis插件原理
在四大对象创建的时候:
- 每个创建出来的对象不是直接返回的,而是均调用
interceptorChain.pluginAll(parameterHandler)
。 - 获取到所有的
Interceptor
(拦截器)(插件需要实现的接口) -> 调用interceptor.plugin(target)
-> 返回target
包装后的对象。 - 插件机制,我们可以使用插件为目标对象创建一个代理对象 -> 通过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
是被重重代理后的对象。
如果我们想要拦截Executor
的query
方法,那么可以这样定义插件:
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }
)
})
public class ExamplePlugin implements Interceptor {
//省略逻辑
}
除此之外,我们还需将插件配置到sqlMapConfig.xml
中。
<plugins>
<plugin interceptor="com.rubin.plugin.ExamplePlugin">
</plugin>
</plugins>
这样MyBatis
在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain
,拦截器链) 中。待准备工作做完后,MyBatis
处于就绪状态。我们在执行SQL
时,需要先通过DefaultSqlSessionFactory
创建 SqlSession
。Executor
实例会在创建 SqlSession
的过程中被创建, Executor
实例创建完毕后,MyBatis
会通过JDK动态代理为实例生成代理类。这样,插件逻辑即可在 Executor
相关方法被调用前执行。
以上就是MyBatis
插件机制的基本原理。
自定义插件
插件接口简介
我们自定义插件,就需要实现MyBatis
提供的Interceptor
接口。并实现下述方法:
intercept
方法,插件的核心方法。plugin
方法,生成target的代理对象。setProperties
方法,传递插件所需参数。
实现步骤
首先,实现我们的自定义插件。代码如下:
Intercepts ({
//注意看这个大花括号,也就这说这里可以定义多个@Signature对多个地方拦截,都用这个拦截器
@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);
}
}
在配置文件中,加入我们的自定义拦截器:
<plugins>
<plugin interceptor="com.rubin.plugin.MyPlugin">
<!--配置参数-->
<property name="name" value="Bob"/>
</plugin>
</plugins>
这样,我们就实现了一个简单的MyBatis插件。
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());
通用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.rubin.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("sqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setId(4);
// (1)mapper基础接口
// select 接口
// 根据实体中的属性进行查询,只能有—个返回值
User user1 = userMapper.selectOne(user);
// 查询全部结果
List<User> users = userMapper.select(null);
// 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
userMapper.selectByPrimaryKey(1);
// 根据实体中的属性查询总数,查询条件使用等号
userMapper.selectCount(user);
// insert 接口
//保存一个实体,null值也会保存,不会使用数据库默认值
int insert = userMapper.insert(user);
// 保存实体,null的属性不会保存,会使用数据库默认值
int i = userMapper.insertSelective(user);
// update 接口
// 根据主键更新实体全部字段,null值会被更新
int i1 = userMapper.updateByPrimaryKey(user);
// delete 接口
//根据实体属性作为条件进行删除,查询条件使用等号
int delete = userMapper.delete(user);
// 根据主键字段进行删除,方法参数必须包含完整的主键属性
userMapper.deleteByPrimaryKey(1);
// (2)example方法
Example example = new Example(User.class);
example.createCriteria().andEqualTo("id", 1);
example.createCriteria().andLike("val", "1");
// 自定义查询
List<User> users1 = userMapper.selectByExample(example);
}
}
文章评论