springboot通用CURD

最近小朋友开始找工作了, 给他弄了一套通用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;
}

posted @ 2025-05-07 17:30  干炸小黄鱼  阅读(22)  评论(0)    收藏  举报