惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
Simon Willison's Weblog
Simon Willison's Weblog
Microsoft Security Blog
Microsoft Security Blog
Y
Y Combinator Blog
The GitHub Blog
The GitHub Blog
Engineering at Meta
Engineering at Meta
F
Fortinet All Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
A
About on SuperTechFans
Last Week in AI
Last Week in AI
月光博客
月光博客
有赞技术团队
有赞技术团队
P
Proofpoint News Feed
MyScale Blog
MyScale Blog
Martin Fowler
Martin Fowler
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
Check Point Blog
U
Unit 42
The Register - Security
The Register - Security
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hugging Face - Blog
Hugging Face - Blog
阮一峰的网络日志
阮一峰的网络日志
V
Visual Studio Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
D
DataBreaches.Net
WordPress大学
WordPress大学
aimingoo的专栏
aimingoo的专栏
H
Hacker News: Front Page
Recent Announcements
Recent Announcements
C
CXSECURITY Database RSS Feed - CXSecurity.com
Latest news
Latest news
小众软件
小众软件
P
Palo Alto Networks Blog
PCI Perspectives
PCI Perspectives
Security Latest
Security Latest
S
Secure Thoughts
Scott Helme
Scott Helme
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threat Research - Cisco Blogs
P
Proofpoint News Feed
M
MIT News - Artificial intelligence
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Google DeepMind News
Google DeepMind News
Recorded Future
Recorded Future
O
OpenAI News
S
Securelist
云风的 BLOG
云风的 BLOG
H
Help Net Security
T
Troy Hunt's Blog

博客园 - 干炸小黄鱼

timex 处理时间戳 gorm-gen go 雪花算法 golang每日一库--协程池库ants golang每日一库--json解析库gjson python高级编程-asyncio python高级编程-condition python高级编程-event python装饰器-自动重试 EAP系统 go实现实现 SECS/GEM 协议 设备通信协议 SECS go项目使用Jenkins进行CICD go操作ES mongo db聚合查询 go如何使用mongodb Apache ShardingSphere paxos and raft (分布式一致性算法) go使用zookeeper分布式锁以及和redis差异 go使用 seata 示例 Alibaba 分布式事务 Seata go中使用saga go中使用TCC示例 分布式事务TCC 熔断器 Hystrix OR Sentinel k8s下部署consul and etcd Consul OR Etcd 【力扣hot100】双指针-盛水最多的容器 【力扣hot100】滑动窗口-最小覆盖子串 shell脚本合集 分布式id生成器 Python PB级检索系统架构设计 rancher 在三台机器搭建k8s集群 python ssh clinet 数据库排序Null值字段靠后/靠前 常规web项目 docker-compose 例子 手搓一个验证码 使用itertools 中的groupby 对字典数组进行分组后排序 使用开源库 geoip2 获取某ip的经纬度地理信息 python中 apscheduler.schedulers.blocking.BlockingScheduler 定时执行任务 简单的python web项目的docker-compose.yml 示例 python和sliver交互 golang sliver二次开发自定义命令(格式乱后面再调) pydantic做参数校验 基于rancher部署k8s 地理位置相关基础数据 flask migrate时报错 Can't locate revision identified by '3d80e4c025df'
springboot通用CURD
干炸小黄鱼 · 2025-05-07 · via 博客园 - 干炸小黄鱼

最近小朋友开始找工作了, 给他弄了一套通用springboot下的curd, 支持分页

在此记录下;

BaseServiceImpl.java

