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

推荐订阅源

博客园 - Franky
J
Java Code Geeks
腾讯CDC
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Jina AI
Jina AI
博客园 - 司徒正美
Stack Overflow Blog
Stack Overflow Blog
美团技术团队
L
LangChain Blog
WordPress大学
WordPress大学
A
About on SuperTechFans
Martin Fowler
Martin Fowler
月光博客
月光博客
Y
Y Combinator Blog
U
Unit 42
D
Docker
Recent Announcements
Recent Announcements
Hugging Face - Blog
Hugging Face - Blog
B
Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
G
Google Developers Blog
Last Week in AI
Last Week in AI
T
The Blog of Author Tim Ferriss
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

博客园 - 干炸小黄鱼

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脚本合集
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;
}