都这么卷了,不懂MyBatis插件开发怎么行,教你实现一个MyBatis分页插件

最后

一次偶然,从朋友那里得到一份“java高分面试指南”,里面涵盖了25个分类的面试题以及详细的解析:JavaOOP、Java集合/泛型、Java中的IO与NIO、Java反射、Java序列化、Java注解、多线程&并发、JVM、Mysql、Redis、Memcached、MongoDB、Spring、Spring Boot、Spring Cloud、RabbitMQ、Dubbo 、MyBatis 、ZooKeeper 、数据结构、算法、Elasticsearch 、Kafka 、微服务、Linux。

这不,马上就要到招聘季了,很多朋友又开始准备“金三银四”的春招啦,那我想这份“java高分面试指南”应该起到不小的作用,所以今天想给大家分享一下。

image

请注意:关于这份“java高分面试指南”,每一个方向专题(25个)的题目这里几乎都会列举,在不看答案的情况下,大家可以自行测试一下水平 且由于篇幅原因,这边无法展示所有完整的答案解析

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

import org.apache.ibatis.plugin.Signature;

import org.apache.ibatis.session.ResultHandler;

import java.math.BigDecimal;

import java.math.RoundingMode;

import java.sql.Statement;

/**

  • 打印SQL语句

    1. 记录SQL语句
    1. 记录执行的时间
  • type 增强的内置对象的类型(必须是四大内置对象中的一个 StatementHandler.class(增强最多的))

  • method 增强的方法名

  • args{} 是形参列表,防止方法重载,找不到对应的方法

  • @author zhj

*/

@Slf4j

@Intercepts({

@Signature(

type = StatementHandler.class,

method = “query”,

args = {Statement.class, ResultHandler.class}

),

@Signature(

type = StatementHandler.class,

method = “update”,

args = {Statement.class}

)

}

)

public class PrintSQLPlugins implements Interceptor {

/**

  • 拦截方法

  • @param invocation

  • @return

  • @throws Throwable

*/

@Override

public Object intercept(Invocation invocation) throws Throwable {

StatementHandler statementHandler= (StatementHandler) invocation.getTarget();

BoundSql boundSql = statementHandler.getBoundSql();

String sql = boundSql.getSql();

log.info(“----------------------------【SQL】-------------------------------”);

log.info(sql.replace(“\n”,“”));

long beginTime = System.currentTimeMillis();

Object proceed = invocation.proceed(); // 放行,执行目标对象的对应方法

long endTime = System.currentTimeMillis();

log.info(“----------------------------【SQL执行的时长为:{} s】”, BigDecimal.valueOf(endTime - beginTime).divide(BigDecimal.valueOf(1000)).setScale(6, RoundingMode.DOWN).doubleValue());

return proceed;

}

}

  • 让该插件生效

package com.zhj.common.db.mysql.config;

import com.zhj.common.db.mysql.plugins.PrintSQLPlugins;

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.transaction.annotation.EnableTransactionManagement;

/**

  • @author zhj

*/

@Configuration

@MapperScan(“com.zhj.data.mapper”)

@EnableTransactionManagement

public class DBAutoConfiguration {

@Bean

@ConditionalOnProperty(value = “zhj.plugins.printSql.enable”, havingValue = “true”, matchIfMissing = false)

public PrintSQLPlugins getPrintSQLPlugins(){

return new PrintSQLPlugins();

}

}

  • 通过配置决定是否启用插件

@ConditionalOnProperty(value = “zhj.plugins.printSql.enable”, havingValue = “true”, matchIfMissing = false)

  • 导入依赖,创建Bean使插件在配置时可以自动提示

package com.zhj.common.db.mysql.entity;

import lombok.Data;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.stereotype.Component;

/**

  • @author zhj

*/

@Component

@ConfigurationProperties(prefix = “zhj.plugins.printSql”)

@Data

public class ZhjConfigInfo {

private Boolean enable;

}

依赖:

org.springframework.boot

spring-boot-configuration-processor

true

  • 配置文件中开启插件:

zhj:

plugins:

printSql:

enable: true

案例二 分页插件:

基础分页插件的实现:

  • 创建分页对象

package com.zhj.common.db.mysql.page;

import lombok.Data;

import lombok.experimental.Accessors;

import java.io.Serializable;

/**

  • 分页信息对象

  • @author zhj

*/

@Data

@Accessors(chain = true)

public class Page implements Serializable {

/**

  • 当前页

*/

private Integer pageNo;

/**

  • 每页多少条

*/

private Integer pageSize;

/**

  • 总页码

*/

private Integer pageTotal;

/**

  • 总条数

*/

private Integer pageCount;

}

  • 创建分页工具

这里我们通过ThreadLocal来设置分页对象

package com.zhj.common.db.mysql.page;

/**

  • 分页管理器

  • @author zhj

*/

