1. 程式人生 > 實用技巧 >基礎框架一:“零程式碼”實現單表增刪改查、唯一性校驗、列舉型別翻譯

基礎框架一:“零程式碼”實現單表增刪改查、唯一性校驗、列舉型別翻譯

概述

在日常開發中,有很多模組如基礎資訊都是單表的增刪改查,如果每個單表都要自己編寫增刪改查程式碼,無疑增加了很多不必要的開發成本。本文通過對增刪改查進行封裝,通過EasyCode生成模組程式碼,無需編寫任何程式碼,即可實現單表增刪改查、唯一性校驗、資料字典翻譯。

模組介紹

本文主要分為以下幾個模組

  1. Controller :封裝對外暴露的增刪改查restful介面
  2. Service ,封裝具體的增刪改查邏輯,包括唯一性校驗、增刪改操作前後的個性操作
  3. Mapper ,繼承Mybatisplus的BaseMapper介面
  4. DTO ,返回給前端的實體,包含欄位校驗、列舉翻譯等

在介紹之前先看下一個完整的案例,熟悉下使用方式(下面案例程式碼均可由EasyCode生成)

以使用者模組為例:

UserController
注:對外暴露使用者的增刪改查介面

@RestController
@RequestMapping("user")
public class UserController extends YaoBaseController<UserParam, UserEntity, UserDTO> {

    @Autowired
    private UserService userService;
    
    @Override
    public YaoBaseService<UserParam, UserEntity> getService() {
        return userService;
    }

    @Override
    public UserDTO getDTO() {
        return new UserDTO();
    }

    @Override
    public UserEntity getEntity() {
        return new UserEntity();
    }
}

UserDTO
注:使用者欄位校驗,列舉翻譯( @Formatter)等

/**
 * 使用者DTO
 * @author YAO
 * @since 2020-06-13
 */
public class UserDTO extends YaoBaseDTO implements Serializable {
    /**
     * 使用者名稱
     */
    @NotNull
    private String username;

    /**
     * 別名即真實姓名
     */
    private String nickname;
    /**
     * 是否有效,將 1:翻譯成有效,0:翻譯成無效
     */
    @Formatter(dictCode = "enabled", targetField = "enabledStr")
    private Integer enabled;

    private String enabledStr;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }
    public Integer getEnabled() {
        return enabled;
    }

    public void setEnabled(Integer enabled) {
        this.enabled = enabled;
    }
        public String getEnabledStr() {
        return enabledStr;
    }

    public void setEnabledStr(String enabledStr) {
        this.enabledStr = enabledStr;
    }
}

UserParam
注:只要username非空,自動查詢指定username對應的使用者,無需編寫其他程式碼

/**
 * 使用者請求引數
 * @author Yao
 * @since 2020-07-02
 */
public class UserParam extends PageParam {

    private String username;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

UserService

/**
 * 使用者service
 * @author YAO
 * @since 2020-06-13
 */
public interface UserService extends YaoBaseService<UserParam, UserEntity> {
}

UserServiceImpl

/**
 * 使用者service的實現類
 *
 * @author YAO
 * @since 2020-06-13
 */
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    
    @Override
    public YaoBaseMapper<UserEntity> getMapper() {
        return userMapper;
    }
}

UserMapper

/**
 * 使用者資料庫操作
 * @author YAO
 * @since 2020-06-13
 */
@Mapper
public interface UserMapper extends YaoBaseMapper<UserEntity> {

}

UserEntity

/**
 * 使用者實體
 * @author YAO
 * @since 2020-06-13
 */
@TableName("user")
public class UserEntity extends YaoBaseEntity implements Serializable {

    /**
     * 使用者名稱
     */
    private String username;
    /**
     * 別名即真實姓名
     */
    private String nickname;
    
    /**
     * 是否有效
     */
    private Integer enabled;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public Integer getEnabled() {
        return enabled;
    }

    public void setEnabled(Integer enabled) {
        this.enabled = enabled;
    }
}

下面是各模組上層的程式碼封裝:

1、Controller

