Mybatis_第⼋部分:Mybatis插件

Mybatis_第⼋部分:Mybatis插件

8.1 插件简介

⼀般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者⾃⾏拓展。这样的好处是显⽽易⻅的,⼀是增加了框架的灵活性。⼆是开发者可以结合实际需求,对框架进⾏拓展,使其能够更好的⼯作。以MyBatis 为例,我们可基于 MyBati s 插件机制实现分⻚、分表,监控等功能。由于插件和业务 ⽆关,业务也⽆法感知插件的存在。因此可以⽆感植⼊插件,在⽆形中增强功能

8.2 Mybatis插件介绍

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

 

MyBatis 所允许拦截的⽅法如下:
  • 执⾏器Executor (updatequerycommitrollback等⽅法)
  • SQL语法构建器StatementHandler (prepareparameterizebatchupdates query等⽅ 法)
  • 参数处理器ParameterHandler (getParameterObjectsetParameters⽅法)
  • 结果集处理器ResultSetHandler (handleResultSetshandleOutputParameters等⽅法)

8.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 是被重重代理后的对象
如果我们想要拦截 Executor query ⽅法,那么可以这样定义插件:
@Intercepts ({
@Signature (
type = Executor . class ,
method = "query" ,
args =
{ MappedStatement . class , Object . class , RowBounds . class , ResultHandler . class }
)
})
public class ExeunplePlugin implements Interceptor {
// 省略逻辑
}
除此之外,我们还需将插件配置到 sqlMapConfig.xm l 中。
<plugins>
<plugin interceptor = "com.lagou.plugin.ExamplePlugin" >
</plugin>
</plugins>
这样 MyBatis 在启动时可以加载插件,并保存插件实例到相关对象 (InterceptorChain ,拦截器链 ) 中。 待准备⼯作做完后,MyBatis 处于就绪状态。我们在执⾏ SQL 时,需要先通过 DefaultSqlSessionFactory创建 SqlSession Executor 实例会在创建 SqlSession 的过程中被创建, Executor 实例创建完毕后,MyBatis会通过 JDK 动态代理为实例⽣成代理类。这样,插件逻辑即可在 Executor 相关⽅法被调⽤前执⾏。
以上就是 MyBatis 插件机制的基本原理

8.4 ⾃定义插件

8.4.1 插件接⼝

Mybatis 插件接⼝ -Interceptor
• Intercept ⽅法,插件的核⼼⽅法
• plugin ⽅法,⽣成 target 的代理对象
• setProperties ⽅法,传递插件所需参数

8.4.2⾃定义插件

设计实现⼀个⾃定义插件
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 );
}
}
sqlMapConfig.xml
<plugins>
<plugin interceptor = "com.lagou.plugin.MySqlPagingPlugin" >
<!-- 配置参数 -->
<property name = "name" value = "Bob" />
</plugin>
</plugins>
mapper 接⼝
public interface UserMapper {
List < User > selectUser ();
}
mapper.xml
<mapper namespace = "com.lagou.mapper.UserMapper" >
<select id = "selectUser" resultType = "com.lagou.pojo.User" >
SELECT
id,username
FROM
user
</select>
</mapper>
测试类
public class PluginTest {
@Test
public void test () throws IOException {
InputStream resourceAsStream =
Resources . getResourceAsStream ( "sqlMapConfig.xml" );
SqlSessionFactory sqlSessionFactory = new
SqlSessionFactoryBuilder (). build ( resourceAsStream );
SqlSession sqlSession = sqlSessionFactory . openSession ();
UserMapper userMapper = sqlSession . getMapper ( UserMapper . class );
List < User > byPaging = userMapper . selectUser ();
for ( User user : byPaging ) {
System . out . println ( user );
}
}
}

8.5 源码分析

执⾏插件逻辑
Plugin 实现了 InvocationHandler 接⼝,因此它的 invoke ⽅法会拦截所有的⽅法调⽤。 invoke ⽅法会 对所拦截的⽅法进⾏检测,以决定是否执⾏插件逻辑。该⽅法的逻辑如下:
public class PluginTest {
@Test
public void test () throws IOException {
InputStream resourceAsStream =
Resources . getResourceAsStream ( "sqlMapConfig.xml" );
SqlSessionFactory sqlSessionFactory = new
SqlSessionFactoryBuilder (). build ( resourceAsStream );
SqlSession sqlSession = sqlSessionFactory . openSession ();
UserMapper userMapper = sqlSession . getMapper ( UserMapper . class );
List < User > byPaging = userMapper . selectUser ();
for ( User user : byPaging ) {
System . out . println ( user );
}
}
}
// -Plugin
public Object invoke ( Object proxy , Method method , Object [] args ) throws
Throwable {
try {
/*
* 获取被拦截⽅法列表,⽐如:
* signatureMap.get(Executor.class), 可能返回 [query, update,
commit]
*/
Set < Method > methods =
signatureMap . get ( method . getDeclaringClass ());
// 检测⽅法列表是否包含被拦截的⽅法
if ( methods != null && methods . contains ( method )) {
// 执⾏插件逻辑
return interceptor . intercept ( new Invocation ( target , method ,
args ));
// 执⾏被拦截的⽅法
return method . invoke ( target , args );
} catch ( Exception e ){
}
}
invoke ⽅法的代码⽐较少,逻辑不难理解。⾸先 ,invoke ⽅法会检测被拦截⽅法是否配置在插件的
@Signature 注解中,若是,则执⾏插件逻辑,否则执⾏被拦截⽅法。插件逻辑封装在 intercept 中,该⽅法的参数类型为Invocationo Invocation 主要⽤于存储⽬标类,⽅法以及⽅法参数列表。下⾯简单看⼀下该类的定义
public class Invocation {
private final Object target ;
private final Method method ;
private final Object [] args ;
public Invocation ( Object targetf Method method , Object [] args ) {
this . target = target ;
this . method = method ;
// 省略部分代码
public Object proceed () throws InvocationTargetException ,
IllegalAccessException { // 调⽤被拦截的⽅法
>>
关于插件的执⾏逻辑就分析结束

8.6 pageHelper分⻚插件

MyBati s 可以使⽤第三⽅的插件来对功能进⾏扩展,分⻚助⼿ 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 ());

8.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 ( "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 接⼝
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 ); // 根据主键字段进⾏删除,⽅法参数必须包含完
整的主键属性
//(2)example ⽅法
Example example = new Example ( User . class );
example . createCriteria (). andEqualTo ( "id" , 1 );
example . createCriteria (). andLike ( "val" , "1" );
// ⾃定义查询
List < User > users1 = userMapper . selectByExample ( example );
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值