public class PageUtils {

private static ThreadLocal pageThreadLocal = new ThreadLocal<>();

/**

  • 设置分页对象

  • @param pageNo

  • @param pageSize

*/

public static void setPage(Integer pageNo, Integer pageSize){

pageThreadLocal.set(new Page().setPageNo(pageNo).setPageSize(pageSize));

}

/**

  • 获取分页对象

  • @return

*/

public static Page getPage(){

return pageThreadLocal.get();

}

/**

  • 清理分页信息

*/

public static void clear(){

pageThreadLocal.remove();

}

}

  • 创建实现分页插件的拦截器

package com.zhj.common.db.mysql.plugins;

import com.zhj.common.db.mysql.page.Page;

import com.zhj.common.db.mysql.page.PageUtils;

import com.zhj.common.db.mysql.util.MybatisUtils;

import lombok.extern.slf4j.Slf4j;

import org.apache.ibatis.executor.statement.StatementHandler;

import org.apache.ibatis.mapping.BoundSql;

import org.apache.ibatis.plugin.Interceptor;

import org.apache.ibatis.plugin.Intercepts;

import org.apache.ibatis.plugin.Invocation;

import org.apache.ibatis.plugin.Signature;

import org.apache.ibatis.reflection.MetaObject;

import org.apache.ibatis.reflection.SystemMetaObject;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

/**

  • 分页插件

  • @author zhj

*/

@Slf4j

@Intercepts({

@Signature(

type = StatementHandler.class,

method = “prepare”,

args = {Connection.class, Integer.class} // 需要与对应版本一致

)

})