自定義YaoBaseController,使用方式繼承該類
自帶增刪改查restful介面,其中查詢會對DTO中被 @Formatter 註解的欄位進行翻譯,如:“0,1” 翻譯成“男、女",支援 JSR-303對欄位進行校驗

package com.jing.yao.component.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jing.yao.annotation.ExcelFileName;
import com.jing.yao.annotation.Permission;
import com.jing.yao.bean.ResponseBean;
import com.jing.yao.component.dto.YaoBaseDTO;
import com.jing.yao.component.entity.YaoBaseEntity;
import com.jing.yao.component.params.page.PageParam;
import com.jing.yao.component.service.YaoBaseService;
import com.jing.yao.constant.ErrorCode;
import com.jing.yao.excel.ExcelUtil;
import com.jing.yao.exception.BusinessExceptionBuilder;
import com.jing.yao.holder.UserContentHolder;
import com.jing.yao.utils.YaoBeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static com.jing.yao.bean.ResponseBean.builder;
import static com.jing.yao.component.constants.ResponseCode.ENTITY_NOT_FOUND;
import static com.jing.yao.component.constants.ResponseCode.FAIL_CODE;


/**
 * 基礎controller
 *
 * @author Yao
 * @since 2020-06-13
 */
public abstract class YaoBaseController<P extends PageParam, T extends YaoBaseEntity, D extends YaoBaseDTO> {

    /**
     * 記錄日誌
     */
    protected Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 獲取實際服務類
     *
     * @return
     */
    public abstract YaoBaseService<P, T> getService();

    /**
     * 獲取當前Controller資料庫實體Entity
     *
     * @return
     */
    public abstract T getEntity();

    /**
     * 獲取當前Controller資料傳輸DTO
     *
     * @return
     */
    public abstract D getDTO();

    /**
     * 分頁模板
     *
     * @param param
     * @return
     */
    @GetMapping("/pageList")
    public ResponseBean pageList(P param) {
        IPage iPage = getService().page(param);
        List<T> records = iPage.getRecords();
        List<D> list = records.stream().map(entity -> {
            D dto = getDTO();
            YaoBeanUtils.copyAndFormatter(entity, dto);
            return dto;
        }).collect(Collectors.toList());

        Page<D> pageDto = new Page<>();
        pageDto.setCurrent(iPage.getCurrent());
        pageDto.setRecords(list);
        pageDto.setPages(iPage.getPages());
        pageDto.setTotal(iPage.getTotal());
        pageDto.setSize(iPage.getSize());
        return successBean(pageDto);
    }


    /**
     * 根據ID查詢相關記錄
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public ResponseBean selectById(@PathVariable("id") Long id) {
        logger.info("{}根據ID查詢服務開始,id為:{}", this.getClass().getSimpleName(), id);
        T result = getService().selectOneById(id);
        if (result == null) {
            throw BusinessExceptionBuilder.build(ErrorCode.DETAIL_ID_NOT_EXIST);
        }
        ResponseBean responseBean = successBean(result);
        logger.info("{}根據ID查詢結束,結果:{}", this.getClass().getSimpleName(), toJSONString(responseBean));
        return responseBean;
    }

    /**
     * 轉換為JSON字串
     *
     * @param object
     * @return
     */
    private String toJSONString(Object object) {
        ObjectMapper mapper = new ObjectMapper();
        String result = "";
        try {
            result = mapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
        }
        return result;
    }

    /**
     * 插入
     *
     * @param dto
     * @return
     * @throws Exception
     */
    @PostMapping
    @Permission(value = "add")
    public ResponseBean insert(@Valid @RequestBody D dto) {
        logger.info("{}新增服務開始,引數:{}", this.getClass().getSimpleName(), toJSONString(dto));

        ResponseBean responseBean = successBean();
        boolean insert;
        T entity = getEntity();
        try {
            //dto轉為資料庫實體
            BeanUtils.copyProperties(dto, entity);
            //新增前校驗
            insert = getService().insert(entity);
        } catch (DuplicateKeyException exception) {
            exception.printStackTrace();
            return builder().code(FAIL_CODE).build();
        }

        //當插入記錄小於1時,表示插入失敗
        if (!insert) {
            responseBean = builder().code(FAIL_CODE).content(insert).build();
        }
        logger.info("{}新增服務結束,結果:{}", this.getClass().getSimpleName(), toJSONString(responseBean));
        return responseBean;
    }


