PageHelper
1. Подготовка к разработке
1. Инструменты разработки
2. Среда разработки
3. Зависимости развития
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
2. Техническая документация
1. На основе SpringBoot
2. На основе MyBatis
3. Интегрируйте PageHelper
3. Объяснение применения
1. Основное использование
В реальном приложении проекта,PageHelperочень удобен и быстр в использовании, толькоPageInfo
+ PageHelper
Два класса достаточны для завершения функции пейджинга, но часто этот простейший интегрированный метод использования не был полностью разработан и использован во многих практических сценариях приложений.
Далее наше наиболее распространенное использование:
public PageInfo<ResponseEntityDto> page(RequestParamDto param) {
PageHelper.startPage(param.getPageNum(), param.getPageSize());
List<ResoinseEntityDto> list = mapper.selectManySelective(param);
PageInfo<ResponseEntityDto> pageInfo = (PageInfo<ResponseEntityDto>)list;
return pageInfo;
}
В определенной степени написанное выше действительно соответствует спецификации использования PageHelper:
Использовать перед запросом коллекции
PageHelper.startPage(pageNum,pageSize)
,а такжеНикакой другой SQL не может быть выполнен с вкраплениями посередине
Разработчик, но как нам часто удается найти прорыв только в погоне за совершенными и экстремальными дорогами и возможностями; Ниже приведены разумные и основные нормы употребления:
public PageInfo<ResponseEntityDto> page(RequestParamDto param) {
return PageHelper.startPage(param.getPageNum(), param.getPageSize())
.doSelectPageInfo(() -> list(param))
}
public List<ResponseEntityDto> list(RequestParamDto param) {
return mapper.selectManySelective(param);
}
FAQ
1. Зачем повторно объявлять функцию списка?
Ответ: часто во многих практических сценариях бизнес-приложений,Пейджинговый запросОн основан на требованиях к отображению таблицы большого объема данных.
Однако много раз,Например: взаимные звонки внутренних служб,
OpenAPI
при условии.
Даже в некоторых бизнес-сценариях, где внешний и внутренний интерфейсы разделены и отлажены, для предоставления услуг также требуется нестраничный интерфейс запроса коллекции.
Кроме того, помимо вышеупомянутых факторов, на данный момент мы можем определить и стандартизировать некоторые вещи в соответствии с приведенным выше письмом.
Например: для запросов на разбиение на страницы и коллекцииразделение и разъединение(Подробности о развязке см. в разделе Расширенное использование),
Запрос и ответ на пейджинговый запрос и фактический бизнесРазделение параметров(Подробности см. в разделе Расширенное использование) и так далее...
2. doSelectPageInfo
что это такое?
отвечать:
doSelectPageInfo
даPageHelper.startPage()
Значение по умолчанию, возвращаемое функциейPage
Встроенные функции экземпляра
Эту функцию можно использовать для
Lambda
в виде доп.Function
запросить без лишнегоPageInfo
а такжеList
конвертировать
а также
doSelectPageInfo
ПараметрыPageHelper
ВстроенныйFunction(ISelect)
интерфейс для достижения конверсииPageInfo
цель
3. Объем кода при таком способе написания вроде бы много но много?
Ответ: Как описано в ①, на самом деле нет дальнейшего упрощения с точки зрения размера кода.
Однако в некоторых бизнес-сценариях
list
В случае функционального интерфейса это более интуитивно понятная оптимизация (подробности оптимизации см. в разделе «Расширенное использование»).
2. Расширенное использование
Сначала посмотрите на код, а потом говорите об анализе:
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import java.util.List;
/**
* @param <Param> 泛型request
* @param <Result> 泛型response
*/
public interface BaseService<Param, Result> {
/**
* 分页查询
*
* @param param 请求参数DTO
* @return 分页集合
*/
default PageInfo<Result> page(PageParam<Param> param) {
return PageHelper.startPage(param).doSelectPageInfo(() -> list(param.getParam()));
}
/**
* 集合查询
*
* @param param 查询参数
* @return 查询响应
*/
List<Result> list(Param param);
}
можно увидеть
BaseService
можно использовать как глобальноеService
Инкапсуляция и объявление универсального интерфейса
В качестве общего пейджингового интерфейса здесь используется функция страницы
interface
Уникальное ключевое словоdefault
Прямой заявленpage
тело метода функцииbody
import com.github.pagehelper.IPage;
import lombok.Data;
import lombok.experimental.Accessors;
@Data // 为省略冗余代码使用lombok 实际应有常规Getter/Setter Construction toString等
@Accessors(chain = true) // 此lombok注解是为了实现 Entity伪Build 譬如: entity.setX(x).setY(y)
public class PageParam<T> implements IPage {
// description = "页码", defaultValue = 1
private Integer pageNum = 1;
// description = "页数", defaultValue = 20
private Integer pageSize = 20;
// description = "排序", example = "id desc"
private String orderBy;
// description = "参数"
private T param;
public PageParam<T> setOrderBy(String orderBy) {
this.orderBy = orderBy; // 此处可优化 优化详情且看解析
return this;
}
}
существует
BaseService
в мы видим новыйPageParam
,СсылкаPageInfo
Используется для переноса/объявления/разделения параметров пейджинга и бизнес-параметров, а тип параметра является универсальным, то есть бизнес-параметры, которые поддерживают любой тип данных.
Это также можно увидеть
PageParam
ДостигнутоIPage
интерфейс и еще кое-чтоorderBy
поле свойства
import common.base.BaseService;
import dto.req.TemplateReqDto;
import dto.resp.TemplateRespDto;
public interface TemplateService extends BaseService<TemplateReqDto, TemplateeRespDto> {
// 同为interface接口, 业务Service只需要继承BaseService
// 并根据实际使用场景声明请求参数和响应结果的Entity实体即可
}
В практических приложениях нам нужно только объявить наши общие параметры запроса бизнес-запроса и результаты ответа.
import dto.req.TemplateReqDto;
import dto.resp.TemplateRespDto;
import service.TemplateService;
import persistence.mapper.TemplateMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j // 基于lombok自动生成logger日志记录实例
@Service // SpringBoot中注册Service Bean的注解
@RequiredArgsConstructor // 基于lombok根据类所有final属性生成构造函数 即可完成Spring构造注入
public class TemplateServiceImpl implements TemplateService {
private final TemplateMapper mapper;
@Override
public List<TemplateRespDto> list(TemplateReqDto param) {
return mapper.selectManySelective(param) // 可根据实际情况将实体做转换
}
}
В классе реализации вам нужно только переопределить
list
Тело метода, которое записывает методы обработки бизнес-логики и запросов, которые необходимо обрабатывать в реальном бизнес-сценарии, не должно заботиться о функции подкачки.
@Slf4j // 同上
@RestController // SpringBoot中注册Controller Bean的注解
@RequiredArgsConstructor // 同上
public class TemplateController {
public final TemplateService service;
/**
* 分页查询
*
* @param pageParam 分页查询参数
* @return 分页查询响应
*/
@PostMapping(path = "page")
public PageInfo<Result> page(@RequestBody PageParam<Param> pageParam) {
return service.page(pageParam);
}
/**
* 集合查询
*
* @param listParam 集合查询参数
* @return 集合查询响应
*/
@PostMapping(path = "list")
public List<Result> list(@RequestBody Param listParam) {
return service.list(listParam);
}
}
окончательный код
Controller
интерфейс, вам нужно только напрямую позвонитьservice.page
Вы можете использовать параметры запроса напрямуюPageParam
Упаковка, разделение параметров пейджинга и бизнес-параметров и поддержка этой == спецификации разделения == при совместной отладке внешнего и внутреннего интерфейсов может значительно снизить затраты на связь и разработку.
FAQ
1. BaseService
так какinterface
,page
Почему может быть объявлено тело метода?
отвечать:
Java8
Одной из новых функций являетсяinterface
Добавлен класс интерфейсаstatic/default
методы, то есть после объявления метода его подклассы или реализации будут иметь эти методы по умолчанию и могут быть вызваны напрямую
и здесь для
Page
объявление методаdefault
Потому чтоpage
функция заботится толькоПараметры пагинации и ответы на пагинацию, отделенное от бизнес-сценария, тело метода сильно отличается, поэтому просто определяется абстрактно, исключая сложный и избыточный процесс его реализации
2. PageParam
В чем смысл декларации?IPage
за что?
отвечать:
PageParam
является ссылкойPageInfo
Письменный класс (не уверен в будущемPageHelper
Будет ли он инкапсулировать этот класс, может быть, я могу упомянуть об этомIssue
Поднимайтесь, а также участвуйте в разработке open source фреймворков)
Целью написания этого класса является разделение пейджинга и бизнес-данных, что позволяет разработчикам сосредоточиться на бизнес-реализации и разработке, а также выполнять запросы пейджинга.
API
Спецификация, будь то запрос или ответ, данные, относящиеся к пейджингу, извлекаются и используются отдельно.
реализовать
IPage
Потому чтоIPage
так какPageHelper
Встроенныйinterface
, прежде чем мы узнаем больше о его роли, его можно использовать в качестве спецификации для нашего объявления параметра подкачки, иIPage
В файле объявлено только три метода.pageNum/pageSize/orderBy
изGetter
метода, и в анализе исходного кода я упомяну более глубокий смысл реализации этого интерфейса
3. PageParam
в дополнение к обычномуpageNum/pageSize
, зачем тебеorderBy
?
Ответ: В обычных запросах на пейджинг только
pageNum/pageSize
Цель пейджинга может быть выполнена, но фильтрация и сортировка часто сопровождаются пейджинговыми запросами.
а также
orderBy
Основное внимание уделяется динамической сортировке параметров на основе SQL.
4. orderBy
Как пользоваться?Не будет ли проблем?
отвечать:
orderBy
а такжеpageNum/pageSize
, Они естьPagehelper
пройти черезMyBatis
Перехватчик внедряется в запрос запроса, поэтому при передаче параметров во внешнем интерфейсеorderBy
параметр должен быть базой данныхcolumn desc/asc
В таком виде сортировка по нескольким полям может быть разделена запятыми (,), например:columnA desc,columnB
,
Но, с другой стороны, есть две проблемы: первая заключается в том, что при разработке большинства полей таблицы базы данных используется ==snake case== вместо ==camel case== в обычной разработке, поэтому существует слой Преобразование, и это преобразование может быть назначено времени передачи внешних параметров или времени подключения внутренних параметров.
Во-вторых, выставить поле сортировки интерфейсу настолько голым, что будетзаказ с помощью SQL-инъекциириск, поэтому в процессе фактического использования нам необходимо проверить и устранить неполадки некоторыми способами
orderBy
Допустимы ли переданные параметры, например, использование регулярных выражений для сопоставления значения параметра может содержать толькоorder by
Необходимые значения в синтаксисе, такие как имена полей,desc or asc
, специальные символы/ключевые слова базы данных и т. д. не допускаются
5. pageNum/pageSize
Должен ли я указывать значения по умолчанию?
Ответ: ЧитаяPageHelperисходный код, мы знаем, что в
Page
Когда параметры запроса нулевые, он не дает им значения по умолчанию, и не выполняет дополнительную обработку, так что подкачка не удалась, а значения по умолчанию даются для защиты от различных аварий, которые могут произойти во время фронтенда и внутренние интерфейсы отладки.
3. Анализ исходного кода
Сначала мы видимPageHelper.startPage(param)
Что произошло в процессе:
public static <E> Page<E> startPage(Object params) {
Page<E> page = PageObjectUtil.getPageFromObject(params, true);
Page<E> oldPage = getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
setLocalPage(page);
return page;
}
Это
PageHelper
Абстрактный класс, который расширяется (extend)PageMethod
статический метод в
Посмотрите на первую строку кодаPage<E> page = PageObjectUtil.getPageFromObject(params, true)
что случилось:
public static <T> Page<T> getPageFromObject(Object params, boolean required) {
if (params == null) {
throw new PageException("无法获取分页查询参数!");
} else if (params instanceof IPage) {
IPage pageParams = (IPage)params;
Page page = null;
if (pageParams.getPageNum() != null && pageParams.getPageSize() != null) {
page = new Page(pageParams.getPageNum(), pageParams.getPageSize());
}
if (StringUtil.isNotEmpty(pageParams.getOrderBy())) {
if (page != null) {
page.setOrderBy(pageParams.getOrderBy());
} else {
page = new Page();
page.setOrderBy(pageParams.getOrderBy());
page.setOrderByOnly(true);
}
}
return page;
} else {
... // 此处我只截取了部分代码片段, 以上是较为重要的一块
}
}
Видно, что в этом методе он будет оцениваться в первую очередь
params
Является ли он нулевым, а затем проходитinstanceof
определитьIPage
подкласс или класс реализации Если два вышеуказанныхif/else
не устраивает, тоPageHelper
Это будет получено с помощью большого количества кода отражения в коде, который я не опубликовал.pageNum/pageSize
так же какorderBy
. Как мы все знаем, хотя рефлексия широко используется в Java, и как одна из уникальных особенностей языка, она глубоко любима большинством разработчиков, но рефлексия в некоторой степени требует затрат на производительность, и даже многие на данном этапе становятся мейнстримом. , Чтобы предотвратить слишком низкую производительность фрейма, он исключается рынком. Итак, на этом этапе мы, наконец, объясняем и знаем, почемуPageParam
достигатьIPage
Интерфейс, в коде здесь параметры подкачки могут быть получены непосредственно через интерфейс, без необходимости получения отрицательного для производительности отражения.PageHelper
Нужны параметры
Продолжай читатьstartPage
Последующий код в:
public abstract class PageMethod {
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal();
protected static boolean DEFAULT_COUNT = true;
public PageMethod() {
}
protected static void setLocalPage(Page page) {
LOCAL_PAGE.set(page);
}
public static <T> Page<T> getLocalPage() {
return (Page)LOCAL_PAGE.get();
}
...
...
}
можно увидеть
PageHelper
Унаследованный абстрактный классPageMethod
объявляетPage
локальная переменная потока, аgetLocalPage()
это получить текущий потокPage
и далее
if (oldPage != null && oldPage.isOrderByOnly())
Это чтобы определить, есть ли старые данные пейджинга
здесь
isOrderByOnly
пройти черезgetPageFromObject()
функции мы можем знать, что когда есть толькоorderBy
параметр, этоtrue
То есть, когда имеются старые данные поискового вызова, а старые данные поискового вызова имеют только параметры сортировки, параметры сортировки старых данных поискового вызова включаются в параметры сортировки новых данных поискового вызова.
Затем поместите новые данные с разбивкой на страницы
page
Хранится в локальной переменной потока
В сценариях практического применения такая ситуация встречается относительно редко, только сортировка без подкачки, поэтому с определенной точки зрения нам нужно только понять
см. далееdoSelectPageInfo(ISelect)
Что произошло в:
public <E> PageInfo<E> doSelectPageInfo(ISelect select) {
select.doSelect();
return this.toPageInfo();
}
Как видите, реализация этого способа очень проста и понятна, то есть через регистрационную декларацию
ISelect
Интерфейс разрабатывается с помощью пользовательского метода запроса коллекции и выполняется им внутри, а затем возвращаетPageInfo
организация
Мы упоминали ранее,
PageHelper
на основеMyBatis
Перехватчик достигает цели пейджинга, так почемуISelect.doSelect()
выполнить, вернутьPageInfo
Как насчет сущностей?
На самом деле это магия перехватчика, в
select.doSelect()
При выполнении срабатываетPageHelper
индивидуальныеMyBatis
Перехватчик запросов и анализ SQL и параметров SQL, разбиение по страницам в соответствии с типом базы данных, например MySQL.limit
, ОракулRownum
Ждать,
В то же время перед запросом SQL мы определяем,
PageHelper
SQL-запрос select count(*) будет перегенерирован и выполнен первым, и он достиг своего определения.Page
Назначение встроенных параметров пагинации
@Intercepts({@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
), @Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
)})
public class PageInterceptor implements Interceptor {
private volatile Dialect dialect;
private String countSuffix = "_COUNT";
protected Cache<String, MappedStatement> msCountMap = null;
private String default_dialect_class = "com.github.pagehelper.PageHelper";
public PageInterceptor() {
}
public Object intercept(Invocation invocation) throws Throwable {
...
...
}
}
ВышеупомянутоеPageHelper
Встроенная настройкаMyBatisПерехватчик, из-за чрезмерного количества кода, чтобы гарантировать, что он не нарушает принцип не нарушать заголовок этого сообщения в блоге, здесь не будет даваться лишнее объяснение.При необходимости я могу написать отдельный блог, чтобы объяснить и объяснить это отдельно.MyBatisКонцепция и принцип перехватчика, глубокий анализMyBatisисходный код
расширять
PageHelper
не толькоpageNum/pageSize/orderBy
Эти параметры и многое другоеpageSizeZero, reasonable
Параметры и т. д. используются для более сложных определений запросов на подкачку. Если вам нужно более глубокое понимание, я могу написать другое расширенное использование ==PageHelper==. Эта статья используется только в качестве объяснения для обычной разработки и использования.
4. Резюме
Как фреймворк для подкачки с открытым исходным кодом, имеющий почти 10 000 на GitHub, PageHelper может быть не таким глубоким и широким, как основные рыночные фреймворки и технологии, хотя построить колесо несложно с точки зрения реализации функций и принципа, а исходный код тоже очень понятно, но в значительной мере решило многие технические проблемы пейджинга на основе MyBatis, упростило и подтолкнуло к эффективности большинство разработчиков.Это направление и дорога, к которой разработчики должны стремиться и стремиться на пути разработка. Как бенефициары, мы должны не только использовать его в основном, Помимо разработки, мы также должны обратить внимание на расширение некоторых фреймворков, иметь определенное представление о нижнем слое фреймворка, расширять и оптимизировать его.
положи сюда еще разPageHelperрепозиторий с открытым исходным кодом!
яЧен Бу Эр, вы знаете меня?