探究MyBatis插件原理以及自定義插件實(shí)現(xiàn)
插件簡(jiǎn)介
?般情況下,開源框架都會(huì)提供插件或其他形式的拓展點(diǎn),供開發(fā)者??拓展。這樣的好處是顯?易?的,?是增加了框架的靈活性。?是開發(fā)者可以結(jié)合實(shí)際需求,對(duì)框架進(jìn)?拓展,使其能夠更好的?作。以MyBatis為例,我們可基于MyBati s插件機(jī)制實(shí)現(xiàn)分?、分表,監(jiān)控等功能。由于插件和業(yè)務(wù)?關(guān),業(yè)務(wù)也?法感知插件的存在。因此可以?感植?插件,在?形中增強(qiáng)功能。
Mybatis插件介紹
Mybati s作為?個(gè)應(yīng)??泛的優(yōu)秀的ORM開源框架,這個(gè)框架具有強(qiáng)?的靈活性,在四?組件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)處提供了簡(jiǎn)單易?的插 件擴(kuò)展機(jī)制。Mybatis對(duì)持久層的操作就是借助于四?核?對(duì)象。MyBatis?持?插件對(duì)四?核?對(duì)象進(jìn)?攔截,對(duì)mybatis來(lái)說(shuō)插件就是攔截器,?來(lái)增強(qiáng)核?對(duì)象的功能,增強(qiáng)功能本質(zhì)上是借助于底層的 動(dòng)態(tài)代理實(shí)現(xiàn)的,換句話說(shuō),MyBatis中的四?對(duì)象都是代理對(duì)象。
MyBatis所允許攔截的?法如下:
- 執(zhí)?器Executor (update、query、commit、rollback等?法);
- SQL語(yǔ)法構(gòu)建器StatementHandler(prepare、parameterize、batch、updates query等? 法);
- 參數(shù)處理器ParameterHandler (getParameterObject、setParameters?法);
- 結(jié)果集處理器ResultSetHandler (handleResultSets、handleOutputParameters等?法);
Mybatis插件原理
在四?對(duì)象創(chuàng)建的時(shí)候
- 每個(gè)創(chuàng)建出來(lái)的對(duì)象不是直接返回的,?是
interceptorChain.pluginAll(parameterHandler); - 獲取到所有的Interceptor (攔截器)(插件需要實(shí)現(xiàn)的接?);調(diào)?
interceptor.plugin(target);返回 target 包裝后的對(duì)象 - 插件機(jī)制,我們可以使?插件為?標(biāo)對(duì)象創(chuàng)建?個(gè)代理對(duì)象;AOP (?向切?)我們的插件可以為四?對(duì)象創(chuàng)建出代理對(duì)象,代理對(duì)象就可以攔截到四?對(duì)象的每?個(gè)執(zhí)?;
攔截
插件具體是如何攔截并附加額外的功能的呢?以ParameterHandler來(lái)說(shuō)。
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初始化的時(shí)候創(chuàng)建的。調(diào)?攔截器鏈中的攔截器依次的對(duì)?標(biāo)進(jìn)?攔截或增強(qiáng)。interceptor.plugin(target)中的target就可以理解為mybatis中的四?對(duì)象。返回的target是被重重代理后的對(duì)象
如果我們想要攔截Executor的query?法,那么可以這樣定義插件:
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}
)
})
public class ExeunplePlugin implements Interceptor {
//省略邏輯
}除此之外,我們還需將插件配置到sqlMapConfig.xml中。
<plugins> <plugin interceptor="com.zjq.plugin.ExamplePlugin"></plugin> </plugins>
這樣MyBatis在啟動(dòng)時(shí)可以加載插件,并保存插件實(shí)例到相關(guān)對(duì)象(InterceptorChain,攔截器鏈) 中。待準(zhǔn)備?作做完后,MyBatis處于就緒狀態(tài)。我們?cè)趫?zhí)?SQL時(shí),需要先通過(guò)DefaultSqlSessionFactory創(chuàng)建 SqlSession。Executor 實(shí)例會(huì)在創(chuàng)建 SqlSession 的過(guò)程中被創(chuàng)建, Executor實(shí)例創(chuàng)建完畢后,MyBatis會(huì)通過(guò)JDK動(dòng)態(tài)代理為實(shí)例?成代理類。這樣,插件邏輯即可在 Executor相關(guān)?法被調(diào)?前執(zhí)?。
以上就是MyBatis插件機(jī)制的基本原理。
?定義插件
插件接口
Mybatis 插件接?-Interceptor
- Intercept?法,插件的核??法
- plugin?法,?成target的代理對(duì)象
- setProperties?法,傳遞插件所需參數(shù)
?定義插件
設(shè)計(jì)實(shí)現(xiàn)?個(gè)?定義插件
@Intercepts({//注意看這個(gè)?花括號(hào),也就這說(shuō)這?可以定義多個(gè)@Signature對(duì)多個(gè)地?攔截,都?這個(gè)攔截器
@Signature(type = StatementHandler.class , //這是指攔截哪個(gè)接?
method = "prepare", //這個(gè)接?內(nèi)的哪個(gè)?法名,不要拼錯(cuò)了
args = { Connection.class, Integer .class}), //這是攔截的?法的?參,按順序?qū)懙竭@,不要多也不要少,如果?法重載,可是要通過(guò)?法名和?參來(lái)確定唯?的
})
public class MyPlugin implements Interceptor {
// //這?是每次執(zhí)?操作的時(shí)候,都會(huì)進(jìn)?這個(gè)攔截器的?法內(nèi)
@Override
public Object intercept(Invocation invocation) throws Throwable {
//增強(qiáng)邏輯
System.out.println("對(duì)?法進(jìn)?了增強(qiáng)....");
return invocation.proceed(); //執(zhí)?原?法
}
/**
* //主要是為了把這個(gè)攔截器?成?個(gè)代理放到攔截器鏈中
* ^Description包裝?標(biāo)對(duì)象 為?標(biāo)對(duì)象創(chuàng)建代理對(duì)象
* @Param target為要攔截的對(duì)象
* @Return代理對(duì)象
*/
@Override
public Object plugin(Object target) {
System.out.println("將要包裝的?標(biāo)對(duì)象:"+target);
return Plugin.wrap(target,this);
}
/**獲取配置?件的屬性**/
//插件初始化的時(shí)候調(diào)?,也只調(diào)??次,插件配置的屬性從這?設(shè)置進(jìn)來(lái)
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置的初始化參數(shù):"+properties );
}
}sqlMapConfig.xml

