MyBatis是一款非常好用的持久層框架,它支持定制化SQL陈症、數(shù)據(jù)庫存儲過程及高級映射。MyBatis讓使用者避免了幾乎所有的 JDBC 代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集。MyBatis可采用XML和注解兩種方式配置和映射原生類型坷剧。接口和 Java 的 POJO。
一喊暖、攔截器Mybatis
而我們實現(xiàn)的分頁功能就是基于mybatis的插件模塊惫企,Mybatis為我們提供了Interceptor接口,通過實現(xiàn)該接口就可以定義我們自己的攔截器陵叽。我們先來看一下這個接口的定義:
public interface Interceptor {
//是實現(xiàn)攔截邏輯的地方狞尔,內(nèi)部要通過invocation.proceed()顯式地推進責(zé)任鏈前進,也就是調(diào)用下一個攔截器攔截目標方法巩掺。
Object intercept(Invocation invocation) throws Throwable;
//就是用當前這個攔截器生成對目標target的代理
Object plugin(Object target);
//用于設(shè)置額外的參數(shù)偏序,參數(shù)配置在攔截器的Properties節(jié)點里
void setProperties(Properties properties);
}
三、簡單案例實現(xiàn)
Demo采用技術(shù)SpringBoot+MyBatis
1胖替、pom.xml文件引入mybatis依賴
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2研儒、封裝類代碼實現(xiàn)
- 該類封裝了分頁查詢的頁碼和頁面大小,還有排序規(guī)独令。
/**
* 通用分頁請求參數(shù)
* @author wuyh
*/
public class PageRequest implements Serializable {
private static final long serialVersionUID = -2464407342708149892L;
/**
*頁碼(從0開始)
*/
private int page;
/**
*每頁顯示數(shù)量
*/
private int size;
/**
*排序參數(shù)
*/
private Sort sort;
public PageRequest() {
this(0, 10, (Sort)null);
}
public PageRequest(Sort sort) {
this(0, 10, sort);
}
public PageRequest(int page, int size) {
this(page, size, (Sort)null);
}
public PageRequest(int page, int size, Sort sort) {
if (page < 0) {
page = 0;
}
if (size < 0) {
size = 0;
}
this.page = page;
this.size = size;
this.sort = sort;
}
public int getPage() {
return this.page;
}
public int getSize() {
return this.size;
}
public int getPageSize() {
return this.size;
}
public int getPageNumber() {
return this.page;
}
public long getOffset() {
return this.page * this.size;
}
public boolean hasPrevious() {
return this.page > 0;
}
public PageRequest next() {
return new PageRequest(this.page + 1, this.size);
}
public PageRequest previousOrFirst() {
return this.hasPrevious() ? new PageRequest(this.page - 1, this.size) : this;
}
public PageRequest first() {
return new PageRequest(0, this.size);
}
public Sort getSort() {
return this.sort;
}
}
- 分頁結(jié)果集封裝
public class Page<T> implements Serializable {
private static final long serialVersionUID = 1625981207349025919L;
//查詢結(jié)果集
private final List<T> content;
//分頁參數(shù)
private PageRequest pageRequest;
//總記錄數(shù)
private int total;
public Page(List<T> content, PageRequest pageRequest, int total) {
this.content = new ArrayList();
if (null == content) {
throw new IllegalArgumentException("Content must not be null!");
} else {
this.content.addAll(content);
this.total = total;
this.pageRequest = pageRequest;
}
}
public Page(List<T> content, PageRequest pageRequest) {
this(content, pageRequest, null == content ? 0 : content.size());
}
public Page(List<T> content) {
this(content, (PageRequest)null, null == content ? 0 : content.size());
}
public int getNumberOfElements() {
return this.content.size();
}
public int getTotalElements() {
return this.total;
}
public List<T> getContent() {
return Collections.unmodifiableList(this.content);
}
public boolean hasContent() {
return !this.content.isEmpty();
}
public PageRequest getPageRequest() {
return this.pageRequest;
}
public int getTotal() {
return this.total;
}
}
- 攔截器部分
這里的只要思路是:
建立一個Mybatis攔截器用于攔截Executor接口的query方法端朵,在攔截之后如果參數(shù)列表有分頁請求對象,我這里分頁重新拼接sql執(zhí)行實現(xiàn)自己的query方法邏輯燃箭,否則按原來方式執(zhí)行冲呢。
@Intercepts 在實現(xiàn)Interceptor接口的類聲明,使該類PageInterceptor注冊成為攔截器。
package com.wuyh.demo.interceptor;
import com.wuyh.demo.utils.Page;
import com.wuyh.demo.utils.PageRequest;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.*;
/**
* @ClassName UserInterceptor
* @Description: TODO
* @Author wuyh
* @Date 2022/2/10
* @Version V1.0
**/
@Component
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class PageInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(PageInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
logger.info("進入攔截器");
Object[] args = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement) args[0];
//獲取參數(shù)
Object param = invocation.getArgs()[1];
PageRequest pageRequest = this.getPageRequest(param);
BoundSql boundSql = null;
Object parameterObject = null;
/**
* 判斷參數(shù)列表是否有PageRequest來判斷是否需要進行分頁
*/
if (pageRequest != null) {
Object whereParam = getWhereParameter(param);
boundSql = mappedStatement.getBoundSql(whereParam);
//強轉(zhuǎn) 為了拿到分頁數(shù)據(jù)
PageRequest pageVo = pageRequest;
String sql = boundSql.getSql();
//獲取相關(guān)配置
Configuration config = mappedStatement.getConfiguration();
Connection connection = config.getEnvironment().getDataSource().getConnection();
//拼接查詢當前條件的sql的總條數(shù)
String countSql = "select count(*) from (" + sql + ") a";
PreparedStatement preparedStatement = connection.prepareStatement(countSql);
BoundSql countBoundSql = new BoundSql(config, countSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, countBoundSql);
parameterHandler.setParameters(preparedStatement);
//執(zhí)行獲得總條數(shù)
ResultSet rs = preparedStatement.executeQuery();
int count = 0;
if (rs.next()) {
count = rs.getInt(1);
}
//拼接分頁sql
String pageSql = sql + " limit " + pageVo.getOffset() + " , " + pageVo.getPageSize();
//重新執(zhí)行新的sql
doNewSql(invocation, pageSql);
Object result = invocation.proceed();
connection.close();
//處理新的結(jié)構(gòu)
Page<?> page = new Page((List)result, pageVo, count);
List<Page> returnResultList = new ArrayList<>();
returnResultList.add(page);
return returnResultList;
}
return invocation.proceed();
}
private void doNewSql(Invocation invocation, String sql){
final Object[] args = invocation.getArgs();
MappedStatement statement = (MappedStatement) args[0];
Object parameterObject = getWhereParameter(args[1]);
BoundSql boundSql = statement.getBoundSql(parameterObject);
MappedStatement newStatement = newMappedStatement(statement, new BoundSqlSqlSource(boundSql));
MetaObject msObject = MetaObject.forObject(newStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
msObject.setValue("sqlSource.boundSql.sql", sql);
args[0] = newStatement;
}
/**
* 獲取新的MappedStatement
* @param ms
* @param newSqlSource
* @return
*/
private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
MappedStatement.Builder builder =
new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
builder.resource(ms.getResource());
builder.fetchSize(ms.getFetchSize());
builder.statementType(ms.getStatementType());
builder.keyGenerator(ms.getKeyGenerator());
if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
StringBuilder keyProperties = new StringBuilder();
for (String keyProperty : ms.getKeyProperties()) {
keyProperties.append(keyProperty).append(",");
}
keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
builder.keyProperty(keyProperties.toString());
}
builder.timeout(ms.getTimeout());
builder.parameterMap(ms.getParameterMap());
builder.resultMaps(ms.getResultMaps());
builder.resultSetType(ms.getResultSetType());
builder.cache(ms.getCache());
builder.flushCacheRequired(ms.isFlushCacheRequired());
builder.useCache(ms.isUseCache());
return builder.build();
}
@Override
public Object plugin(Object o) {
Object wrap = Plugin.wrap(o, this);
return wrap;
}
@Override
public void setProperties(Properties properties) {
}
/**
* 新的SqlSource需要實現(xiàn)
*/
class BoundSqlSqlSource implements SqlSource {
private BoundSql boundSql;
public BoundSqlSqlSource(BoundSql boundSql) {
this.boundSql = boundSql;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return boundSql;
}
}
/**
* 從參數(shù)列表返回PageRequest
*/
public PageRequest getPageRequest(Object paramMap) {
if (paramMap == null) {
return null;
} else if (PageRequest.class.isAssignableFrom(paramMap.getClass())) {
return (PageRequest)paramMap;
} else {
if (paramMap instanceof ParamMap) {
ParamMap map = (ParamMap)paramMap;
Iterator iterator = map.entrySet().iterator();
while(iterator.hasNext()) {
Map.Entry entry = (Map.Entry)iterator.next();
Object obj = entry.getValue();
if (obj != null && PageRequest.class.isAssignableFrom(obj.getClass())) {
return (PageRequest)obj;
}
}
}
return null;
}
}
private Object getWhereParameter(Object obj) {
if (obj instanceof ParamMap) {
ParamMap paramMap = (ParamMap)obj;
if (paramMap.size() == 4) {
Iterator iterator = paramMap.entrySet().iterator();
while(iterator.hasNext()) {
Map.Entry var4 = (Map.Entry)iterator.next();
Object var5 = var4.getValue();
if (Sort.class.isAssignableFrom(var5.getClass()) || PageRequest.class.isAssignableFrom(var5.getClass())) {
return paramMap.get("param1");
}
}
}
}
return obj;
}
}
4)業(yè)務(wù)代碼
@Mapper
public interface SysUserMapper {
Page<SysUser> selectUserPage(SysUser user, PageRequest pageRequest);
int addUser(SysUser user);
void deleteUser(Long userId);
}
@Service
public class UserService {
@Autowired
SysUserMapper userMapper;
public Page<SysUser> selectUserPage(){
SysUser sysUser = new SysUser();
return userMapper.selectUserPage(sysUser, new PageRequest(0, 10));
}
public void add(Long userId, String userName, String passWord) {
userMapper.deleteUser(userId);
SysUser sysUser = new SysUser();
sysUser.setUserId(userId);
sysUser.setUserName(userName);
sysUser.setPassWord(passWord);
userMapper.addUser(sysUser);
}
}
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("getUser")
public String GetUser(){
userService.add(11L,"pageOne", "Admin1");
userService.add(12L,"pageTwo", "Admin1");
return userService.selectUserPage().getContent().toString();
}
}
-
Demo測試結(jié)果
image
四招狸、 小結(jié)
Executor是sql語句的執(zhí)行器敬拓,Executor通過配置對象創(chuàng)建StatementHandler邻薯,繼而得到了StatementHandler,StatementHandler是整個數(shù)據(jù)庫訪問過程的控制關(guān)鍵乘凸,它的內(nèi)部持有ParameterHandler厕诡,因此StatementHandler可以通過后者來處理參數(shù)。在StatementHandler處理參數(shù)的過程中會通過參數(shù)類型來找到對應(yīng)的typeHandler來處理參數(shù)营勤,整個過程中Statement對象都作為參數(shù)在傳遞木人,到了typeHandler他會調(diào)用Statement的setInt來設(shè)置值,其實整個過程中Statement對象都在傳遞冀偶,Mybatis通過封裝醒第,但是還是在使用JDBC的API。