Mybatis全面分頁插件
根據(jù)下面分頁的思想,很容易實(shí)現(xiàn)Mybitas的多租戶設(shè)計(jì)。
使用Mybatis提供的攔截器。對分頁的SQL語句通過封裝處理,處理成不同的分頁sql。
本例已經(jīng)實(shí)現(xiàn)了對Mysql和Oracle的分頁功能。注意下面的引用包,不要引用錯(cuò)了。
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import com.yidao.utils.Page;
import com.yidao.utils.ReflectHelper;
/**
*
* 分頁攔截器,用于攔截需要進(jìn)行分頁查詢的操作,然后對其進(jìn)行分頁處理。
* 利用攔截器實(shí)現(xiàn)Mybatis分頁的原理:
* 要利用JDBC對數(shù)據(jù)庫進(jìn)行操作就必須要有一個(gè)對應(yīng)的Statement對象,Mybatis在執(zhí)行Sql語句前就會產(chǎn)生一個(gè)包含Sql語句的Statement對象,而且對應(yīng)的Sql語句
* 是在Statement之前產(chǎn)生的,所以我們就可以在它生成Statement之前對用來生成Statement的Sql語句下手。在Mybatis中Statement語句是通過RoutingStatementHandler對象的
* prepare方法生成的。所以利用攔截器實(shí)現(xiàn)Mybatis分頁的一個(gè)思路就是攔截StatementHandler接口的prepare方法,然后在攔截器方法中把Sql語句改成對應(yīng)的分頁查詢Sql語句,之后再調(diào)用
* StatementHandler對象的prepare方法,即調(diào)用invocation.proceed()。
* 對于分頁而言,在攔截器里面我們還需要做的一個(gè)操作就是統(tǒng)計(jì)滿足當(dāng)前條件的記錄一共有多少,這是通過獲取到了原始的Sql語句后,把它改為對應(yīng)的統(tǒng)計(jì)語句再利用Mybatis封裝好的參數(shù)和設(shè)
* 置參數(shù)的功能把Sql語句中的參數(shù)進(jìn)行替換,之后再執(zhí)行查詢記錄數(shù)的Sql語句進(jìn)行總記錄數(shù)的統(tǒng)計(jì)。
*
*/
@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class})})
public class PageInterceptor implements Interceptor {
private String dialect = ""; //數(shù)據(jù)庫方言
private String pageSqlId = ""; //mapper.xml中需要攔截的ID(正則匹配)
public Object intercept(Invocation invocation) throws Throwable {
//對于StatementHandler其實(shí)只有兩個(gè)實(shí)現(xiàn)類,一個(gè)是RoutingStatementHandler,另一個(gè)是抽象類BaseStatementHandler,
//BaseStatementHandler有三個(gè)子類,分別是SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler,
//SimpleStatementHandler是用于處理Statement的,PreparedStatementHandler是處理PreparedStatement的,而CallableStatementHandler是
//處理CallableStatement的。Mybatis在進(jìn)行Sql語句處理的時(shí)候都是建立的RoutingStatementHandler,而在RoutingStatementHandler里面擁有一個(gè)
//StatementHandler類型的delegate屬性,RoutingStatementHandler會依據(jù)Statement的不同建立對應(yīng)的BaseStatementHandler,即SimpleStatementHandler、
//PreparedStatementHandler或CallableStatementHandler,在RoutingStatementHandler里面所有StatementHandler接口方法的實(shí)現(xiàn)都是調(diào)用的delegate對應(yīng)的方法。
//我們在PageInterceptor類上已經(jīng)用@Signature標(biāo)記了該Interceptor只攔截StatementHandler接口的prepare方法,又因?yàn)镸ybatis只有在建立RoutingStatementHandler的時(shí)候
//是通過Interceptor的plugin方法進(jìn)行包裹的,所以我們這里攔截到的目標(biāo)對象肯定是RoutingStatementHandler對象。
if(invocation.getTarget() instanceof RoutingStatementHandler){
RoutingStatementHandler statementHandler = (RoutingStatementHandler)invocation.getTarget();
StatementHandler delegate = (StatementHandler) ReflectHelper.getFieldValue(statementHandler, "delegate");
BoundSql boundSql = delegate.getBoundSql();
Object obj = boundSql.getParameterObject();
if (obj instanceof Page<?>) {
Page<?> page = (Page<?>) obj;
//通過反射獲取delegate父類BaseStatementHandler的mappedStatement屬性
MappedStatement mappedStatement = (MappedStatement)ReflectHelper.getFieldValue(delegate, "mappedStatement");
//攔截到的prepare方法參數(shù)是一個(gè)Connection對象
Connection connection = (Connection)invocation.getArgs()[0];
//獲取當(dāng)前要執(zhí)行的Sql語句,也就是我們直接在Mapper映射語句中寫的Sql語句
String sql = boundSql.getSql();
//給當(dāng)前的page參數(shù)對象設(shè)置總記錄數(shù)
this.setTotalRecord(page,
mappedStatement, connection);
//獲取分頁Sql語句
String pageSql = this.getPageSql(page, sql);
//利用反射設(shè)置當(dāng)前BoundSql對應(yīng)的sql屬性為我們建立好的分頁Sql語句
ReflectHelper.setFieldValue(boundSql, "sql", pageSql);
}
}
return invocation.proceed();
}
/**
* 給當(dāng)前的參數(shù)對象page設(shè)置總記錄數(shù)
*
* @param page Mapper映射語句對應(yīng)的參數(shù)對象
* @param mappedStatement Mapper映射語句
* @param connection 當(dāng)前的數(shù)據(jù)庫連接
*/
private void setTotalRecord(Page<?> page,
MappedStatement mappedStatement, Connection connection) {
//獲取對應(yīng)的BoundSql,這個(gè)BoundSql其實(shí)跟我們利用StatementHandler獲取到的BoundSql是同一個(gè)對象。
//delegate里面的boundSql也是通過mappedStatement.getBoundSql(paramObj)方法獲取到的。
BoundSql boundSql = mappedStatement.getBoundSql(page);
//獲取到我們自己寫在Mapper映射語句中對應(yīng)的Sql語句
String sql = boundSql.getSql();
//通過查詢Sql語句獲取到對應(yīng)的計(jì)算總記錄數(shù)的sql語句
String countSql = this.getCountSql(sql);
//通過BoundSql獲取對應(yīng)的參數(shù)映射
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
//利用Configuration、查詢記錄數(shù)的Sql語句countSql、參數(shù)映射關(guān)系parameterMappings和參數(shù)對象page建立查詢記錄數(shù)對應(yīng)的BoundSql對象。
BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page);
//通過mappedStatement、參數(shù)對象page和BoundSql對象countBoundSql建立一個(gè)用于設(shè)定參數(shù)的ParameterHandler對象
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql);
//通過connection建立一個(gè)countSql對應(yīng)的PreparedStatement對象。
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = connection.prepareStatement(countSql);
//通過parameterHandler給PreparedStatement對象設(shè)置參數(shù)
parameterHandler.setParameters(pstmt);
//之后就是執(zhí)行獲取總記錄數(shù)的Sql語句和獲取結(jié)果了。
rs = pstmt.executeQuery();
if (rs.next()) {
int totalRecord = rs.getInt(1);
//給當(dāng)前的參數(shù)page對象設(shè)置總記錄數(shù)
page.setTotalRecord(totalRecord);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
if (pstmt != null)
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 根據(jù)原Sql語句獲取對應(yīng)的查詢總記錄數(shù)的Sql語句
* @param sql
* @return
*/
private String getCountSql(String sql) {
int index = sql.indexOf("from");
return "select count(*) " + sql.substring(index);
}
/**
* 根據(jù)page對象獲取對應(yīng)的分頁查詢Sql語句,這里只做了兩種數(shù)據(jù)庫類型,Mysql和Oracle
* 其它的數(shù)據(jù)庫都 沒有進(jìn)行分頁
*
* @param page 分頁對象
* @param sql 原sql語句
* @return
*/
private String getPageSql(Page<?> page, String sql) {
StringBuffer sqlBuffer = new StringBuffer(sql);
if ("mysql".equalsIgnoreCase(dialect)) {
return getMysqlPageSql(page, sqlBuffer);
} else if ("oracle".equalsIgnoreCase(dialect)) {
return getOraclePageSql(page, sqlBuffer);
}
return sqlBuffer.toString();
}
/**
* 獲取Mysql數(shù)據(jù)庫的分頁查詢語句
* @param page 分頁對象
* @param sqlBuffer 包含原sql語句的StringBuffer對象
* @return Mysql數(shù)據(jù)庫分頁語句
*/
private String getMysqlPageSql(Page<?> page, StringBuffer sqlBuffer) {
//計(jì)算第一條記錄的位置,Mysql中記錄的位置是從0開始的。
// System.out.println("page:"+page.getPage()+"-------"+page.getRows());
int offset = (page.getPage() - 1) * page.getRows();
sqlBuffer.append(" limit ").append(offset).append(",").append(page.getRows());
return sqlBuffer.toString();
}
/**
* 獲取Oracle數(shù)據(jù)庫的分頁查詢語句
* @param page 分頁對象
* @param sqlBuffer 包含原sql語句的StringBuffer對象
* @return Oracle數(shù)據(jù)庫的分頁查詢語句
*/
private String getOraclePageSql(Page<?> page, StringBuffer sqlBuffer) {
//計(jì)算第一條記錄的位置,Oracle分頁是通過rownum進(jìn)行的,而rownum是從1開始的
int offset = (page.getPage() - 1) * page.getRows() + 1;
sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum < ").append(offset + page.getRows());
sqlBuffer.insert(0, "select * from (").append(") where r >= ").append(offset);
//上面的Sql語句拼接之后大概是這個(gè)樣子:
//select * from (select u.*, rownum r from (select * from t_user) u where rownum < 31) where r >= 16
return sqlBuffer.toString();
}
/**
* 攔截器對應(yīng)的封裝原始對象的方法
*/
public Object plugin(Object arg0) {
// TODO Auto-generated method stub
if (arg0 instanceof StatementHandler) {
return Plugin.wrap(arg0, this);
} else {
return arg0;
}
}
/**
* 設(shè)置注冊攔截器時(shí)設(shè)定的屬性
*/
public void setProperties(Properties p) {
}
public String getDialect() {
return dialect;
}
public void setDialect(String dialect) {
this.dialect = dialect;
}
public String getPageSqlId() {
return pageSqlId;
}
public void setPageSqlId(String pageSqlId) {
this.pageSqlId = pageSqlId;
}
}
xml配置:
<!-- MyBatis 接口編程配置 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- basePackage指定要掃描的包,在此包之下的映射器都會被搜索到,可指定多個(gè)包,包與包之間用逗號或分號分隔--> <property name="basePackage" value="com.yidao.mybatis.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean> <!-- MyBatis 分頁攔截器--> <bean id="paginationInterceptor" class="com.mybatis.interceptor.PageInterceptor"> <property name="dialect" value="mysql"/> <!-- 攔截Mapper.xml文件中,id包含query字符的語句 --> <property name="pageSqlId" value=".*query$"/> </bean>
Page類
package com.yidao.utils;
/**自己看看,需要什么字段加什么字段吧*/
public class Page {
private Integer rows;
private Integer page = 1;
private Integer totalRecord;
public Integer getRows() {
return rows;
}
public void setRows(Integer rows) {
this.rows = rows;
}
public Integer getPage() {
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public Integer getTotalRecord() {
return totalRecord;
}
public void setTotalRecord(Integer totalRecord) {
this.totalRecord = totalRecord;
}
}
ReflectHelper類
package com.yidao.utils;
import java.lang.reflect.Field;
import org.apache.commons.lang3.reflect.FieldUtils;
public class ReflectHelper {
public static Object getFieldValue(Object obj , String fieldName ){
if(obj == null){
return null ;
}
Field targetField = getTargetField(obj.getClass(), fieldName);
try {
return FieldUtils.readField(targetField, obj, true ) ;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null ;
}
public static Field getTargetField(Class<?> targetClass, String fieldName) {
Field field = null;
try {
if (targetClass == null) {
return field;
}
if (Object.class.equals(targetClass)) {
return field;
}
field = FieldUtils.getDeclaredField(targetClass, fieldName, true);
if (field == null) {
field = getTargetField(targetClass.getSuperclass(), fieldName);
}
} catch (Exception e) {
}
return field;
}
public static void setFieldValue(Object obj , String fieldName , Object value ){
if(null == obj){return;}
Field targetField = getTargetField(obj.getClass(), fieldName);
try {
FieldUtils.writeField(targetField, obj, value) ;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Java簡單實(shí)現(xiàn)SpringMVC+MyBatis分頁插件
- Mybatis分頁插件PageHelper的使用詳解
- Java的MyBatis框架中實(shí)現(xiàn)多表連接查詢和查詢結(jié)果分頁
- Mybatis實(shí)現(xiàn)增刪改查及分頁查詢的方法
- SpringMvc+Mybatis+Pagehelper分頁詳解
- Mybatis常用分頁插件實(shí)現(xiàn)快速分頁處理技巧
- mybatis分頁插件pageHelper詳解及簡單實(shí)例
- Spring mvc整合mybatis(crud+分頁插件)操作mysql
- spring boot和mybatis集成分頁插件
- mybatis分頁及模糊查詢功能實(shí)現(xiàn)
相關(guān)文章
Spring?Security短信驗(yàn)證碼實(shí)現(xiàn)詳解
本文主要介紹了Spring?Security短信驗(yàn)證碼的實(shí)現(xiàn)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-11-11
Java詳解如何將excel數(shù)據(jù)轉(zhuǎn)為樹形
在平常的辦公工作中,excel數(shù)據(jù)的操作是最常見的需求,今天就來看一下通過Java如何來實(shí)現(xiàn)將excel數(shù)據(jù)轉(zhuǎn)為樹形,感興趣的朋友可以了解下2022-08-08
Java源碼解析ArrayList及ConcurrentModificationException
今天小編就為大家分享一篇關(guān)于Java源碼解析ArrayList及ConcurrentModificationException,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-01-01
java實(shí)現(xiàn)查找PDF關(guān)鍵字所在頁碼及其坐標(biāo)
這篇文章主要介紹了java實(shí)現(xiàn)查找PDF關(guān)鍵字所在頁碼及其坐標(biāo)的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09
Java把Map轉(zhuǎn)為對象的實(shí)現(xiàn)代碼
在項(xiàng)目開發(fā)中,經(jīng)常碰到map轉(zhuǎn)實(shí)體對象或者對象轉(zhuǎn)map的場景,工作中,很多時(shí)候我們可能比較喜歡使用第三方j(luò)ar包的API對他們進(jìn)行轉(zhuǎn)化,但這里,我想通過反射的方式對他們做轉(zhuǎn)化,感興趣的同學(xué)跟著小編來看看吧2023-08-08
SpringBoot中的@RequestMapping注解的用法示例
@RequestMapping注解是SpringBoot中最常用的注解之一,它可以幫助開發(fā)者定義和處理HTTP請求,本篇文章我們將詳細(xì)為大家介紹如何使用SpringBoot中的@RequestMapping注解,感興趣的同學(xué)跟著小編一起來學(xué)習(xí)吧2023-06-06
Java編程實(shí)現(xiàn)從給定范圍內(nèi)隨機(jī)N個(gè)不重復(fù)數(shù)生成隨機(jī)數(shù)的方法小結(jié)
這篇文章主要介紹了Java編程實(shí)現(xiàn)從給定范圍內(nèi)隨機(jī)N個(gè)不重復(fù)數(shù)生成隨機(jī)數(shù)的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了java根據(jù)指定范圍生成不重復(fù)隨機(jī)數(shù)的相關(guān)操作技巧,需要的朋友可以參考下2017-04-04
java list中包含某個(gè)字符串的兩種方法實(shí)現(xiàn)
在Java開發(fā)中,經(jīng)常需要判斷一個(gè)List中是否包含特定的字符串,包括使用contains()方法和循環(huán)遍歷判斷,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03
Java Swing JLabel標(biāo)簽的使用方法
這篇文章主要介紹了Java Swing JLabel標(biāo)簽的使用方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12