    /**
     * 根據ID修改對應記錄
     *
     * @param dto
     * @return
     * @throws Exception
     */
    @PutMapping
    @Permission(value = "edit")
    public ResponseBean updateById(@Valid @RequestBody D dto) {
        String username = UserContentHolder.getContext().getUsername();
        logger.info("{}更新服務開始,更新人:{},引數:{}", this.getClass().getSimpleName(), username, toJSONString(dto));
        T entity = getEntity();
        //dto轉換entity
        BeanUtils.copyProperties(dto, entity);
        ResponseBean responseBean = null;
        boolean count;

        try {
            count = getService().update(entity);
        } catch (DuplicateKeyException exception) {
            return builder().code(FAIL_CODE).build();
        }

        //當更新記錄小於1時,表示更新失敗
        if (!count) {
            responseBean = builder().code(FAIL_CODE).content(count).build();
        }
        logger.info("{}更新服務結束,結果:{}", this.getClass().getSimpleName(), toJSONString(responseBean));

        return successBean(count);
    }


    /**
     * 根據ID刪除指定記錄,這裡被刪除的記錄會進入刪除記錄表
     *
     * @param id
     * @return
     */
    @DeleteMapping("/{id}")
    @Permission(value = "del")
    public ResponseBean deleteById(@PathVariable("id") Long id) {
        logger.info("{}刪除服務開始,引數ID:{}", this.getClass().getSimpleName(), id);
        boolean deleteCount = getService().removeById(id);
        ResponseBean responseBean = successBean(deleteCount);
        //當刪除記錄小於1時,表示更新失敗
        if (!deleteCount) {
            responseBean = builder().code(FAIL_CODE).build();
        }

        logger.info("{}刪除服務結束,結果:{}", this.getClass().getSimpleName(), toJSONString(responseBean));
        return responseBean;
    }
    
    /**
     * 構建成功響應例項
     *
     * @return
     */
    public ResponseBean successBean() {
        return builder().build();
    }

    /**
     * 構建成功響應例項
     *
     * @param data
     * @return
     */
    public ResponseBean successBean(Object data) {
        return builder().content(data).build();
    }
}

2、Service

自定義YaoBaseService,使用方式繼承該類
主要提供以下功能
(1)封裝基礎的增刪改查功能
(2)當查詢有查詢條件時,只需請求對應的Param物件中屬性有值即可,無需寫再寫查詢程式碼
自定義的引數物件繼承 YaoBaseParams,並定義自己的屬性即可。當然,如果想自定義查詢條件只需重寫 com.anji.plus.component.service.YaoBaseService#getWrapper即可

/**
 * 基礎查詢引數
 * @author Yao
 * @since 2020-06-13
 */
public class YaoBaseParams {
}

(3)前置後置處理器 processBeforeOperationprocessAfterOperation。當InsertUpdateDelete 執行前後需要增加些邏輯時,需重寫前置後置處理器。比如:刪除之前想校驗資料是否能刪除、儲存更新後想操作下快取(Redis)等
Demo:

  @Override
    public void processBeforeOperation(BmsApplyManagement entity, BaseOperationEnum operationEnum) throws BusinessException {
        switch (operationEnum) {
            case INSERT:
                break;
            case UPDATE:
                break;
            case DELETE:
                break;
            default:
        }
    }
package com.jing.yao.component.service;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.jing.yao.annotation.UnionUnique;
import com.jing.yao.annotation.Unique;
import com.jing.yao.component.constants.BaseOperationEnum;
import com.jing.yao.component.constants.BmsConstants;
import com.jing.yao.component.dto.Query;
import com.jing.yao.component.dto.QueryEnum;
import com.jing.yao.component.entity.YaoBaseEntity;
import com.jing.yao.component.mapper.YaoBaseMapper;
import com.jing.yao.component.params.page.PageParam;
import com.jing.yao.exception.BusinessException;
import com.jing.yao.exception.BusinessExceptionBuilder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

