第⼋部分:Mybatis插件
8.1 插件简介
⼀般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者⾃⾏拓展。这样的好处是显⽽易⻅
的,⼀是增加了框架的灵活性。⼆是开发者可以结合实际需求,对框架进⾏拓展,使其能够更好的⼯
作。以
MyBatis
为例,我们可基于
MyBati s
插件机制实现分⻚、分表,监控等功能。由于插件和业务 ⽆
关,业务也⽆法感知插件的存在。因此可以⽆感植⼊插件,在⽆形中增强功能
8.2 Mybatis插件介绍
Mybati s
作为⼀个应⽤⼴泛的优秀的
ORM
开源框架,这个框架具有强⼤的灵活性,在四⼤组件
(Executor
、
StatementHandler
、
ParameterHandler
、
ResultSetHandler)
处提供了简单易⽤的插 件扩
展机制。
Mybatis
对持久层的操作就是借助于四⼤核⼼对象。
MyBatis
⽀持⽤插件对四⼤核⼼对象进 ⾏
拦截,对
mybatis
来说插件就是拦截器,⽤来增强核⼼对象的功能,增强功能本质上是借助于底层的 动
态代理实现的,换句话说,
MyBatis
中的四⼤对象都是代理对象
![](https://i-blog.csdnimg.cn/blog_migrate/721ab1003a17bb220dfc11152f7f7dd6.png)
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
⽅法会 对
所拦截的⽅法进⾏检测,以决定是否执⾏插件逻辑。该⽅法的逻辑如下:
// -Pluginpublic Object invoke ( Object proxy , Method method , Object [] args ) throwsThrowable {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 {@Testpublic void test1 () throws IOException {Inputstream resourceAsStream =Resources . getResourceAsStream ( "sqlMapConfig.xml" );SqlSessionFactory build = newSqlSessionFactoryBuilder (). 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