package data.service.impl;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import common.mybatis.HbblPage;
import data.errors.GlobeError;
import data.model.BaseModel;
import data.service.BaseService;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.ParameterizedType;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Transactional(readOnly = true)
public class BaseServiceImpl<T extends BaseModel> implements BaseService<T> {
    private final Class<T> clazzOfT = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];

    @Autowired
    private BaseMapper<T> mapper;

    @Autowired
    private Snowflake snowflake;

    @Override
    public T selectOne(Long id) {
        return mapper.selectById(id);
    }

    @Override
    @Transactional
    public void insert(T model) {
        val id = snowflake.nextId();
        model.setId(id);
        mapper.insert(model);
    }

    @Override
    @Transactional
    public void update(Long id, T model) {
        model.setId(id);
        mapper.updateById(model);
    }

    @Override
    @Transactional
    public void delete(Long id) {
        try {
            val model = clazzOfT.getConstructor().newInstance();
            model.setId(id);
            model.setDeleted(true);
            mapper.updateById(model);
        } catch (Exception e) {
            log.error(e.getLocalizedMessage(), e);
        }
    }

    @Override
    public List<T> selectListByMap(Map<String, Object> map) {
        val queryWrapper = mapToQueryWrapper(map, false);
        return mapper.selectList(queryWrapper);
    }

    @Override
    public Page<T> selectPageByMap(Map<String, Object> map, Pageable pageable) {
        val p = new HbblPage<T>(pageable);
        val queryWrapper = mapToQueryWrapper(map, false);
        val page = mapper.selectPage(p, queryWrapper);
        return page.toPageableResponse();
    }

    @Override
    public Page<T> searchPageByMap(Map<String, Object> map, Pageable pageable) {
        val p = new HbblPage<T>(pageable);
        val queryWrapper = mapToQueryWrapper(map, true);
        val page = mapper.selectPage(p, queryWrapper);
        return page.toPageableResponse();
    }

    @Override
    public QueryWrapper<T> mapToQueryWrapper(Map<String, Object> map, boolean or) {
        val queryWrapper = new QueryWrapper<T>();
        queryWrapper.eq("deleted", false);
        Map<String, Object> query = map.entrySet().stream()
                .filter(it -> it.getValue() != null
                        && !it.getValue().toString().isEmpty()
                        && !it.getKey().equals("page")
                        && !it.getKey().equals("size")
                        && !it.getKey().equals("sort"))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        if (!query.isEmpty()) {
            if (or) {
                queryWrapper.and(wrapper -> wrappers(wrapper, query, true));
            } else {
                wrappers(queryWrapper, query, false);
            }
        }
        return queryWrapper;
    }

    private void wrappers(QueryWrapper<T> queryWrapper, Map<String, Object> map, boolean or) {
        map.keySet().forEach(key -> {
            // 防止sql注入
            if (!key.matches("^(?!_)(?!.*?_$)[a-zA-Z_]+$")) {
//                throw GlobeError.SQL_ERROR.buildException();
                throw new RuntimeException(GlobeError.SQL_ERROR.getMsg());
            }
            val property = key.split("_");
            if (property.length <= 1) {
                queryWrapper.eq(toLowerUnderscore(property[0]), map.get(key));
            } else if (map.get(key) == null) {
                return;
            } else if ("eq".equals(property[1])) {
                queryWrapper.eq(toLowerUnderscore(property[0]), map.get(key));
            } else if ("li".equals(property[1])) {
                queryWrapper.like(toLowerUnderscore(property[0]), "%" + map.get(key) + "%");
            } else if ("nl".equals(property[1])) {
                queryWrapper.notLike(toLowerUnderscore(property[0]), "%" + map.get(key) + "%");
            } else if ("start".equals(property[1])) {
                queryWrapper.like(toLowerUnderscore(property[0]), map.get(key) + "%");
            } else if ("in".equals(property[1])) {
                queryWrapper.in(toLowerUnderscore(property[0]), map.get(key));
            } else if ("ni".equals(property[1])) {
                queryWrapper.notIn(toLowerUnderscore(property[0]), map.get(key));
            } else if ("gt".equals(property[1])) {
                queryWrapper.gt(toLowerUnderscore(property[0]), map.get(key));
            } else if ("ge".equals(property[1])) {
                queryWrapper.ge(toLowerUnderscore(property[0]), map.get(key));
            } else if ("lt".equals(property[1])) {
                queryWrapper.lt(toLowerUnderscore(property[0]), map.get(key));
            } else if ("le".equals(property[1])) {
                queryWrapper.le(toLowerUnderscore(property[0]), map.get(key));
            } else if ("desc".equals(property[1])) {
                queryWrapper.orderByDesc(toLowerUnderscore(property[0]));
            } else if ("asc".equals(property[1])) {
                queryWrapper.orderByAsc(toLowerUnderscore(property[0]));
            } else {
                queryWrapper.eq(toLowerUnderscore(property[0]), map.get(key));
            }
            if (or) {
                queryWrapper.or();
            }
        });
    }

    private String toLowerUnderscore(String source) {
        return StrUtil.toUnderlineCase(source);
    }
}

BaseService.java

package data.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import data.model.BaseModel;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.List;
import java.util.Map;

