mybatis 在当前项目中的实际应用及自定义分页的实现

news2024/11/25 9:48:04

mybatis 在当前项目中的实际应用及自定义分页的实现

项目中分页代码的解耦

实现目标

实现目标,使用spring 提供的分页相关的类,避免代码中直接使用PageHelper

具体实现

创建自定义PageHelper,并使用spring-data-common提供的具体实现类操作分页

maven 中引入相关依赖

<dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-commons</artifactId>
            <version>2.6.2</version>
</dependency>
public class PageHelper {

    //创建分页参数
    public static Pageable createPageable(int page, int size ) {
        return PageRequest.of(page,size);
    }

    //创建分页参数
    public static Pageable createPageable(int page, int size, Sort sort) {
        return PageRequest.of(page,size,sort);
    }

    public static Page createPage(List content, Pageable pageable, long total) {
        return new PageImpl(content,pageable,total);
    }

    public static Page doSelectPage(Pageable pageable,Select select) {
        com.github.pagehelper.Page page = com.github.pagehelper.PageHelper.startPage(pageable.getPageNumber(), pageable.getPageSize()).doSelectPage(() -> select.doSelect());
        return createPage(page,pageable,page.getTotal());
    }


}

public interface Select {

    void doSelect();

}

这样我们在项目中就不直接与pageHelper 打交道了,将代码控制在自定义的 PageHelper 中了。

具体应用

首先我们的controller层面的过滤bean class 统一继承 PagedRequest.class

public class PagedRequest extends Request {

    /**
     * 页号
     */
    private Integer pageNum;

    /**
     * 单页大小(rows)
     */
    private Integer pageSize;

    public Integer getPageNum() {
        return pageNum;
    }

    public void setPageNum(Integer pageNum) {
        this.pageNum = pageNum;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }
}

到controller层面 因为要调用service,所以转化为 domain对象 我们使用 spring data 提供的 domain Pageable.class

//调用我们自己的工具类创建分页对象
Pageable pageable =
                PagingHelper.createPageable(merchantAuditListReq.getPageNum(), merchantAuditListReq.getPageSize());

将 Pageable 对象和 过滤条件对象传递给service方法

 Page<MerchantAuditListResultModel> page_result = merchantService.getMerchAuditList(pageable, merchantAuditListParamModel);

service 的方法的返回值必须返回 spring data 提供的domain Page 作为返回值

service 层调用具体的查询语句需要使用mybatis 的分页插件,这里提供了一个工具类 PagingHepler.class

//select 方法有两个参数  1 分页数据  2 具体执行的查询方法
Page<MerchantInfoDetailResult> page = PagingHelper.select(pageable, () ->
                custMerchantInfoMapper.getMerchantDetailInfoByExample(merchantInfoParam));

select 方法的具体实现如下,其实还是调用page hepler 的方法来查询分页,获取到数据之后通过 createPage 来创建我们内部流传的Page domain

    public static Page select(Pageable pageable, PagingSelector pagingSelector) {
        com.github.pagehelper.Page page = PageHelper.startPage(pageable.getPageNumber() + 1, pageable.getPageSize())
                .doSelectPage(new PageHelperSelect(pagingSelector));

        return createPage(page.getResult(), pageable, page.getTotal());
    }
 public static Page createPage(List content, Pageable pageable, long total) {
        return new PageImpl(content, pageable, total);
    }

实体类中枚举的更高实现在MyBatis 中的应用

配置

实现自定义枚举类的 TypeHandler,并将枚举转换的TypeHandler 放入mybatis 的config当中.

public class TypeCodeTypeHandler<E extends Enumerable> extends BaseTypeHandler<E> {

	private Class<E> type;
	private final Map<Integer, E> enums;

	public TypeCodeTypeHandler(Class<E> type) {
		if (type == null) {
			throw new IllegalArgumentException("type argument cannot be null");
		}
	/// 是一个 Java 反射 API 方法,用于获取指定枚举类的所有枚举常量。
		E[] enumArray = type.getEnumConstants();
		Map<Integer, E> enums = new HashMap<Integer, E>();
		for (E each : enumArray) {
			enums.put(each.getCode(), each);
		}
		this.type = type;
		this.enums = enums;
	}

