文章目录
插件简介
⼀般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者⾃⾏拓展。这样的好处是显⽽易⻅的,⼀是增加了框架的灵活性。⼆是开发者可以结合实际需求,对框架进⾏拓展,使其能够更好的⼯作。以MyBatis为例,我们可基于MyBati s插件机制实现分⻚、分表,监控等功能。由于插件和业务⽆关,业务也⽆法感知插件的存在。因此可以⽆感植⼊插件,在⽆形中增强功能。
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等⽅法);
Mybatis插件原理
在四⼤对象创建的时候
- 每个创建出来的对象不是直接返回的,⽽是
interceptorChain.pluginAll(parameterHandler)
; - 获取到所有的Interceptor (拦截器)(插件需要实现的接⼝);调⽤
interceptor.plugin(target)
;返回 target 包装后的对象 - 插件机制,我们可以使⽤插件为⽬标对象创建⼀个代理对象;AOP (⾯向切⾯)我们的插件可以为四⼤对象创建出代理对象,代理对象就可以拦截到四⼤对象的每⼀个执⾏;
拦截
插件具体是如何拦截并附加额外的功能的呢?以ParameterHandler来说。
publicParameterHandlernewParameterHandler(MappedStatement mappedStatement,Object object,BoundSql sql,InterceptorChain interceptorChain){ParameterHandler parameterHandler =
mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql);
parameterHandler =(ParameterHandler)interceptorChain.pluginAll(parameterHandler);return parameterHandler;}publicObjectpluginAll(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})})publicclassExeunplePluginimplementsInterceptor{//省略逻辑}
除此之外,我们还需将插件配置到sqlMapConfig.xml中。
<plugins><plugininterceptor="com.zjq.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}),//这是拦截的⽅法的⼊参,按顺序写到这,不要多也不要少,如果⽅法重载,可是要通过⽅法名和⼊参来确定唯⼀的})publicclassMyPluginimplementsInterceptor{// //这⾥是每次执⾏操作的时候,都会进⾏这个拦截器的⽅法内@OverridepublicObjectintercept(Invocation invocation)throwsThrowable{//增强逻辑System.out.println("对⽅法进⾏了增强....");return invocation.proceed();//执⾏原⽅法}/**
* //主要是为了把这个拦截器⽣成⼀个代理放到拦截器链中
* ^Description包装⽬标对象 为⽬标对象创建代理对象
* @Param target为要拦截的对象
* @Return代理对象
*/@OverridepublicObjectplugin(Object target){System.out.println("将要包装的⽬标对象:"+target);returnPlugin.wrap(target,this);}/**获取配置⽂件的属性**///插件初始化的时候调⽤,也只调⽤⼀次,插件配置的属性从这⾥设置进来@OverridepublicvoidsetProperties(Properties properties){System.out.println("插件配置的初始化参数:"+properties );}}
sqlMapConfig.xml
mapper接⼝
mapper.xml
<mappernamespace="com.zjq.mapper.UserMapper"><!--sql语句抽取--><sqlid="selectUser">
select * from user
</sql><selectid="findByCondition"parameterType="user"resultType="user"><includerefid="selectUser"></include><where><iftest="id!=0">
and id=#{id}
</if><iftest="username!=null and username!=''">
and username=#{username}
</if><iftest="password!=null and password!=''">
and password=#{password}
</if></where></select></mapper>
测试类
publicclassPluginTest{@Testpublicvoidtest()throwsIOException{InputStream resourceAsStream =Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory =newSqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);User condition =newUser();//condition.setId(1);
condition.setUsername("zjq");List<User> byPaging = userMapper.findByCondition(condition);for(User user : byPaging){System.out.println(user);}}}
源码分析
执⾏插件逻辑
Plugin实现了
InvocationHandler
接⼝,因此它的invoke⽅法会拦截所有的⽅法调⽤。invoke⽅法会 对
所拦截的⽅法进⾏检测,以决定是否执⾏插件逻辑。该⽅法的逻辑如下:
// -PluginpublicObjectinvoke(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(newInvocation(target, method,
args));//执⾏被拦截的⽅法return method.invoke(target, args);}catch(Exception e){}}
invoke⽅法的代码⽐较少,逻辑不难理解。⾸先,invoke⽅法会检测被拦截⽅法是否配置在插件的
@Signature
注解中,若是,则执⾏插件逻辑,否则执⾏被拦截⽅法。插件逻辑封装在intercept中,该⽅法的参数类型为Invocationo Invocation主要⽤于存储⽬标类,⽅法以及⽅法参数列表。下⾯简单看⼀下该类的定义
publicclassInvocation{privatefinalObject target;privatefinalMethod method;privatefinalObject[] args;publicInvocation(Object targetf Method method,Object[] args){this.target = target;this.method = method;//省略部分代码publicObjectproceed()throwsInvocationTargetException,IllegalAccessException{//调⽤被拦截的⽅法
关于插件的执⾏逻辑就分析结束。
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之前*-->*
<plugininterceptor="com.github.pagehelper.PageHelper"><!—指定⽅⾔—><propertyname="dialect"value="mysql"/></plugin>
- 测试分⻚代码实现
@TestpublicvoidtestPageHelper(){//设置分⻚参数PageHelper.startPage(1,2);User condition =newUser();//condition.setId(1);
condition.setUsername("zjq");List<User> select = userMapper.findByCondition(condition);for(User user : select){System.out.println(user);}}
获得分⻚相关的其他参数
//其他分⻚的数据PageInfo<User> pageInfo =newPageInfo<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中增加⽅法,只要写好实体类,就能⽀持相应的增删改查⽅法
如何使⽤
- ⾸先在maven项⽬,在pom.xml中引⼊mapper的依赖
<dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId><version>3.1.2</version></dependency>
- Mybatis配置⽂件中完成配置
<plugins><!--分⻚插件:如果有分⻚插件,要排在通⽤mapper之前--><plugininterceptor="com.github.pagehelper.PageHelper"><propertyname="dialect"value="mysql"/></plugin><plugininterceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor"><!-- 通⽤Mapper接⼝,多个通⽤接⼝⽤逗号隔开 --><propertyname="mappers"value="tk.mybatis.mapper.common.Mapper"/></plugin></plugins>
- 实体类设置主键
@Table(name ="t_user")publicclassUser{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateInteger id;privateString username;}
- 定义通⽤mapper
importcom.zjq.domain.User;importtk.mybatis.mapper.common.Mapper;publicinterfaceUserMapperextendsMapper<User>{}
- 测试
@Testpublicvoidtest1()throwsIOException{Inputstream resourceAsStream =Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory build =newSqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = build.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);User user =newUser();
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 =newExample(User.class);
example.createCriteria().andEqualTo("id",1);
example.createCriteria().andLike("val","1");//⾃定义查询List<User> users1 = userMapper.selectByExample(example);}
本文内容到此结束了,
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位指出。
主页:共饮一杯无的博客汇总👨💻保持热爱,奔赴下一场山海。🏃🏃🏃
版权归原作者 共饮一杯无 所有, 如有侵权,请联系我们删除。