mapper接?

mapper.xml
<mapper namespace="com.zjq.mapper.UserMapper">
<!--sql語(yǔ)句抽取-->
<sql id="selectUser">
select * from user
</sql>
<select id="findByCondition" parameterType="user" resultType="user">
<include refid="selectUser"></include>
<where>
<if test="id!=0">
and id=#{id}
</if>
<if test="username!=null and username!=''">
and username=#{username}
</if>
<if test="password!=null and password!=''">
and password=#{password}
</if>
</where>
</select>
</mapper>測(cè)試類
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);
User condition = new User();
//condition.setId(1);
condition.setUsername("zjq");
List<User> byPaging = userMapper.findByCondition(condition);
for (User user : byPaging) {
System.out.println(user);
}
}
}源碼分析
執(zhí)?插件邏輯Plugin實(shí)現(xiàn)了 InvocationHandler接?,因此它的invoke?法會(huì)攔截所有的?法調(diào)?。invoke?法會(huì) 對(duì)所攔截的?法進(jìn)?檢測(cè),以決定是否執(zhí)?插件邏輯。該?法的邏輯如下:
// -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());
//檢測(cè)?法列表是否包含被攔截的?法
if (methods != null && methods.contains(method)) {
//執(zhí)?插件邏輯
return interceptor.intercept(new Invocation(target, method,
args));
//執(zhí)?被攔截的?法
return method.invoke(target, args);
} catch(Exception e){
}
}invoke?法的代碼?較少,邏輯不難理解。?先,invoke?法會(huì)檢測(cè)被攔截?法是否配置在插件的@Signature注解中,若是,則執(zhí)?插件邏輯,否則執(zhí)?被攔截?法。插件邏輯封裝在intercept中,該?法的參數(shù)類型為Invocationo Invocation主要?于存儲(chǔ)?標(biāo)類,?法以及?法參數(shù)列表。下?簡(jiǎn)單看?下該類的定義
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 { //調(diào)?被攔截的?法關(guān)于插件的執(zhí)?邏輯就分析結(jié)束。
pageHelper分頁(yè)插件
MyBatis可以使?第三?的插件來(lái)對(duì)功能進(jìn)?擴(kuò)展,分?助?PageHelper是將分?的復(fù)雜操作進(jìn)?封裝,使?簡(jiǎn)單的?式即可獲得分?的相關(guān)數(shù)據(jù)
開發(fā)步驟:
- 導(dǎo)?通?PageHelper坐標(biāo)
<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>
- 測(cè)試分?代碼實(shí)現(xiàn)
@Test
public void testPageHelper() {
//設(shè)置分?參數(shù)
PageHelper.startPage(1, 2);
User condition = new User();
//condition.setId(1);
condition.setUsername("zjq");
List<User> select = userMapper.findByCondition(condition);
for (User user : select) {
System.out.println(user);
}
}獲得分?相關(guān)的其他參數(shù)
//其他分?的數(shù)據(jù)
PageInfo<User> pageInfo = new PageInfo<User>(select);
System.out.println("總條數(shù):"+pageInfo.getTotal());
System.out.println("總?數(shù):"+pageInfo. getPages ());
System.out.println("當(dāng)前?:"+pageInfo. getPageNum());
System.out.println("每?顯示?度:"+pageInfo.getPageSize());
System.out.println("是否第??:"+pageInfo.isIsFirstPage());
System.out.println("是否最后??:"+pageInfo.isIsLastPage());通? mapper
什么是通?Mapper
通?Mapper就是為了解決單表增刪改查,基于Mybatis的插件機(jī)制。開發(fā)?員不需要編寫SQL,不需要在DAO中增加?法,只要寫好實(shí)體類,就能?持相應(yīng)的增刪改查?法
如何使?
- ?先在maven項(xiàng)?,在pom.xml中引?mapper的依賴
<dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper</artifactId> <version>3.1.2</version> </dependency>
- Mybatis配置?件中完成配置
<plugins>
<!--分?插件:如果有分?插件,要排在通?mapper之前-->
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
</plugin>
<plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
<!-- 通?Mapper接?,多個(gè)通?接??逗號(hào)隔開 -->
<property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
</plugin>
</plugins>- 實(shí)體類設(shè)置主鍵
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
}- 定義通?mapper
import com.zjq.domain.User;
import tk.mybatis.mapper.common.Mapper;
public interface UserMapper extends Mapper<User> {
}- 測(cè)試
@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基礎(chǔ)接?
//select 接?
//根據(jù)實(shí)體中的屬性進(jìn)?查詢,只能有一個(gè)返回值
User user1 = userMapper.selectOne(user);
//查詢?nèi)拷Y(jié)果
List<User> users = userMapper.select(null);
//根據(jù)主鍵字段進(jìn)?查詢,?法參數(shù)必須包含完整的主鍵屬性,查詢條件使?等號(hào)
userMapper.selectByPrimaryKey(1);
//根據(jù)實(shí)體中的屬性查詢總數(shù),查詢條件使?等號(hào)
userMapper.selectCount(user);
// insert 接?
//保存?個(gè)實(shí)體,null值也會(huì)保存,不會(huì)使?數(shù)據(jù)庫(kù)默認(rèn)值
int insert = userMapper.insert(user);
//保存實(shí)體,null的屬性不會(huì)保存,會(huì)使?數(shù)據(jù)庫(kù)默認(rèn)值
int i = userMapper.insertSelective(user);
// update 接?
//根據(jù)主鍵更新實(shí)體全部字段,null值會(huì)被更新
int i1 = userMapper.updateByPrimaryKey(user);
// delete 接?
//根據(jù)實(shí)體屬性作為條件進(jìn)?刪除,查詢條件使?等號(hào)
int delete = userMapper.delete(user);
//根據(jù)主鍵字段進(jìn)?刪除,?法參數(shù)必須包含完整的主鍵屬性
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插件原理以及自定義插件實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于MyBatis插件原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java web實(shí)現(xiàn)簡(jiǎn)單留言板功能
這篇文章主要為大家詳細(xì)介紹了java web實(shí)現(xiàn)簡(jiǎn)單留言板功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11
SpringBoot利用EasyExcel實(shí)現(xiàn)導(dǎo)出數(shù)據(jù)
EasyExcel是一個(gè)基于Java的、快速、簡(jiǎn)潔、解決大文件內(nèi)存溢出的Excel處理工具,它能讓你在不用考慮性能、內(nèi)存的等因素的情況下,快速完成Excel的讀、寫等功能看,本文就將介紹如何利用EasyExcel實(shí)現(xiàn)導(dǎo)出數(shù)據(jù),需要的朋友可以參考下2023-07-07
VSCode中開發(fā)JavaWeb項(xiàng)目的詳細(xì)過(guò)程(Maven+Tomcat+熱部署)
這篇文章主要介紹了VSCode中開發(fā)JavaWeb項(xiàng)目(Maven+Tomcat+熱部署),本文分步驟通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
gradle項(xiàng)目中資源文件的相對(duì)路徑打包技巧必看
這篇文章主要介紹了gradle項(xiàng)目中資源文件的相對(duì)路徑打包技巧必看篇,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
Java 數(shù)據(jù)類型及類型轉(zhuǎn)換的互相轉(zhuǎn)換實(shí)例代碼
這篇文章主要介紹了Java 數(shù)據(jù)類型及類型轉(zhuǎn)換的互相轉(zhuǎn)換實(shí)例代碼,需要的朋友可以參考下2020-10-10
java并發(fā)編程之深入理解Synchronized的使用
文詳細(xì)講述了線程、進(jìn)程的關(guān)系及在操作系統(tǒng)中的表現(xiàn),這是多線程學(xué)習(xí)必須了解的基礎(chǔ)。本文將接著講一下Java線程同步中的一個(gè)重要的概念synchronized,希望能夠給你有所幫助2021-06-06
SpringBoot實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源的示例代碼
這篇文章主要為大家詳細(xì)介紹了SpringBoot實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-08-08

