8.mybatis插件

第⼋部分:Mybatis插件

8.1 插件简介

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

8.2 Mybatis插件介绍

Mybati s 作为⼀个应⽤⼴泛的优秀的 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 等⽅法 )

 

 

8.3 Mybatis插件原理 (di)

在四⼤对象创建的时候
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⾃定义插件

设计实现⼀个⾃定义插件
 
package com.ch.plugin;


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

import java.sql.Connection;
import java.util.Properties;

@Intercepts({
        @Signature(type= StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class})
})
public class MyPlugin implements Interceptor {

    /*
    拦截方法:只要被拦截的目标对象的目标方法被执行时,每次都会执行intercept方法
    */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("对方法进行了增强");
        return invocation.proceed(); //让原方法执行
    }

    /*
    主要为了把当前的拦截器生成代理存到拦截器当中
    * */
    @Override
    public Object plugin(Object o) {
        Object wrap = Plugin.wrap(o, this);
        return wrap;
    }

   /* 获取配置文件的参数
   * */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("获取得到的配置文件的参数"+properties);

    }
}
sqlMapConfig.xml配置自定义插件
<plugins>
    <plugin interceptor="com.ch.plugin.MyPlugin">
        <property name="name" value="tom"/>
    </plugin>
</plugins>
测试
 @Test
public void firstLevelCache()  {
     //第一次查询id为1的用户
     User user1=iUserMapper.findUserById(1);

     User user = new User();
     user.setId(1);
     user.setUsername("zhangsan");
     iUserMapper.updateUser(user);

     //第二次查询
     User user2=iUserMapper.findUserById(1);
     System.out.println(user1==user2);

 }
结果是最先执行的是setProperties,然后每次进行查询之前都会执行intercept
 

8.5 源码分析

执⾏插件逻辑
Plugin 实现了 InvocationHandler 接⼝,因此它的 invoke ⽅法会拦截所有的⽅法调⽤。 invoke ⽅法会 对
所拦截的⽅法进⾏检测,以决定是否执⾏插件逻辑。该⽅法的逻辑如下:
 
// -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 { // 调⽤被拦截的⽅法
>>
关于插件的执⾏逻辑就分析结束
 
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>
sqlMapConfig.xml中加入配置
 
<plugin interceptor="com.github.pagehelper.PageHelper">
    <property name="dialect" value="mysql"/>
</plugin>
测试:
@Test
public void testPageHelper(){
    PageHelper.startPage(1,1);
    List<User> users = mapper.selectUser();
    for (User user:users) {
        System.out.println("用户信息"+user);
    }

    PageInfo<User> userPageInfo = new PageInfo<>(users);
    System.out.println("总条数"+userPageInfo.getTotal());
    System.out.println("总页数"+userPageInfo.getPages());
    System.out.println("当前页"+userPageInfo.getPageNum());
    System.out.println("每页显示条数"+userPageInfo.getPageSize());
}

    2021-04-12 07:07:27 [DEBUG]-[com.ch.mapper.IUserMapper.selectUser_PageHelper] ==>  Preparing: select * from user limit ?,?
    2021-04-12 07:07:27 [DEBUG]-[com.ch.mapper.IUserMapper.selectUser_PageHelper] ==> Parameters: 0(Integer), 1(Integer)
    2021-04-12 07:07:27 [DEBUG]-[com.ch.mapper.IUserMapper.selectUser_PageHelper] <==      Total: 1
    用户信息User{id=1, username='zhangsan', roleList=[]}
   总条数2
   总页数2
   当前页1

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.在sqlMapConfig.xml中完成配置

<plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
   <!-- 指定当前通用mapper接口使用的是哪一个-->
    <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
</plugin>

 3.实体类注解

package com.ch.pojo;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Table(name="user") //指定与数据库哪一张表进行映射
public class User implements Serializable {

    @Id //对应的是注解Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)  //设置主键生成策略
    private Integer id;
    private String username;

    //该用户具有的订单信息
    private List<Order>orderList=new ArrayList<>();
    private List<Role>roleList=new ArrayList<>();

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public List<Order> getOrderList() {
        return orderList;
    }

    public void setOrderList(List<Order> orderList) {
        this.orderList = orderList;
    }

    public List<Role> getRoleList() {
        return roleList;
    }

    public void setRoleList(List<Role> roleList) {
        this.roleList = roleList;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", roleList=" + roleList +
                '}';
    }
}

 4.定义通用mapper

package com.ch.mapper;

import com.ch.pojo.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 );
}
}

下一页:Mybatis架构原理https://blog.csdn.net/moshubai/article/details/115643446?spm=1001.2014.3001.5501

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值