public class PagePlugins implements Interceptor {

@Override

public Object intercept(Invocation invocation) throws Throwable {

// 获取非代理对象

StatementHandler target = MybatisUtils.getNoProxyTarget(invocation.getTarget());

BoundSql boundSql = target.getBoundSql();

// 拿到sql 转为小写,去掉前后空格

String sql = boundSql.getSql().toLowerCase().trim();

// 判断是否需要添加分页

if (!sql.startsWith(“select”)) {

return invocation.proceed();

}

// 获取分页参数

Page page = PageUtils.getPage();

if (page == null) {

return invocation.proceed();

}

// 处理分页

log.info(“[需要分页的SQL: {}”, sql.replace(“\n”,“”));

// 构建一个查询分页总条数的sql;

Integer count = count(target, invocation, sql);

log.info("[SQL的总条数为: " + count);

// 处理pageNo

if (page.getPageNo() == null || page.getPageNo() < 1)

page.setPageNo(1);

// 处理pageSize

if (page.getPageSize() == null || page.getPageSize() < 1)

page.setPageSize(10);

// 设置分页对象

page.setPageCount(count);

page.setPageTotal(page.getPageCount() % page.getPageSize() == 0 ? page.getPageCount()/ page.getPageSize() : page.getPageCount()/ page.getPageSize() + 1);

if (page.getPageNo() > page.getPageTotal())

page.setPageNo(page.getPageTotal());

log.info("[处理过的Page为: " + page);

sql += " limit " + (page.getPageNo() * page.getPageSize() - 1) + “,” + page.getPageSize();

log.info(“[分页处理过的SQL: {}”, sql.replace(“\n”,“”));

// 通过反射设置BoundSql的sql

// MyBatis提供了工具,该工具通过反射实现

MetaObject metaObject = SystemMetaObject.forObject(boundSql);

metaObject.setValue(“sql”, sql);

return invocation.proceed();

}

/**

  • 获取sql的总条数

  • @param sql

  • @return

*/

private Integer count(StatementHandler statementHandler, Invocation invocation, String sql) throws SQLException {

// 判断是否存在排序的内容

int orderByIndex = -1;

if (sql.lastIndexOf(“order by”) != -1) {

sql = sql.substring(0, orderByIndex);

}

// 获取查询总条数sql

int fromIndex = sql.indexOf(“from”);

String countSQL = "select count(*) " + sql.substring(fromIndex);

log.info("[查询总条数的SQL: " + countSQL);

// 执行sql

// 获得方法的参数

Connection connection = (Connection) invocation.getArgs()[0];

PreparedStatement ps = null;

ResultSet resultSet = null;

try {

// sql 处理器

ps = connection.prepareStatement(countSQL);

// 处理参数

statementHandler.parameterize(ps);

// 执行sql

resultSet = ps.executeQuery();

// 获取结果

if (resultSet.first()) {

return resultSet.getInt(1);

}

} catch (SQLException sqlException) {

log.info(“[查询总条数的SQL出现异常!!!]”);

throw sqlException;

} finally {

if (resultSet != null) {

resultSet.close();

}

if (ps != null) {

ps.close();

}

}

return -1;

}

}

  • 由于使用代理模式对MyBatis四大内置对象进行增强,当创建多个分页插件时会进行干扰,我们有时候获得的目标对象,并不是真实的目标对象,而是其它插件形成的代理对象,我们需要写一个工具类获取真实的目标对象。

package com.zhj.common.db.mysql.util;

import org.apache.ibatis.reflection.MetaObject;

import org.apache.ibatis.reflection.SystemMetaObject;

/**

  • @author zhj

*/

public class MybatisUtils {

/**

  • 获取非代理对象

  • @param target

  • @param

  • @return

*/

public static T getNoProxyTarget(Object target) {

MetaObject invocationMetaObject = SystemMetaObject.forObject(target);

while (invocationMetaObject.hasGetter(“h”)) {

// 说明获得的是代理对象

target = invocationMetaObject.getValue(“h.target”);

invocationMetaObject = SystemMetaObject.forObject(target);

}

return (T) target;

}

}

  • 注入分页插件,使其生效

package com.zhj.common.db.mysql.config;

import com.zhj.common.db.mysql.plugins.PagePlugins;

import com.zhj.common.db.mysql.plugins.PrintSQLPlugins;

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.transaction.annotation.EnableTransactionManagement;

/**

  • @author zhj

*/

@Configuration

@MapperScan(“com.zhj.data.mapper”)

@EnableTransactionManagement

public class DBAutoConfiguration {

@Bean

@ConditionalOnProperty(value = “zhj.plugins.printSql.enable”, havingValue = “true”, matchIfMissing = false)

public PrintSQLPlugins getPrintSQLPlugins(){

return new PrintSQLPlugins();

}

@Bean

public PagePlugins getPagePlugins(){

return new PagePlugins();

}

}

  • 在Controller(Service)中设置开启分页

package com.zhj.business.controller;

import com.zhj.business.protocol.input.StudentInput;

import com.zhj.business.service.StudentService;

import com.zhj.common.core.result.Result;

import com.zhj.common.core.util.ResultUtils;

import com.zhj.common.db.mysql.page.PageUtils;

import com.zhj.data.entity.example.Student;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.BeanUtils;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

import java.util.List;

/**

  • @author zhj

*/

@Slf4j

@RestController

@RequestMapping(“/student”)

public class StudentController {

@Autowired

private StudentService studentService;

@GetMapping(“/list”)

public Result<List> list() {

// 开启分页,可将前端传入的值设置到Page中

PageUtils.setPage(1,2);

List list = studentService.list();

return ResultUtils.createSuccess(list);

}

}

让分页插件更优雅:

  • 将侵入部分去掉,通过AOP的方式开启分页,并将分页信息返回

package com.zhj.common.db.mysql.aop;

import com.zhj.common.db.mysql.page.BasePageResult;

import com.zhj.common.db.mysql.page.Page;

import com.zhj.common.db.mysql.page.PageUtils;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.springframework.util.StringUtils;

import org.springframework.web.context.request.RequestContextHolder;

import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**

  • 如果参数携带pageNo和pageSize自动开启分页

  • @author zhj

*/

@Aspect

public class WebPageAOP {

@Around(“@within(org.springframework.web.bind.annotation.RestController) || @within(org.springframework.stereotype.Controller)”)

public Object pageAOP(ProceedingJoinPoint joinPoint) throws Throwable {

// 获取参数

ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

HttpServletRequest request = requestAttributes.getRequest();

String pageNo = request.getParameter(“pageNo”);

String pageSize = request.getParameter(“pageSize”);

if (!StringUtils.isEmpty(pageNo) && !StringUtils.isEmpty(pageSize)) {

PageUtils.setPage(Integer.parseInt(pageNo), Integer.parseInt(pageSize));

}

Object proceed = null;

try {

proceed = joinPoint.proceed();

Page page = PageUtils.getPage();

if (proceed instanceof BasePageResult && page != null) {

BasePageResult basePageResult = (BasePageResult) proceed;

basePageResult.setPage(page);

}

} catch (Throwable e) {

throw e;

} finally {

PageUtils.clear();

总结

对于面试还是要好好准备的,尤其是有些问题还是很容易挖坑的,例如你为什么离开现在的公司(你当然不应该抱怨现在的公司有哪些不好的地方,更多的应该表明自己想要寻找更好的发展机会,自己的一些现实因素,比如对于我而言是现在应聘的公司离自己的家更近,又或者是自己工作到达了迷茫期,想跳出迷茫期等等)

image

Java面试精选题、架构实战文档

整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~

你的支持,我的动力;祝各位前程似锦,offer不断!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

ter(“pageSize”);

if (!StringUtils.isEmpty(pageNo) && !StringUtils.isEmpty(pageSize)) {

PageUtils.setPage(Integer.parseInt(pageNo), Integer.parseInt(pageSize));

}

Object proceed = null;

try {

proceed = joinPoint.proceed();

Page page = PageUtils.getPage();

if (proceed instanceof BasePageResult && page != null) {

BasePageResult basePageResult = (BasePageResult) proceed;

basePageResult.setPage(page);

}

} catch (Throwable e) {

throw e;

} finally {

PageUtils.clear();

总结

对于面试还是要好好准备的,尤其是有些问题还是很容易挖坑的,例如你为什么离开现在的公司(你当然不应该抱怨现在的公司有哪些不好的地方,更多的应该表明自己想要寻找更好的发展机会,自己的一些现实因素,比如对于我而言是现在应聘的公司离自己的家更近,又或者是自己工作到达了迷茫期,想跳出迷茫期等等)

[外链图片转存中…(img-gHUyFmeB-1715710681688)]

Java面试精选题、架构实战文档

整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~

你的支持,我的动力;祝各位前程似锦,offer不断!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值