import static com.jing.yao.component.constants.ResponseCode.ENTITY_NOT_FOUND;

/**
 * 基礎service
 * @author Yao
 * @since 2020-06-13
 */
public interface YaoBaseService<P extends PageParam,T extends YaoBaseEntity> {
    /**
     * 獲取直接操作資料庫介面
     * @return
     */
    YaoBaseMapper<T> getMapper();

    /**
     * 根據id查詢記錄
     * @param id
     * @return
     */
    default T selectOneById(Long id) {
        return getMapper().selectById(id);
    }

    /**
     * 排序
     * @param pageParam
     * @return
     */
    default IPage<T> page(P pageParam) {
        Page<T> page = new Page<>();
        page.setCurrent(pageParam.getPageNumber());
        page.setSize(pageParam.getPageSize());

        //設定排序
        if(StringUtils.equals(BmsConstants.ASC,pageParam.getOrder()) && StringUtils.isNotBlank(pageParam.getOrder())){
            page.addOrder(OrderItem.asc(pageParam.getSort()));
        } else if(StringUtils.equals(BmsConstants.DESC,pageParam.getOrder()) && StringUtils.isNotBlank(pageParam.getOrder())){
            page.addOrder(OrderItem.desc(pageParam.getSort()));
        } else {
            page.addOrder(OrderItem.desc("update_time"));
        }
        TimeZone.setDefault(TimeZone.getTimeZone("GMT+8:00"));
        return getMapper().selectPage(page, getWrapper(pageParam));
    }