public interface BaseService<T extends BaseModel> {
    T selectOne(Long id);
    void insert(T model);
    void update(Long id, T model);
    void delete(Long id);

    List<T> selectListByMap(Map<String, Object> map);
    Page<T> selectPageByMap(Map<String, Object> map, Pageable pageable);

    /**
     * 搜索,现在采用‘或’查询,此方法后续可以接入搜索引擎
     */
    Page<T> searchPageByMap(Map<String, Object> map, Pageable pageable);

    QueryWrapper<T> mapToQueryWrapper(Map<String, Object> map, boolean or);
}

BaseModel.java

package data.model;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.Getter;
import lombok.Setter;

import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;

@Getter
@Setter
abstract public class BaseModel implements Serializable {
    @Serial
    private static final long serialVersionUID = -7472715591255438006L;

    protected Long id;

    @JsonSerialize(using = LocalDateTimeSerializer.class)
    protected LocalDateTime createTime;

    @JsonSerialize(using = LocalDateTimeSerializer.class)
    protected LocalDateTime updateTime;

    protected Boolean deleted = false;
}

mapper定义

package data.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hbblkj.pis.data.model.Demo;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface DemoMapper extends BaseMapper<Demo> {
}


通用分页 HbblPage.java;

package common.mybatis;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hbblkj.pis.common.spring.PageableRequest;
import com.hbblkj.pis.common.spring.PageableResponse;
import lombok.val;
import org.springframework.data.domain.Pageable;

public class HbblPage<T> extends Page<T> {
    public HbblPage() {
    }

    public HbblPage(Pageable pageable) {
        super(pageable.getPageNumber() + 1, pageable.getPageSize());
        if (pageable.getSort().isSorted()) {
            val orders = pageable.getSort().stream().map((it) -> {
                val column = StrUtil.toUnderlineCase(it.getProperty());
                val item = new OrderItem();
                item.setColumn(column);
                item.setAsc(it.isAscending());
                return item;
            }).toList();
            this.setOrders(orders);
        }
    }

    public PageableResponse<T> toPageableResponse() {
        PageableRequest pageable = new PageableRequest(Long.valueOf(this.getCurrent() - 1L).intValue(), Long.valueOf(this.getSize()).intValue());
        return new PageableResponse<>(this.getRecords(), pageable, this.getTotal());
    }
}

公用spring类

package common.spring;

import org.springframework.data.domain.Sort;

import java.lang.reflect.Field;
import java.util.*;

public class FieldsSort extends Sort {

    private static final long serialVersionUID = 4240779646538412666L;

    private static final FieldOrder DEFAULT_FIX_ORDER = new FieldOrder("_id");


    public FieldsSort() {
        super(Collections.singletonList(DEFAULT_FIX_ORDER));
        setOrders();
    }

    public FieldsSort(FieldOrder... orders) {
        super(Collections.singletonList(DEFAULT_FIX_ORDER));
        setOrders(orders);
    }

    public FieldsSort(List<FieldOrder> orders) {
        super(Collections.singletonList(DEFAULT_FIX_ORDER));
        setOrders(orders);
    }

    public FieldsSort(String... properties) {
        this(DEFAULT_DIRECTION, properties);
    }

    public FieldsSort(Direction direction, String... properties) {
        this(direction, properties == null ? new ArrayList<>() : Arrays.asList(properties));
    }

    public FieldsSort(Direction direction, List<String> properties) {
        super(Collections.singletonList(DEFAULT_FIX_ORDER));
        if (properties == null || properties.isEmpty()) {
            setOrders();
            return;
        }

        List<FieldOrder> orders = new ArrayList<>();
        for (String property : properties) {
            orders.add(new FieldOrder(direction, property));
        }
        setOrders(orders);
    }

    @SuppressWarnings("unchecked")
    public FieldsSort(Sort sort) {
        this();
        if (sort == null) return;
        try {
            Field field = Sort.class.getDeclaredField("orders");
            field.setAccessible(true);
            List<Order> orders = (List<Order>) field.get(sort);
            List<FieldOrder> fieldOrders = new LinkedList<>();
            if (orders != null && orders.size() > 0) {
                for (Order order : orders) {
                    if (order != null)
                        fieldOrders.add(new FieldOrder(order));
                }
            }
            setOrders(fieldOrders);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new IllegalArgumentException(e);
        }
    }

