MyBatis實現(xiàn)動態(tài)SQL更新的代碼示例
本文示例代碼全部在 Spring Boot3.0、Mybatis Plus3.5.3.1 版本下運行。
簡介
MyBatis 是一個流行的 Java 持久層框架,它提供了靈活的 SQL 映射和執(zhí)行功能。有時候我們可能需要在運行時動態(tài)地修改 SQL 語句,例如添加一些條件、排序、分頁等。MyBatis 提供了一個強大的機制來實現(xiàn)這個需求,那就是攔截器(Interceptor)。
攔截器介紹
攔截器是一種基于 AOP(面向切面編程)的技術(shù),它可以在目標對象的方法執(zhí)行前后插入自定義的邏輯。MyBatis 定義了四種類型的攔截器,分別是:
- Executor:攔截執(zhí)行器的方法,例如 update、query、commit、rollback 等??梢杂脕韺崿F(xiàn)緩存、事務、分頁等功能。
- ParameterHandler:攔截參數(shù)處理器的方法,例如 setParameters 等??梢杂脕磙D(zhuǎn)換或加密參數(shù)等功能。
- ResultSetHandler:攔截結(jié)果集處理器的方法,例如 handleResultSets、handleOutputParameters 等??梢杂脕磙D(zhuǎn)換或過濾結(jié)果集等功能。
- StatementHandler:攔截語句處理器的方法,例如 prepare、parameterize、batch、update、query 等。可以用來修改 SQL 語句、添加參數(shù)、記錄日志等功能。
實現(xiàn)攔截器
- 定義一個實現(xiàn) org.apache.ibatis.plugin.Interceptor 接口的攔截器類,并重寫其中的 intercept、plugin 和 setProperties 方法。
- 添加 @Intercepts 注解,寫上需要攔截的對象和方法,以及方法參數(shù),例如
@Intercepts({@Signature(type = StatementHandler.class, method = “prepare”, args = {Connection.class, Integer.class})})
,表示在 SQL 執(zhí)行之前進行攔截處理。
注冊攔截器
Spring Boot 項目中集成了 Mybatis Plus 后要讓攔截器生效很簡單,Mybatis Plus 的自動配置類會讀取項目中所有注冊到 Spring 容器的攔截器并進行自動注冊。如下圖,
所以我們只需要定義一個 DynamicSqlInterceptor 攔截器并加上 @Component 注解就行,代碼如下,
@Component @Slf4j @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}) }) public class DynamicSqlInterceptor implements Interceptor { ... }
代碼示例
yml 配置
指定 xml 文件中需要替換的占位符標識:@dynamicSql
以及待替換日期條件。
# 動態(tài)sql配置 dynamicSql: placeholder: "@dynamicSql" date: "2023-07-10 20:10:30"
Dao 層代碼
在需要進行 SQL 占位符替換的方法上加 @DynamicSql 注解。
public interface DynamicSqlMapper { @DynamicSql Long count(); }
mapper 文件
將日期條件改成占位符 where create_time > @dynamicSql
。
<mapper namespace="ltd.newbee.mall.core.dao.DynamicSqlMapper"> <select id="count" resultType="java.lang.Long"> select count(1) from member where create_time > @dynamicSql </select> </mapper>
攔截器核心代碼
@Component @Slf4j @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}) }) public class DynamicSqlInterceptor implements Interceptor { @Value("${dynamicSql.placeholder}") private String placeholder; @Value("${dynamicSql.date}") private String dynamicDate; @Override public Object intercept(Invocation invocation) throws Throwable { // 1. 獲取 StatementHandler 對象也就是執(zhí)行語句 StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); // 2. MetaObject 是 MyBatis 提供的一個反射幫助類,可以優(yōu)雅訪問對象的屬性,這里是對 statementHandler 對象進行反射處理, MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory()); // 3. 通過 metaObject 反射獲取 statementHandler 對象的成員變量 mappedStatement MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); // mappedStatement 對象的 id 方法返回執(zhí)行的 mapper 方法的全路徑名,如ltd.newbee.mall.core.dao.UserMapper.insertUser String id = mappedStatement.getId(); // 4. 通過 id 獲取到 Dao 層類的全限定名稱,然后反射獲取 Class 對象 Class<?> classType = Class.forName(id.substring(0, id.lastIndexOf("."))); // 5. 獲取包含原始 sql 語句的 BoundSql 對象 BoundSql boundSql = statementHandler.getBoundSql(); String sql = boundSql.getSql(); log.info("替換前---sql:{}", sql); // 攔截方法 String mSql = null; // 6. 遍歷 Dao 層類的方法 for (Method method : classType.getMethods()) { // 7. 判斷方法上是否有 DynamicSql 注解,有的話,就認為需要進行 sql 替換 if (method.isAnnotationPresent(DynamicSql.class)) { mSql = sql.replaceAll(placeholder, String.format("'%s'", dynamicDate)); break; } } if (StringUtils.isNotBlank(mSql)) { log.info("替換后---mSql:{}", mSql); // 8. 對 BoundSql 對象通過反射修改 SQL 語句。 Field field = boundSql.getClass().getDeclaredField("sql"); field.setAccessible(true); field.set(boundSql, mSql); } // 9. 執(zhí)行修改后的 SQL 語句。 return invocation.proceed(); } @Override public Object plugin(Object target) { // 使用 Plugin.wrap 方法生成代理對象 return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 獲取配置文件中的屬性值 } }
現(xiàn)在我們對攔截器核心代碼邏輯進行講解:
- 通過 invocation 參數(shù)獲取 statementHandler 對象,也就是包含拼接后 SQL 語句的對象。
- 獲取 metaObject 對象, MetaObject 是 MyBatis 提供的一個反射幫助類,可以優(yōu)雅訪問對象的屬性,這里是訪問 statementHandler 對象進行反射處理。
- 通過 metaObject 反射獲取 statementHandler 對象的成員變量 mappedStatement。
- 通過 mappedStatement 對象的 id 方法獲取到 Dao 層類的全限定名稱,然后反射獲取 Dao 層類的 Class 對象。
- 獲取包含原始 SQL 語句的 BoundSql 對象。
- 遍歷 Dao 層類的方法。
- 判斷方法上是否有 DynamicSql 注解,有的話就進行時間條件替換。
- 對 BoundSql 對象通過反射修改 SQL 語句。
- 執(zhí)行修改后的 SQL 語句。
代碼測試
// 測試類 @SpringBootTest @RunWith(SpringRunner.class) public class DynamicTest { @Autowired private DynamicSqlMapper dynamicSqlMapper; @Test public void test() { Long count = dynamicSqlMapper.count(); Assert.notNull(count, "count不能為null"); } }
執(zhí)行結(jié)果:
2023-07-11 22:13:33.375 [main] INFO l.n.m.config.DynamicSqlInterceptor - [intercept,52] - 替換前---sql:select count(1) from member where create_time > @dynamicSql 2023-07-11 22:13:33.376 [main] INFO l.n.m.config.DynamicSqlInterceptor - [intercept,62] - 替換后---mSql:select count(1) from member where create_time > '2023-07-10 20:10:30'
攔截器應用場景
- SQL 語句執(zhí)行監(jiān)控:可以攔截執(zhí)行的 SQL 方法,打印執(zhí)行的 SQL 語句、參數(shù)等信息,并且還能夠記錄執(zhí)行的總耗時,可供后期的 SQL 分析時使用。
- SQL 分頁查詢:MyBatis 中使用的 RowBounds 使用的內(nèi)存分頁,在分頁前會查詢所有符合條件的數(shù)據(jù),在數(shù)據(jù)量大的情況下性能較差。通過攔截器,可以在查詢前修改 SQL 語句,提前加上需要的分頁參數(shù)。
- 公共字段的賦值:在數(shù)據(jù)庫中通常會有 createTime , updateTime 等公共字段,這類字段可以通過攔截統(tǒng)一對參數(shù)進行的賦值,從而省去手工通過 set 方法賦值的繁瑣過程。
- 數(shù)據(jù)權(quán)限過濾:在很多系統(tǒng)中,不同的用戶可能擁有不同的數(shù)據(jù)訪問權(quán)限,例如在多租戶的系統(tǒng)中,要做到租戶間的數(shù)據(jù)隔離,每個租戶只能訪問到自己的數(shù)據(jù),通過攔截器改寫 SQL 語句及參數(shù),能夠?qū)崿F(xiàn)對數(shù)據(jù)的自動過濾。
- SQL 語句替換:對 SQL 中條件或者特殊字符進行邏輯替換。(也是本文的應用場景)
總結(jié)
到此本文講解的 MyBatis 實現(xiàn)動態(tài) SQL 內(nèi)容就講解完畢了,希望大家喜歡。
以上就是MyBatis實現(xiàn)動態(tài)SQL更新的代碼示例的詳細內(nèi)容,更多關(guān)于MyBatis動態(tài)SQL更新的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java ReentrantLock條件鎖實現(xiàn)原理示例詳解
這篇文章主要為大家介紹了java ReentrantLock條件鎖實現(xiàn)原理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01基于SpringMVC攔截器實現(xiàn)接口耗時監(jiān)控功能
本文呢主要介紹了基于SpringMVC攔截器實現(xiàn)的接口耗時監(jiān)控功能,統(tǒng)計接口的耗時情況屬于一個可以復用的功能點,因此這里直接使用 SpringMVC的HandlerInterceptor攔截器來實現(xiàn),需要的朋友可以參考下2024-02-02Java利用ElasticSearch實現(xiàn)自動補全功能
這篇文章主要為大家詳細介紹了Java如何利用ElasticSearch實現(xiàn)跟谷歌和百度類似的下拉補全提示功能,文中的示例代碼講解詳細,需要的可以參考一下2023-08-08springboot登陸頁面圖片驗證碼簡單的web項目實現(xiàn)
這篇文章主要介紹了springboot登陸頁面圖片驗證碼簡單的web項目實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-04-04SSH框架網(wǎng)上商城項目第7戰(zhàn)之整合Struts2和Json
SSH框架網(wǎng)上商城項目第7戰(zhàn)之整合Struts2和Json,打通EasyUI和Struts2之間的交互,感興趣的小伙伴們可以參考一下2016-05-05