    /**
     * 抽象查詢條件
     * @param param 查詢條件
     * @return
     */
    default Wrapper<T> getWrapper(P param) {
        QueryWrapper<T> queryWrapper = new QueryWrapper<>();

        //條件的值
        Field[] fields = param.getClass().getDeclaredFields();

        Arrays.stream(fields).forEach(field -> {
            try {
                boolean flag = false;
                field.setAccessible(true);
                if(field.get(param) instanceof String) {
                    flag = StringUtils.isNoneBlank((String)field.get(param));
                } else {
                    flag = field.get(param) != null;
                }
                //判斷是否是模糊查詢
                if(field.isAnnotationPresent(Query.class) && field.getAnnotation(Query.class).value() == QueryEnum.LIKE) {
                    queryWrapper.like(flag, com.baomidou.mybatisplus.core.toolkit.StringUtils.camelToUnderline(field.getName()), field.get(param));
                } else {
                    queryWrapper.eq(flag, com.baomidou.mybatisplus.core.toolkit.StringUtils.camelToUnderline(field.getName()), field.get(param));
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        });

        return queryWrapper;
    }

    /**
     * 操作前處理
     * @param entity 前端傳遞的物件
     * @param operationEnum 操作型別
     * @throws BusinessException 阻止程式繼續執行或回滾事務
     */
    default void processBeforeOperation(T entity, BaseOperationEnum operationEnum) throws BusinessException{
    }

    /**
     * 操作後續處理
     * @param entity
     * @param operationEnum 操作型別
     * @throws BusinessException  阻止程式繼續執行或回滾事務
     */
    default void processAfterOperation(T entity, BaseOperationEnum operationEnum) throws BusinessException {
    }

    /**
     * 插入資料
     * @param entity
     * @throws BusinessException 業務異常
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    default boolean insert(T entity) throws BusinessException {
        //儲存前處理
        processBeforeOperation(entity, BaseOperationEnum.INSERT);
        checkUniqueField(entity);
        Integer result = getMapper().insert(entity);
        processAfterOperation(entity, BaseOperationEnum.INSERT);
        return result != null && result >= 1;
    }

    /**
     * 校驗唯一
     * @param entity
     */
    default void checkUniqueField(T entity) {
        //獲取所有屬性
        Field[] fields = entity.getClass().getDeclaredFields();

        //判斷單一索引
        for (Field field : fields) {
            if (field.isAnnotationPresent(Unique.class)) {
                Unique unique = field.getDeclaredAnnotation(Unique.class);
                QueryWrapper<T> wrapper = Wrappers.query();
                Integer integer = 0;
                try {
                    Object value = getFieldValue(entity, field);
                    if (entity.getId() != null) {
                        wrapper.ne("id", entity.getId());
                    }
                    wrapper.eq(unique.column(), value);
                    integer = getMapper().selectCount(wrapper);
                } catch (Exception e) {
                    continue;
                }
                if (integer >  0) {
                    throw BusinessExceptionBuilder.build(unique.code());
                }
            }
        }

        //判斷聯合索引
        QueryWrapper<T> unionWrapper = Wrappers.query();
        if (entity.getId() != null) {
            unionWrapper.ne("id", entity.getId());
        }
        Integer integer = 0;
        boolean flag = false;
        for (Field field : fields) {
            if (field.isAnnotationPresent(UnionUnique.class)) {
                UnionUnique unionUnique = field.getDeclaredAnnotation(UnionUnique.class);
                try {
                    Object value = getFieldValue(entity, field);
                    unionWrapper.eq(unionUnique.column(), value);
                    flag = true;
                } catch (Exception e) {
                    continue;
                }
            }
        }

        if (flag) {
            integer = getMapper().selectCount(unionWrapper);
            if (integer >  0) {
                throw BusinessExceptionBuilder.build(entity.getClass().getAnnotation(UnionUnique.class).code());
            }
        }
    }

    /**
     * 獲取屬性值
     * @param entity
     * @param field
     * @return
     * @throws IntrospectionException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    default Object getFieldValue(T entity, Field field) throws IntrospectionException, IllegalAccessException, InvocationTargetException {
        PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), entity.getClass());
        Method readMethod = propertyDescriptor.getReadMethod();
        return readMethod.invoke(entity);
    }

    /**
     * 編輯資料
     * @param entity
     * @throws BusinessException 業務異常
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    default boolean update(T entity) throws BusinessException {
        //編輯前處理
        processBeforeOperation(entity, BaseOperationEnum.UPDATE);
        Integer result = getMapper().updateById(entity);
        processAfterOperation(entity, BaseOperationEnum.UPDATE);
        return result != null && result >= 1;
    }

    /**
     * 根據指定欄位查詢對應的值
     * @param column
     * @param value
     * @return
     */
    default List<T> list(String column, String value) {
        QueryWrapper<T> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(column, value);
        return getMapper().selectList(queryWrapper);
    }


    /**
     * 根據指定條件查詢對應的記錄
     * @param wrapper
     * @return
     */
    default List<T> list(Wrapper<T> wrapper) {
        return getMapper().selectList(wrapper);
    }

    /**
     * 根據ID查詢記錄
     * @param id
     * @return
     */
    default T getById(Serializable id) {
        return getMapper().selectById(id);
    }

    /**
     * 刪除
     * @param id
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    default boolean removeById(Serializable id) {
        T t = getById(id);
        if(t == null) {
            throw BusinessExceptionBuilder.build(ENTITY_NOT_FOUND);
        }
        processBeforeOperation(t,BaseOperationEnum.DELETE);
        boolean result = SqlHelper.retBool(getMapper().deleteById(id));
        processAfterOperation(t,BaseOperationEnum.DELETE);
        return result;
    }

    /**
     * 刪除
     * @param lambdaQueryWrapper
     */
    @Transactional(rollbackFor = Exception.class)
    default void delete(LambdaQueryWrapper<T> lambdaQueryWrapper) {
        getMapper().delete(lambdaQueryWrapper);
    }

    /**
     * 批量刪除
     * @param idList
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    default boolean removeByIds(Collection<? extends Serializable> idList) {
        return SqlHelper.retBool(getMapper().deleteBatchIds(idList));
    }

    /**
     * 查詢所有
     * @return
     */
    default List<T> findAll() {
        return getMapper().selectList(Wrappers.emptyWrapper());
    }

    /**
     * @Description 匯出
     * @author wangxiaoliang
     * @date 2020-07-15 14:11
     */
    default List<T> exportList(P param, HttpServletResponse response) {
        Wrapper<T> wrapper = getWrapper(param);
        List<T> managementList = getMapper().selectList(wrapper);
       return managementList;
    }

    /**
     * 根據指定欄位更新值
     * @param id
     * @param column
     * @param value
     */
    default void updateColumn(Long id, String column, Object value) {
        Map<String,Object> params = new HashMap<>();
        params.put(column, value);
        getMapper().updateFieldsById(params, id);
    }

}

3、Mapper

自定義YaoBaseMapper,專案中的Mapper只要實現該介面即可

/**
 * mybatis基礎介面
 * @author Yao
 * @since 2020-06-23
 */
public interface YaoBaseMapper<T extends YaoBaseEntity> extends BaseMapper<T> {

    /**
     * 批量插入
     *
     * @param list
     * @return
     */
    int insertBatch(@Param("list") List<T> list);


    /**
     *  根據ID 更新指定欄位
     * @param map 指定欄位和值
     * @param id id
     * @return
     */
    int updateFieldsById(@Param("map") Map<String, Object> map, @Param("id") Long id);
}

資料庫對應實體YaoBaseEntity,使用方式繼承即可

package com.jing.yao.component.entity;

import com.baomidou.mybatisplus.annotation.*;

import java.util.Date;

/**
 * 基礎資料庫實體
 * @author Yao
 * @since 2020-06-13
 */
public class YaoBaseEntity {

    @TableId(value = "id",type = IdType.AUTO)
    private Long id;

    /**
     * 建立人
     */
    @TableField(fill = FieldFill.INSERT)
    private String createBy;

    /**
     * 建立時間
     */
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    /**
     * 修改人
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateBy;

    /**
     * 修改時間
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;

    /**
     * 版本號
     * @Version:樂觀鎖,,需要新增mybatis plus外掛optimisticLockerInterceptor
     */
    @TableField(fill = FieldFill.INSERT_UPDATE, update="%s+1")
    @Version
    private Integer version;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getCreateBy() {
        return createBy;
    }

    public void setCreateBy(String createBy) {
        this.createBy = createBy;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public String getUpdateBy() {
        return updateBy;
    }

    public void setUpdateBy(String updateBy) {
        this.updateBy = updateBy;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }
}

4、DTO

自定義YaoBaseDTO,專案中所有跟前端互動的物件都需要轉換為DTO(不必糾結DTO作為傳輸物件,返回給前端適不適合),DTO包含了欄位校驗、翻譯等。專案中的DTO需要繼承該類。該物件配合註解 @Formatter可實現欄位的翻譯
YaoBaseDTO:

package com.jing.yao.component.dto;
import java.util.Date;

/**
 * 基礎傳輸物件
 * @author Yao
 * @since 2020-06-13
 */
public class YaoBaseDTO {
    private Long id;

    /**
     * 建立人
     */
    private String createBy;

    /**
     * 建立時間
     */
    private Date createTime;

    /**
     * 修改人
     */
    private String updateBy;

    /**
     * 修改時間
     */
    private Date updateTime;

    /**
     * 版本號
     */
    private Integer version;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getCreateBy() {
        return createBy;
    }

    public void setCreateBy(String createBy) {
        this.createBy = createBy;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public String getUpdateBy() {
        return updateBy;
    }

    public void setUpdateBy(String updateBy) {
        this.updateBy = updateBy;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }
}

@Formatter

package com.jing.yao.annotation;

import java.lang.annotation.*;

/**
 * 翻譯
 * @author Yao
 * @since 2020-07-14
 */

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Formatter {

    /**
     * 對應的資料字典,用被註解欄位的值取該字典中取值
     * @return
     */
    String dictCode() default "";


    /**
     * 字典翻譯後,把翻譯後的值賦值到該欄位
     * @return
     */
    String targetField() default "";

    /**
     * 指定快取Key,不用資料字典,直接從對應的可以取值
     * 但是對應Key在Redis的儲存必須是Hash型別
     */
    String key() default "";
}

以上只是工作中使用的一部分,如果有問題或者建議歡迎各位老鐵指出
程式碼倉庫地址連結