一、應(yīng)用場(chǎng)景
1.分頁(yè),如com.github.pagehelper的分頁(yè)插件實(shí)現(xiàn)亮垫;
2.攔截sql做日志監(jiān)控;
3.統(tǒng)一對(duì)某些sql進(jìn)行統(tǒng)一條件拼接,類(lèi)似于分頁(yè)外永。
二、MyBatis的攔截器簡(jiǎn)介
? ? ? ?然后我們要知道攔截器攔截什么樣的對(duì)象拧咳,攔截對(duì)象的什么行為伯顶,什么時(shí)候攔截?
???????在Mybatis框架中骆膝,已經(jīng)給我們提供了攔截器接口祭衩,防止我們改動(dòng)源碼來(lái)添加行為實(shí)現(xiàn)攔截。說(shuō)到攔截器阅签,不得不
提一下掐暮,攔截器是通過(guò)動(dòng)態(tài)代理對(duì)Mybatis加入一些自己的行為。
攔截對(duì)象
確立攔截對(duì)象范圍:要攔對(duì)人政钟,既要保證攔對(duì)人路克,又要保證對(duì)正確的人執(zhí)行正確的攔截動(dòng)作
攔截地點(diǎn)
???????在Mybatis的源碼中:
Executor、StatementHandler
攔截時(shí)機(jī)
? ? ? ? 如果mybatis為我們提供了攔截的功能养交,我們應(yīng)該在Mybatis執(zhí)行過(guò)程的哪一步攔截更加合適呢精算?
? ? ? ? 攔截某個(gè)對(duì)象干某件事的時(shí)候,攔截的時(shí)機(jī)要對(duì)碎连,過(guò)早的攔截會(huì)耽誤別人做自己的工作灰羽,攔截太晚達(dá)不到目的。
? ? ? ? Mybatis實(shí)際也是一個(gè)JDBC執(zhí)行的過(guò)程鱼辙,只不過(guò)被包裝起來(lái)了而已廉嚼,我們要攔截Mybatis的執(zhí)行,最遲也要在獲取
PreparedStatement時(shí)攔截:
PreparedStatement statement = conn.prepareStatement(sql.toString());
? ? ? 在此處偷偷的將sql語(yǔ)句換掉倒戏,就可以改變mybatis的執(zhí)行怠噪,加入自己想要的執(zhí)行行為。
? ? ? 而獲取Mybatis的Statement是在StatementHandler中進(jìn)行的杜跷。
三傍念、代碼示例
第一步矫夷、引入依賴(lài)
<dependency>
????<groupId>org.mybatis.spring.boot</groupId>
????<artifactId>mybatis-spring-boot-starter</artifactId>
????<version>1.3.2</version>
</dependency>
?第二步、配置application.propertities,指定好好映射文件和實(shí)體類(lèi)的目錄
### mybatis
mybatis.mapperLocations: classpath:mapping/*.xml?
###classpath就是應(yīng)用程序resources的路徑
mybatis.type-aliases-package: com.pingan.yc.demo.model
第三步捂寿、配置代碼生成器,引入配置在pom中添加一下代碼
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!--<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>${encoding}</encoding>
</configuration>
</plugin>-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- mybatis generator 自動(dòng)生成代碼插件 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<!-- 自動(dòng)生成代碼的配置文件地址 -->
<configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>
在resource文件夾下新建generator.xml文件
? ? ? ? 內(nèi)容如下:需要注意配置數(shù)據(jù)庫(kù)連接驅(qū)動(dòng)和數(shù)據(jù)庫(kù)連接孵运,指定生成實(shí)體類(lèi)和映射文件的路徑秦陋,最后按格式配置要生成的數(shù)據(jù)表
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
? ? ? ? PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
? ? ? ? "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
? ? <!-- 數(shù)據(jù)庫(kù)驅(qū)動(dòng):選擇你的本地硬盤(pán)上面的數(shù)據(jù)庫(kù)驅(qū)動(dòng)包-->
? ? <classPathEntry? location="D:\Users\admin\.m2\repository\mysql\mysql-connector-java\5.1.46\mysql-connector-java-5.1.46.jar"/>
? ? <context id="DB2Tables"? targetRuntime="MyBatis3">
? ? ? ? <commentGenerator>
? ? ? ? ? ? <property name="suppressDate" value="true"/>
? ? ? ? ? ? <!-- 是否去除自動(dòng)生成的注釋 true:是 : false:否 -->
? ? ? ? ? ? <property name="suppressAllComments" value="true"/>
? ? ? ? </commentGenerator>
? ? ? ? <!--數(shù)據(jù)庫(kù)鏈接URL,用戶名治笨、密碼 -->
? ? ? ? <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:4433/standard_policy_db" userId="dev" password="dev">
? ? ? ? </jdbcConnection>
? ? ? ? <javaTypeResolver>
? ? ? ? ? ? <property name="forceBigDecimals" value="false"/>
? ? ? ? </javaTypeResolver>
? ? ? ? <!-- 生成模型的包名和位置-->
? ? ? ? <javaModelGenerator targetPackage="com.pingan.yc.policy.model" targetProject="src/main/java">
? ? ? ? ? ? <property name="enableSubPackages" value="true"/>
? ? ? ? ? ? <property name="trimStrings" value="true"/>
? ? ? ? </javaModelGenerator>
? ? ? ? <!-- 生成映射文件的包名和位置-->
? ? ? ? <sqlMapGenerator targetPackage="mapping" targetProject="src/main/resources">
? ? ? ? ? ? <property name="enableSubPackages" value="true"/>
? ? ? ? </sqlMapGenerator>
? ? ? ? <!-- 生成DAO的包名和位置-->
? ? ? ? <javaClientGenerator type="XMLMAPPER" targetPackage="com.pingan.yc.policy.dao" targetProject="src/main/java">
? ? ? ? ? ? <property name="enableSubPackages" value="true"/>
? ? ? ? </javaClientGenerator>
? ? ? ? <!-- 要生成的表 tableName是數(shù)據(jù)庫(kù)中的表名或視圖名 domainObjectName是實(shí)體類(lèi)名-->
? ? ? ? <table tableName="t_user_yc" domainObjectName="User" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false"? />
? ? ? ? <table tableName="t_userinfo_yc" domainObjectName="UserInfo" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false"? selectByExampleQueryId="true"/>
? ? </context>
</generatorConfiguration>
最后:在maven命令窗口或如下界面中執(zhí)行mybatis-generator:generate命令
最后生成如下文件:
第四步驳概、攔截器?
? ? ?(生成對(duì)應(yīng)文件后,mybatis環(huán)境算是集成好了旷赖,可以運(yùn)行一個(gè)測(cè)試類(lèi)試試能否從數(shù)據(jù)庫(kù)讀取數(shù)據(jù)顺又。)定義一個(gè)類(lèi)實(shí)現(xiàn)Mybatis的Interceptor接口,@Component注解必須要添加等孵,不然可能出現(xiàn)攔截器無(wú)效的情況V烧铡!俯萌!
import org.apache.ibatis.executor.statement.StatementHandler;? ?
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.Properties;
@Component
@Intercepts({
? ? ? ? @Signature(
? ? ? ? ? ? ? ? type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class
? ? ? ? })
})
public class MySqlInterceptor implements Interceptor {
? ? @Override
? ? public Object intercept(Invocation invocation) throws Throwable {
? ? ? ? // 方法一
? ? ? ? StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
? ? ? ? MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
? ? ? ? //先攔截到RoutingStatementHandler果录,里面有個(gè)StatementHandler類(lèi)型的delegate變量,其實(shí)現(xiàn)類(lèi)是BaseStatementHandler咐熙,然后就到BaseStatementHandler的成員變量mappedStatement
? ? ? ? MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
? ? ? ? //id為執(zhí)行的mapper方法的全路徑名弱恒,如com.uv.dao.UserMapper.insertUser
? ? ? ? String id = mappedStatement.getId();
? ? ? ? //sql語(yǔ)句類(lèi)型 select、delete棋恼、insert返弹、update
? ? ? ? String sqlCommandType = mappedStatement.getSqlCommandType().toString();
? ? ? ? BoundSql boundSql = statementHandler.getBoundSql();
? ? ? ? //獲取到原始sql語(yǔ)句
? ? ? ? String sql = boundSql.getSql();
? ? ? ? String mSql = sql;
? ? ? ? //TODO 修改位置
? ? ? ? //注解邏輯判斷? 添加注解了才攔截
? ? ? ? Class<?> classType = Class.forName(mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf(".")));
? ? ? ? String mName = mappedStatement.getId().substring(mappedStatement.getId().lastIndexOf(".") + 1, mappedStatement.getId().length());
? ? ? ? for (Method method : classType.getDeclaredMethods()) {
? ? ? ? ? ? if (method.isAnnotationPresent(InterceptAnnotation.class) && mName.equals(method.getName())) {
? ? ? ? ? ? ? ? InterceptAnnotation interceptorAnnotation = method.getAnnotation(InterceptAnnotation.class);
? ? ? ? ? ? ? ? if (interceptorAnnotation.flag()) {
? ? ? ? ? ? ? ? ? ? mSql = sql + " limit 2";
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? //通過(guò)反射修改sql語(yǔ)句
? ? ? ? Field field = boundSql.getClass().getDeclaredField("sql");
? ? ? ? field.setAccessible(true);
? ? ? ? field.set(boundSql, mSql);
? ? ? ? return invocation.proceed();
? ? }
? ? @Override
? ? public Object plugin(Object target) {
? ? ? ? if (target instanceof StatementHandler) {
? ? ? ? ? ? return Plugin.wrap(target, this);
? ? ? ? } else {
? ? ? ? ? ? return target;
? ? ? ? }
? ? }
? ? ? ? @Override
? ? public void setProperties(Properties properties) {
? ? }
}
此外,定義了一個(gè)方法層面的注解爪飘,實(shí)現(xiàn)局部指定攔截
@Target({ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface InterceptAnnotation {
? ? boolean flag() default? true;
}
最后只需要在指定的方法出添加注解就可以實(shí)現(xiàn)局部攔截
最后運(yùn)行看結(jié)果就好了义起。(按我的邏輯下來(lái)會(huì)只查到2條數(shù)據(jù))
在@Interceptor的注解中也有以下的注解方式,究竟有什么不同和差異师崎,請(qǐng)大家自己研究咯并扇,我就在此拋磚引玉了,請(qǐng)各位大牛指導(dǎo)了抡诞。
@Intercepts(value = {
? ? ? ? @Signature(type = Executor.class,
? ? ? ? ? ? ? ? method = "update",
? ? ? ? ? ? ? ? args = {MappedStatement.class, Object.class}),
? ? ? ? @Signature(type = Executor.class,
? ? ? ? ? ? ? ? method = "query",
? ? ? ? ? ? ? ? args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,
? ? ? ? ? ? ? ? ? ? ? ? CacheKey.class, BoundSql.class}),
? ? ? ? @Signature(type = Executor.class,
? ? ? ? ? ? ? ? method = "query",
? ? ? ? ? ? ? ? args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})