MyBatis分頁的拓展--合併高階查詢
阿新 • • 發佈:2019-01-25
MyBatis分頁的拓展–合併查詢
在網上有很多關於MyBatis攔截器分頁的辦法,可缺少關於合併查詢的方法。本文將講述這一過程的具體實現。
話不多說,直接貼程式碼:
applicationContext.xml裡這樣配置:
<!-- MyBatis配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- mybatis配置檔案路徑 -->
<property name="configLocation" value="WEB-INF/conf/myBatisConfig.xml"/>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
<!-- 這個執行器會批量執行更新語句, 還有SIMPLE 和 REUSE -->
<constructor-arg index="1" value="BATCH" />
</bean>
<!-- 掃描basePackage介面 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="annotationClass" value="org.springframework.stereotype.Repository" />
<!-- 對映器介面檔案的包路徑, -->
<property name="basePackage" value="com.stock.dao" />
</bean>
接下來是Page分頁部分:
public class Page<T> {
private int pageNo = 1;//頁碼,預設是第一頁
private int pageSize = 15;//每頁顯示的記錄數,預設是15
private int totalRecord;//總記錄數
private int totalPage;//總頁數
private List<?> results;//對應的當前頁記錄
private Map<String, Object> params = new HashMap<String, Object>();//其他的引數我們把它分裝成一個Map物件
/** 頁碼*/
public int getPageNo() {
return pageNo;
}
public void setPageNo(int pageNo) {
this.pageNo = pageNo;
}
/** 每頁記錄數*/
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
/** 總頁數*/
public int getTotalRecord() {
return totalRecord;
}
public void setTotalRecord(int totalRecord) {
this.totalRecord = totalRecord;
//在設定總頁數的時候計算出對應的總頁數,在下面的三目運算中加法擁有更高的優先順序,所以最後可以不加括號。
int totalPage = totalRecord%pageSize==0 ? totalRecord/pageSize : totalRecord/pageSize + 1;
this.setTotalPage(totalPage);
if(0!=totalRecord)
params.clear();
}
public int getTotalPage() {
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
public List<?> getResults() {
return results;
}
public void setResults(List<?> results) {
this.results = results;
}
public Map<String, Object> getParams() {
return params;
}
public void setParams(Map<String, Object> params) {
this.params = params;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Page [pageNo=").append(pageNo).append(", pageSize=")
.append(pageSize).append(", results=").append(results).append(
", totalPage=").append(totalPage).append(
", totalRecord=").append(totalRecord).append("]");
return builder.toString();
}
}
接著,是攔截器
@Intercepts( {
@Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class}) })
public class PageInterceptor implements Interceptor {
private static Logger log = Logger.getLogger(PageInterceptor.class);
private String databaseType;//資料庫型別,不同的資料庫有不同的分頁方法
/**
* 攔截後要執行的方法
*/
public Object intercept(Invocation invocation) throws Throwable {
RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
//通過反射獲取到當前RoutingStatementHandler物件的delegate屬性
StatementHandler delegate = (StatementHandler)ReflectUtil.getFieldValue(handler, "delegate");
BoundSql boundSql = delegate.getBoundSql();
Object obj = boundSql.getParameterObject();
if (obj instanceof Page<?>) {
Page<?> page = (Page<?>) obj;
MappedStatement mappedStatement = (MappedStatement)ReflectUtil.getFieldValue(delegate, "mappedStatement");
Connection connection = (Connection)invocation.getArgs()[0];
String sql = boundSql.getSql();
this.setTotalRecord(page,mappedStatement, connection);
String pageSql = this.getPageSql(page, sql);
ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
}
return invocation.proceed();
}
/**
* 攔截器對應的封裝原始物件的方法
*/
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
/**
* 設定註冊攔截器時設定的屬性
*/
public void setProperties(Properties properties) {
this.databaseType = properties.getProperty("databaseType");
}
/**
* 根據page物件獲取對應的分頁查詢Sql語句,這裡只做了兩種資料庫型別,Mysql和Oracle
* 其它的資料庫都 沒有進行分頁
*
* @param page 分頁物件
* @param sql 原sql語句
* @return
*/
private String getPageSql(Page<?> page, String sql) {
StringBuffer sqlBuffer = new StringBuffer(sql);
if ("mysql".equalsIgnoreCase(databaseType)) {
return getMysqlPageSql(page, sqlBuffer);
} else if ("oracle".equalsIgnoreCase(databaseType)) {
return getOraclePageSql(page, sqlBuffer);
}
return sqlBuffer.toString();
}
/**
* 獲取Mysql資料庫的分頁查詢語句
* @param page 分頁物件
* @param sqlBuffer 包含原sql語句的StringBuffer物件
* @return Mysql資料庫分頁語句
*/
private String getMysqlPageSql(Page<?> page, StringBuffer sqlBuffer) {
sqlBuffer =new StringBuffer(getParamSql(page, sqlBuffer));
//計算第一條記錄的位置,Mysql中記錄的位置是從0開始的。
int offset = (page.getPageNo() - 1) * page.getPageSize();
sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());
return sqlBuffer.toString();
}
/**
* 獲取Oracle資料庫的分頁查詢語句
* @param page 分頁物件
* @param sqlBuffer 包含原sql語句的StringBuffer物件
* @return Oracle資料庫的分頁查詢語句
*/
private String getOraclePageSql(Page<?> page, StringBuffer sqlBuffer) {
//計算第一條記錄的位置,Oracle分頁是通過rownum進行的,而rownum是從1開始的
int offset = (page.getPageNo() - 1) * page.getPageSize() + 1;
sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum < ").append(offset + page.getPageSize());
sqlBuffer.insert(0, "select * from (").append(") where r >= ").append(offset);
//上面的Sql語句拼接之後大概是這個樣子:
//select * from (select u.*, rownum r from (select * from t_user) u where rownum < 31) where r >= 16
return sqlBuffer.toString();
}
/**
* 給當前的引數物件page設定總記錄數
*
* @param page Mapper對映語句對應的引數物件
* @param mappedStatement Mapper對映語句
* @param connection 當前的資料庫連線
*/
private void setTotalRecord(Page<?> page,
MappedStatement mappedStatement, Connection connection) {
BoundSql boundSql = mappedStatement.getBoundSql(page);
String sql = boundSql.getSql();
String countSql = this.getCountSql(page,sql);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page);
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql);
//通過connection建立一個countSql對應的PreparedStatement物件。
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = connection.prepareStatement(countSql);
//通過parameterHandler給PreparedStatement物件設定引數
parameterHandler.setParameters(pstmt);
rs = pstmt.executeQuery();
if (rs.next()) {
int totalRecord = rs.getInt(1);
//給當前的引數page物件設定總記錄數
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();
}
}
}
/**
* 拼接引數
* @param page
* @param sql
* @return
*/
private String getParamSql(Page<?> page, StringBuffer sqlBuffer){
Map<String, Object> params =page.getParams();
if(null!=params){
boolean first= true;
for(Map.Entry<String, Object> entry :params.entrySet() ){
if(first){
sqlBuffer.append(" where ").append(entry.getKey()).append(" = ").append(entry.getValue());
first=!first;
}else{
sqlBuffer.append(" and ").append(entry.getKey()).append(" = ").append(entry.getValue());
}
}
}
return sqlBuffer.toString();
}
/**
* 根據原Sql語句獲取對應的查詢總記錄數的Sql語句
* @param sql
* @return
*/
private String getCountSql(Page<?> page,String sql) {
String countSql = null;
int index = sql.indexOf("from");
countSql = "select count(*) " + sql.substring(index);
countSql = getParamSql(page, new StringBuffer(countSql));
return countSql;
}
/**
* 利用反射進行操作的一個工具類
*
*/
private static class ReflectUtil {
/**
* 利用反射獲取指定物件的指定屬性
* @param obj 目標物件
* @param fieldName 目標屬性
* @return 目標屬性的值
*/
public static Object getFieldValue(Object obj, String fieldName) {
Object result = null;
Field field = ReflectUtil.getField(obj, fieldName);
if (field != null) {
field.setAccessible(true);
try {
result = field.get(obj);
} catch (IllegalArgumentException e) {
log.error(e.getMessage());
e.printStackTrace();
} catch (IllegalAccessException e) {
log.error(e.getMessage());
e.printStackTrace();
}
}
return result;
}
/**
* 利用反射獲取指定物件裡面的指定屬性
* @param obj 目標物件
* @param fieldName 目標屬性
* @return 目標欄位
*/
private static Field getField(Object obj, String fieldName) {
Field field = null;
for (Class<?> clazz=obj.getClass(); clazz != Object.class; clazz=clazz.getSuperclass()) {
try {
field = clazz.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e) {
//這裡不用做處理,子類沒有該欄位可能對應的父類有,都沒有就返回null。
}
}
return field;
}
/**
* 利用反射設定指定物件的指定屬性為指定的值
* @param obj 目標物件
* @param fieldName 目標屬性
* @param fieldValue 目標值
*/
public static void setFieldValue(Object obj, String fieldName,
String fieldValue) {
Field field = ReflectUtil.getField(obj, fieldName);
if (field != null) {
try {
field.setAccessible(true);
field.set(obj, fieldValue);
} catch (IllegalArgumentException e) {
log.error(e.getMessage());
e.printStackTrace();
} catch (IllegalAccessException e) {
log.error(e.getMessage());
e.printStackTrace();
}
}
}
}
}
然後是mybatis配置檔案在這裡我命名為myBatisConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 開啟懶載入 -->
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<setting name="cacheEnabled" value="true" />
</settings>
<typeAliases>
<typeAlias type="com.stock.common.mybatis.Page" alias="page"/>
</typeAliases>
<plugins>
<plugin interceptor="com.stock.common.mybatis.PageInterceptor">
<property name="databaseType" value="Mysql"/>
</plugin>
</plugins>
<mappers>
<mapper resource="com/stock/mybatis-conf/jikeUser.xml" />
</mappers>
</configuration>
最後是Mappers檔案 xx.xml
<mapper namespace="com.xx.dao.xxDao">
<select id="findBy" parameterType="page" resultType="java.util.LinkedHashMap" useCache="true">
<![CDATA[
select * from jikeuser
]]>
</select>
對應DAO介面
@Repository
public interface xxDao {
List<Map<String, Object>> findBy(Page<Map<String, Object>> params);
}
如此以來,就成功啦!