	@Override
	public void setNonNullParameter(PreparedStatement ps, int i, E parameter,
			JdbcType jdbcType) throws SQLException {
		ps.setInt(i, parameter.getCode());
	}

	@Override
	public E getNullableResult(ResultSet rs, String columnName)
			throws SQLException {
		int code = rs.getInt(columnName);
		if (rs.wasNull()) {
			return null;
		} else {
			try {
				return enums.get(code);
			} catch (Exception ex) {
				throw new IllegalArgumentException("Cannot convert " + code
						+ " to " + type.getSimpleName() + " by code value.", ex);
			}
		}
	}

	@Override
	public E getNullableResult(ResultSet rs, int columnIndex)
			throws SQLException {
		int code = rs.getInt(columnIndex);
		if (rs.wasNull()) {
			return null;
		} else {
			try {
				return enums.get(code);
			} catch (Exception ex) {
				throw new IllegalArgumentException("Cannot convert " + code
						+ " to " + type.getSimpleName() + " by code value.", ex);
			}
		}
	}

	@Override
	public E getNullableResult(CallableStatement cs, int columnIndex)
			throws SQLException {
		int code = cs.getInt(columnIndex);
		if (cs.wasNull()) {
			return null;
		} else {
			try {
				return enums.get(code);
			} catch (Exception ex) {
				throw new IllegalArgumentException("Cannot convert " + code
						+ " to " + type.getSimpleName() + " by code value.", ex);
			}
		}
	}
}

public SqlSessionFactory sqlSessionFactory() {
       
        try {
            List<String> packages = Lists.newArrayList();
            packages.add("com.kaffatech.mocha.common.lang.type");
            //创建对应类型的 TypeHandler
            List<TypeHandler> typeHandlers = TypeHandlerUtils.getTypeHandlerByPackages(packages);
            bean.setTypeHandlers(typeHandlers.toArray(new TypeHandler[typeHandlers.size()]));
            return bean.getObject();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }

使用

枚举类都需要继承自Enumerable 接口

public enum ConsumeTradeStatus implements Enumerable {

    WAIT(0, "待支付"), PROCESSING(2, "支付中"), SUCCESS(3, "支付成功"),FALSE(4,"支付失败"), CLOSE(5, "已失效");

    private Integer code;
    private String description;

    ConsumeTradeStatus(Integer code, String description) {
        this.code = code;
        this.description = description;
    }


    @Override
    public int getCode() {
        return code;
    }

    @Override
    public String getCodeString() {
        return name();
    }

    @Override
    public String getDescription() {
        return description;
    }
}
public interface Enumerable {

	/**
	 * 获取代码
	 * @return
	 */
	int getCode();


	/**
	 * 获取字符型代码(一般情况应为枚举name)
	 * @return
	 */
	String getCodeString();

	/**
	 * 获取描述
	 * @return
	 */
	String getDescription();

}


如果数据库中的字段使用枚举类型,声明为 tinyInt 类型,并在代码生成器那进行属性类型覆盖EntryManualType

 <table tableName="accounting_entry_apply">
            <columnOverride column="entry_manual_type"
                            javaType="com.flashfin.flp.ms.domain.accounting.type.EntryManualType"></columnOverride>
            <columnOverride column="audit_status"
                            javaType="com.flashfin.flp.ms.domain.accounting.type.AuditStatus"></columnOverride>
            <columnOverride column="deleted" javaType="java.lang.Boolean"></columnOverride>
            <columnOverride column="create_time" javaType="java.time.Instant"></columnOverride>
            <columnOverride column="update_time" javaType="java.time.Instant"></columnOverride>
            <columnOverride column="audit_time" javaType="java.time.Instant"></columnOverride>
        </table>

关于这样做的好处

记当前项目中国际化的做法_奋斗的小面包的博客-CSDN博客

扩展

分页原理的实现

常见的分页方式有如下两种

PageHelper.startPage(1, 10);
Page<User> users = (Page<User>) userMapper.selectList("user",null);//也可以是 List<Country> list = countryMapper.selectIf(1);

这种机制的实现原理是在调用PageHelper.start 的时候创建一个Page对象放入threadLocal 中,当mybatis的插件拦截到他时获取threadLocal中的page对象,进行sql的改写,之后讲查询到的结果放入 page 对象之中进行返回,Page是派生自List的,所以这也就是解释了为什么我们使用List 接收或使用Page 进行接收都不会报错的原理。

page 的继承结构

第二种分页方式如下

Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() {
    @Override
    public void doSelect() {
        countryMapper.selectGroupBy();
    }
});