    private void setOrders(List<FieldOrder> orders) {
        try {
            Field field = Sort.class.getDeclaredField("orders");
            field.setAccessible(true);
            field.set(this, orders);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new IllegalArgumentException(e);
        }
    }

    private void setOrders(FieldOrder... orders) {
        if (orders == null) {
            orders = new FieldOrder[0];
        }
        setOrders(Arrays.asList(orders));
    }

    public static class FieldOrder extends Order {

        private static final long serialVersionUID = -4307899744334924872L;

        public FieldOrder() {
            this("_id");
        }

        public FieldOrder(String property) {
            super(null, "_id");
            setProperties(property);
        }

        public FieldOrder(Direction direction, String property) {
            super(direction, "_id");
            setProperties(property);
        }

        public FieldOrder(Direction direction, String property, NullHandling nullHandlingHint) {
            super(direction, "_id", nullHandlingHint);
            setProperties(property);
        }

        public FieldOrder(Order order) {
            this();
            if (order == null) return;
            String[] properties = {"direction", "property", "ignoreCase", "nullHandling"};
            try {
                for (String property : properties) {
                    Field field = Order.class.getDeclaredField(property);
                    field.setAccessible(true);
                    field.set(this, field.get(order));
                }
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new IllegalArgumentException(e);
            }
        }


        private void setProperties(String property) {
            Field field;
            try {
                field = Order.class.getDeclaredField("property");
                field.setAccessible(true);
                field.set(this, property);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new IllegalArgumentException(e);
            }
        }

    }

}

package common.spring;

import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import java.io.Serial;

public class PageableRequest extends PageRequest {
    @Serial
    private static final long serialVersionUID = 6516764632433368424L;

    private static final int PAGE_SIZE = 10000;

    public static PageableRequest one() {
        return new PageableRequest(0, 1);
    }

    public static PageableRequest max() {
        return new PageableRequest(0, PAGE_SIZE - 1);
    }

    public PageableRequest() {
        this(0, 10);
    }

    public PageableRequest(int page, int size) {
        this(page, size < 1 ? 10 : size, new FieldsSort());
    }

    public PageableRequest(int page, int size, FieldsSort.Direction direction, String... properties) {
        this(page, size, new FieldsSort(direction, properties));
    }

    public PageableRequest(int page, int size, FieldsSort sort) {
        super(page, size, sort);
        verifyPageSize(size);
    }

    public PageableRequest(Pageable pageable) {
        this(pageable.getPageNumber(), pageable.getPageSize(), new FieldsSort(pageable.getSort()));
    }

    private void verifyPageSize(int size) {
        if (size > PAGE_SIZE) {
            throw new IllegalArgumentException("分页参数大小请小于10000");
        }
    }
}

package common.spring;

import org.springframework.core.MethodParameter;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;

public class PageableRequestHandlerMethodArgumentResolver extends PageableHandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return Pageable.class.equals(parameter.getParameterType());
    }

    @Override
    public PageableRequest resolveArgument(
            MethodParameter methodParameter,
            ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest,
            WebDataBinderFactory binderFactory) {
        Pageable pageable = super.resolveArgument(methodParameter, mavContainer, webRequest, binderFactory);
        return new PageableRequest(pageable);
    }
}

package common.spring;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class PageableResponse<T> extends PageImpl<T> {
    private static final long serialVersionUID = -6529648340639543186L;

    public PageableResponse() {
        super(new ArrayList<>());
    }

    public PageableResponse(List<T> content, Pageable pageable, long total) {
        super(content, pageable, total);
    }

    public PageableResponse(List<T> content) {
        super(content);
    }

    public static <T> Page<T> empty() {
        return empty(Pageable.unpaged());
    }

    public static <T> Page<T> empty(Pageable pageable) {
        return new PageableResponse<>(Collections.emptyList(), pageable, 0L);
    }

    public static <T> Page<T> fromPage(Page<T> page) {
        return new PageableResponse<>(page.getContent(), page.getPageable(), page.getTotalElements());
    }
}


错误定义

package data.errors;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum GlobeError {
    FIND_ERROR("0", "查找失败"),
    LOAD_ERROR("0", "获取单个失败"),
    SAVE_ERROR("0", "保存失败"),
    REMOVE_ERROR("0", "删除失败"),
    SQL_ERROR("0", "sql语句错误,可能产生sql注入"),
    ;

    final private String code;
    final private String msg;
}