这又是什么机理呢,其实妙处就在于Page对象的提前暴露,当我们在调用调用start 的时候会创建一个page对象,并将该对象放入ThreadLocal当中,并将Page对象返回,这就给我我们机会提前引用,调用select 的时候会执行具体的dao操作,此时就算在plugin 中删除了ThreadLocal的Page的对象,我们外接依旧是有引用的

源码展示

//pagehelper 中
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
        Page<E> page = new Page<E>(pageNum, pageSize, count);
        page.setReasonable(reasonable);
        page.setPageSizeZero(pageSizeZero);
        //当已经执行过orderBy的时候
        Page<E> oldPage = getLocalPage();
        if (oldPage != null && oldPage.isOrderByOnly()) {
            page.setOrderBy(oldPage.getOrderBy());
        }
        setLocalPage(page);
        return page;
    }
//Page 中

 public <E> Page<E> doSelectPage(ISelect select) {
        select.doSelect();
        return (Page<E>) this;
    }

说了这么久,分页插件的大致机理已经了解了,多说无益我们直接实现一下自定义的分页插件吧

实现自定义分页 插件

@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 PageHelper implements Interceptor {
    public static ThreadLocal<Pageable> pageInfos = new ThreadLocal<>();

    public static void startPage(int pageNum,int pageSize) {
        pageInfos.set(PageRequest.of(pageNum,pageSize));
    }

    public static Pageable getPage() {
        return pageInfos.get();
    }

    public static void remove() {
        pageInfos.remove();
    }


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {

            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            SqlSource sqlSource = mappedStatement.getSqlSource();
            BoundSql boundSql;
            if (invocation.getArgs().length == 6) {
                boundSql = (BoundSql) invocation.getArgs()[5];
            } else {
                boundSql = mappedStatement.getBoundSql(invocation.getArgs()[1]);
            }

            Object parameterObject = invocation.getArgs()[1];
            //判读是否需要分页,判断的逻辑就是PageHelper 中是否有page信息
            Pageable page = PageHelper.getPage();
            if (page != null) {
                //查询总的total
                String sql = boundSql.getSql();
                String prefix = "Select count(0) ";
                int from = sql.indexOf("from");
                String countSql = prefix + sql.substring(from, sql.length());
                Executor executor = (Executor) invocation.getTarget();

                ResultMap resultMap = new ResultMap.Builder(mappedStatement.getConfiguration(), mappedStatement.getId() + "count", Integer.class, Collections.EMPTY_LIST, true).build();

                StaticSqlSource countBoundSql = new StaticSqlSource(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings());

                MappedStatement countMappedStatement = new MappedStatement.Builder(mappedStatement.getConfiguration(), mappedStatement.getId() + "count", countBoundSql, mappedStatement.getSqlCommandType())
                        .cache(mappedStatement.getCache())
                        .databaseId(mappedStatement.getDatabaseId())
                        .fetchSize(mappedStatement.getFetchSize())
                        .statementType(mappedStatement.getStatementType())
                        .resultMaps(Arrays.asList(resultMap)).build();

                List<Object> query = executor.query(countMappedStatement,parameterObject,(RowBounds)invocation.getArgs()[2],(ResultHandler)invocation.getArgs()[3]);
                int total = (int) query.get(0);
                //分页插叙
                int pageSize = page.getPageSize();
                int pageNumber = page.getPageNumber();

                String selectSql = boundSql.getSql();
                selectSql = selectSql + " " + "limit " + (pageNumber - 1) * pageSize + "," + pageSize;

                BoundSql selectBoundSql = new BoundSql(mappedStatement.getConfiguration(), selectSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
                //进行查询操作
                Object proceed = executor.query(mappedStatement,parameterObject,(RowBounds)invocation.getArgs()[2],(ResultHandler)invocation.getArgs()[3],(CacheKey) invocation.getArgs()[4],selectBoundSql);
                Page ret = new Page(pageNumber, pageSize);
                ret.setTotal(total);
                ret.addAll((Collection) proceed);
                return ret;
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            //清除分页信息
            PageHelper.remove();
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
@Configuration
public class PageHelperConfig implements InitializingBean {
    @Autowired
    private SqlSessionFactory sqlSessionFactory;


    public void afterPropertiesSet() throws Exception {
        PageHelper interceptor = new PageHelper();
        interceptor.setProperties(null);
        sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
    }
}

ok ok ok 目前先写到这里,本文档持续更新ing

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/566513.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OTA升级技术概览

随着物联网技术的不断发展&#xff0c;越来越多的设备和系统需要进行远程升级以保持其安全性和功能性。OTA&#xff08;Over-the-Air&#xff09;升级技术是一种通过无线网络远程升级固件或软件的方法&#xff0c;已经成为现代工业、智能家居、汽车等领域中广泛应用的技术。本文…

欧拉公式——最令人着迷的公式之一

欧拉公式是数学里最令人着迷的公式之一&#xff0c;它将数学里最重要的几个常数联系到了一起&#xff1a;两个超越数&#xff1a;自然对数的底e&#xff0c;圆周率π&#xff1b;两个单位&#xff1a;虚数单位i和自然数的单位1&#xff0c;以及数学里常见的0。 ​而且它对数学领…

generate 和 summary 配合——解析 bingchat 逻辑

generate 和 summary 配合——解析 bingchat 逻辑 new bing 微软作为 openai 公司背后的大股东&#xff0c;多年投入一朝开花结果&#xff0c;当然要把 ChatGPT 技术融入到自己的核心产品中&#xff0c;提升整体生产力。微软的第一个措施&#xff0c;就是在必应搜索引擎 bing…

chatgpt赋能Python-python_span_抓取

介绍 随着互联网的不断发展&#xff0c;SEO&#xff08;搜索引擎优化&#xff09;已成为所有网站主人必须面对的问题。在SEO中&#xff0c;抓取是一个非常重要的环节&#xff0c;也是一个关键性的步骤&#xff0c;它直接影响到网站的排名。 在Python编程中&#xff0c;有很多…

Mac上安装多个版本的MySQL

文章目录 准备工作先确定自己机器是多少位的下载包 具体步骤1. 先安装低版本的MySQL2. 清理完后&#xff0c;再安装高版本的MySQL3. 将低版本的文件夹移回 /usr/local4. 切换版本5. 验证 扩展清理命令其他信息 准备工作 先确定自己机器是多少位的 uname -a输出X86_64&#xf…

【Error】Python3.7 No module named ‘_sqlite3‘ 解决方案

场景&#xff1a;docker容器运行keybert时出现错误 No module named ‘_sqlite3‘&#xff0c;是容器环境没有sqlite的库&#xff0c;如下图所示&#xff1a; 本机是能够正常导入sqlite3的&#xff0c;虚拟环境conda下也有该库。 python3.8版本的不可用于python3.7中&#xff0…

【LeetCode】169. 多数元素

169. 多数元素&#xff08;简单&#xff09; 方法一&#xff1a;sort排序&#xff0c;时间复杂度为O(nlogn) 思路 我自己的写法用了最简单的方法&#xff0c;首先使用 sort() 对数组元素按照从小到大进行排序&#xff0c;然后依次遍历每个元素&#xff0c;如果该元素的出现次…

CompletableFuture 异步编排如何使用?

概述&#xff1a; 在做提交订单功能时&#xff0c;我们需要处理的事务很多&#xff0c;如&#xff1a;修改库存、计算优惠促销信息、会员积分加减、线上支付、金额计算、生成产品订单、生成财务信息、删除购物车等等。如果这些功能全部串行化处理&#xff0c;会发费很长的时间…

【盘点】界面控件DevExpress WPF的几大应用程序主题

DevExpress WPF控件包含了50个应用程序主题和40个调色板&#xff0c;用户可以在发布应用程序是指定主题&#xff0c;或允许最终用户动态修改WPF应用程序的外观和样式&#xff0c;其中主题带有调色板&#xff0c;可以进一步个性化您的UI&#xff01; PS&#xff1a;DevExpress …

oracle WAITED TOO LONG FOR A ROW CACHE ENQUEUE LOCK问题分析

服务概述 财务系统出现业务卡顿&#xff0c;数据库实例2遇到>>> WAITED TOO LONG FOR A ROW CACHE ENQUEUE LOCK! <<<错误导致业务HANG住。 对此HANG的原因进行分析&#xff1a; 故障发生时&#xff0c;未有主机监控数据&#xff0c;从可以获取的数据库A…

c++ 11标准模板(STL) std::map(三)

定义于头文件<map> template< class Key, class T, class Compare std::less<Key>, class Allocator std::allocator<std::pair<const Key, T> > > class map;(1)namespace pmr { template <class Key, class T, clas…

使用Linkage Mapper工进行物种分布建模的步骤详解(含实际案例分析)

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: Linkage Mapper解密数字世界链接 文章目录 引言:一、介绍二、数据准备2.1 物种分布数据获取2.2 环境变量数据获取2.3 数据预处理

【拼多多API 开发系列】百亿补贴商品详情接口,代码封装

为了进行电商平台 PDD 的API开发&#xff0c;首先我们需要做下面几件事情。 1&#xff09;开发者注册一个账号 2&#xff09;然后为每个 PDD 应用注册一个应用程序键&#xff08;App Key) 。 3&#xff09;下载 PDD API的SDK并掌握基本的API基础知识和调用 4&#xff09;利用SD…

CSS布局:浮动与绝对定位的异同点

CSS布局&#xff1a;浮动与绝对定位的异同点_cherry_vincent的博客-CSDN博客 浮动 ( float ) 和绝对定位 ( position:absolute ) 相同点&#xff1a; &#xff08;1&#xff09;都是漂起来( 离开原来的位置 ) &#xff08;2&#xff09;并且都不占着原来的位置 &#xff08;3…

Flutter 笔记 | Flutter 布局组件

布局类组件都会包含一个或多个子组件&#xff0c;布局类组件都是直接或间接继承SingleChildRenderObjectWidget 和MultiChildRenderObjectWidget的Widget&#xff0c;它们一般都会有一个child或children属性用于接收子 Widget。 不同的布局类组件对子组件排列&#xff08;layo…

企业级WordPress开发 – 创建企业级网站的优秀提示

目录 “企业级”是什么意思&#xff1f; 使用WordPress创建企业级网站有什么好处&#xff1f; 使用 WordPress 进行企业开发的主要好处 WordPress 可扩展、灵活且价格合理 WordPress 提供响应式 Web 开发 WordPress 提供了巨大的可扩展性 不断更新使 WordPress 万无一…

JAVA-创建PDF文档

目录 一、前期准备 1、中文字体文件 2、maven依赖 二、创建PDF文档方法 三、通过可填充PDF模板将业务参数进行填充 1、 设置可填充的PDF表单 2、代码开干&#xff0c;代码填充可编辑PDF并另存文件 一、前期准备 1、中文字体文件 本演示使用的是iText 7版本&#xff0c…

Jeddak-DPSQL 首次开源!基于差分隐私的 SQL 代理保护能力

动手点关注 干货不迷路 ‍ ‍1. 背景 火山引擎对于用户敏感数据尤为重视&#xff0c;在火山引擎提供的数据分析产品中&#xff0c;广泛采用差分隐私技术对用户敏感信息进行保护。此类数据产品通常构建于 ClickHouse 等数据引擎之上&#xff0c;以 SQL 查询方式来执行计算逻辑&a…

【计算机网络复习】第六章 关系数据理论 1

关系模式的设计 按照一定的原则从数量众多而又相互关联的数据中&#xff0c;构造出一组既能较好地反映现实世界&#xff0c;而又有良好的操作性能的关系模式 ●冗余度高 ●修改困难 ●插入问题 ●删除问题 ★产生问题的原因 属性间约束关系&#xff08;即数据间的依赖关系&…

【JavaSE】Java基础语法(十):构造方法

文章目录 ⛄1. 构造方法的格式和执行时机⛄2. 构造方法的作用⛄3. 构造方法的特点⛄4. 构造方法的注意事项⛄5. 构造方法为什么不能被重写 在面向对象编程的思想中&#xff0c;构造方法&#xff08;Constructor&#xff09;是一个特殊的函数&#xff0c;用于创建和初始化类的对…