SpringBoot項(xiàng)目標(biāo)準(zhǔn)化代碼編寫

一屹篓、認(rèn)識項(xiàng)目結(jié)構(gòu)

1、項(xiàng)目基本結(jié)構(gòu)

  • 基礎(chǔ)功能結(jié)構(gòu)
entitys // 存放實(shí)體類
enums // 存放枚舉類
dto  // 存放入?yún)⒔Y(jié)構(gòu)
vo   // 存放出參結(jié)構(gòu)
utils // 存放相關(guān)工具類
  • 核心邏輯功能結(jié)構(gòu)
-controller  // 基本參數(shù)校驗(yàn)
--service    // 存放服務(wù)接口
---impl       // 存放服務(wù)接口實(shí)現(xiàn)類(核心業(yè)務(wù)邏輯功能開發(fā))
----dao      // 持久層村刨,數(shù)據(jù)增刪改查
-----provider // 動態(tài)sql拼接層亦歉,編寫動態(tài)的sql

2嘿棘、統(tǒng)一消息返回

  • ErrorCodeEnum.java(存放各類錯(cuò)誤碼)
public enum ErrorCodeEnum {
    /**
     * 錯(cuò)誤碼
     */
    ERROR(9999, "系統(tǒng)異常"),
    HTTP_CONNECTION_OVERTIME(9998, "連接超時(shí)"),
    FREQUENTLY_REQUEST(9003, "操作頻繁"),
    INVALID_RSA_KEY(9002, "超時(shí)失效"),
    TOKEN_TIMEOUT(9005, "token失效"),
    INVALID_PARAMS(9001, "非法參數(shù)"),
    SIGN_ERROR(9000, "簽名錯(cuò)誤"),
    INVALID_STATUS(9004, "狀態(tài)不符"),

    OK(200, "請求通過"),
    NO(201, "請求不通過"),
    TIP(202, "提示"),

    private Integer code;

    private String message;

    ErrorCodeEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}
  • ErrorCodeException.java 統(tǒng)一異常結(jié)構(gòu)
@ToString
public class ErrorCodeException extends RuntimeException {

    private static final long serialVersionUID = -7638041501183925225L;

    private Integer code;

    public ErrorCodeException(ErrorCodeEnum errorCode, String msg) {
        super(msg);
        this.code = errorCode.getCode();
    }

    public ErrorCodeException(ErrorCodeEnum errorCode) {
        super(errorCode.getMessage());
        this.code = errorCode.getCode();
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

}
  • SimpleMessage.java (簡易信息返回)
@Data
public class SimpleMessage implements Serializable {
    private static final long serialVersionUID = -2957516153008725933L;
    private Integer errorCode;
    private String errorMsg;

    public SimpleMessage(ErrorCodeEnum errorCode, String errorMsg) {
        this.errorCode = errorCode.getCode();
        this.errorMsg = errorMsg;
    }
}
  • MessageBean.java (豐富信息返回)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageBean implements Serializable {
    private static final long serialVersionUID = 7192766535561421181L;
    private String errorMsg;
    private Object data;
    private Integer errorCode;
}
  • AppResponseBodyAdvice.java 處理統(tǒng)一返回
@Slf4j
@ControllerAdvice
public class AppResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
    }

    @SuppressWarnings("Duplicates")
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {
       // 特殊返回類型處理
        if (body instanceof SimpleMessage || body instanceof MessageBean) {
            return body;
        }
        MessageBean messageBean = new MessageBean();
        messageBean.setErrorCode(ErrorCodeEnum.OK.getCode());
        messageBean.setErrorMsg(ErrorCodeEnum.OK.getMessage());
        messageBean.setData(body);
        return messageBean;
    }
}

· ManagerExceptionHandler.java (全局異常處理)

@Slf4j
@ControllerAdvice
public class ManagerExceptionHandler {

    @ExceptionHandler(value = ErrorCodeException.class)
    @ResponseBody
    public SimpleMessage myErrorHandler(ErrorCodeException e) {
        SimpleMessage message = new SimpleMessage();
        message.setErrorCode(e.getCode());
        message.setErrorMsg(e.getMessage());
        return message;
    }

    @ExceptionHandler(value = DuplicateKeyException.class)
    @ResponseBody
    public SimpleMessage duplicateKeyErrorHandler() {
        SimpleMessage message = new SimpleMessage();
        message.setErrorCode(ErrorCodeEnum.NO.getCode());
        message.setErrorMsg("數(shù)據(jù)重復(fù)");
        return message;
    }

    @ExceptionHandler(value = MaxUploadSizeExceededException.class)
    @ResponseBody
    public SimpleMessage fileSizeLimitErrorHandler() {
        SimpleMessage message = new SimpleMessage();
        message.setErrorCode(ErrorCodeEnum.NO.getCode());
        message.setErrorMsg("圖片過大");
        return message;
    }

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public SimpleMessage errorHandler(Exception e, HttpServletRequest request) {
        SimpleMessage message = new SimpleMessage();
        message.setErrorCode(ErrorCodeEnum.ERROR.getCode());
        message.setErrorMsg(ErrorCodeEnum.ERROR.getMessage());
        log.error("url [{}] params [{}] error", request.getRequestURI(), JSON.toJSONString(request.getParameterMap()), e);
        return message;
    }
}

二、了解常用規(guī)范

1.實(shí)體類(以設(shè)備表功能為例)

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class DeviceInfo implements Serializable {
    private static final long serialVersionUID = -4951578247348538266L;
    /**
     * 自增ID
     */
    @NotNull(message = "設(shè)備ID不能為空", groups = {Update.class})
    private Integer id;
    /**
     * 設(shè)備名稱
     */
    @NotBlank(message = "設(shè)備名稱不能為空", groups = {Insert.class, Update.class})
    private String deviceName;
    /**
     * 設(shè)備碼
     */
    @NotNull(message = "設(shè)備碼不能為空", groups = {Insert.class, Update.class})
    private DeviceTypeEnum deviceCode;
    /**
     * 二級分類ID
     */
    @NotNull(message = "二級分類ID", groups = {Insert.class, Update.class})
    private Integer parentId;
    /**
     * 圖標(biāo)地址
     */
    @NotBlank(message = "圖標(biāo)地址不能為空", groups = {Insert.class, Update.class})
    private String iconUrl;
    /**
     * 排序
     */
    @NotNull(message = "排序不能為空", groups = {Insert.class, Update.class})
    private Integer sort;
    /**
     * 創(chuàng)建人
     */
    private String createNo;
    /**
     * 更新人
     */
    private String updateNo;
    /**
     * 創(chuàng)建時(shí)間
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    /**
     * 更新時(shí)間
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;

}
  • 講解點(diǎn)

1挨稿、 Lomok 注解
2搔预、@Valid 注解 【@NotNull 、@NotBlank叶组、@NotEmpty拯田、@JsonFormat、@DateTimeFormat】
3甩十、自定義接口【Insert.class船庇、Update.class】
4、javadoc 注釋
5侣监、@Valid延伸鸭轮、多級結(jié)構(gòu)校驗(yàn)

  • 注意點(diǎn)

【強(qiáng)制】1、實(shí)體類必須與表結(jié)構(gòu)完全對應(yīng)一致
【強(qiáng)制】2橄霉、實(shí)體類必須實(shí)現(xiàn)序列化接口
【強(qiáng)制】3窃爷、增、刪、改按厘、查的接口医吊,必須使用公共接口
【強(qiáng)制】4、java類逮京、各參數(shù)值必須使用 javadoc 注釋(具體注釋情況卿堂,需按照alibaba編程規(guī)范執(zhí)行)
【推薦】5、添加@JsonInclude(JsonInclude.Include.NON_NULL) 和 @Builder

2.DTO類

@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class DeviceInfoDTO extends DeviceInfo implements Serializable {
    private static final long serialVersionUID = 7535820681677648701L;

    /**
     * 當(dāng)前頁
     */
    @NotNull(message = "當(dāng)前頁不能為空", groups = {PageQuery.class})
    private Integer pageNumber;
    /**
     * 每頁的數(shù)量
     */
    @NotNull(message = "每頁顯示數(shù)量不能為空", groups = {PageQuery.class})
    private Integer pageSize;

}
  • 講解點(diǎn)

1懒棉、extend 繼承的好處
2草描、PageQuery.class 接口

  • 注意點(diǎn)

【強(qiáng)制】DTO 類必須繼承至實(shí)體類
【強(qiáng)制】DTO 類型實(shí)現(xiàn)后必須實(shí)現(xiàn)序列化接口
【強(qiáng)制】DTO 類的命名,必須最后的DTO 為大寫
【建議】在需要分頁功能中策严,pageNumber和pageSzie必傳

2.VO類

@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class DeviceInfoVO extends DeviceInfo implements Serializable {
    private static final long serialVersionUID = -5721621832940504628L;
    /**
     * 二級分類
     */
    private String secondaryCategoryName;
    /**
     * 一級分類
     */
    private String primaryCategoryName;

}
  • 講解點(diǎn)

用法與DTO保持一致

  • 注意點(diǎn)

【強(qiáng)制】VO 類必須繼承至實(shí)體類
【強(qiáng)制】VO 類型實(shí)現(xiàn)后必須實(shí)現(xiàn)序列化接口
【強(qiáng)制】VO 類的命名穗慕,必須最后的DTO 為大寫

3.枚舉類

public enum DeviceTypeEnum {

    /**
     * 智能插座
     */
    SOCKET("智能插座"),
    /**
     * 通斷器
     */
    ON_OFF("通斷器")
    ;

    private final String str;

    DeviceTypeEnum(String str) {
        this.str = str;
    }

    /**
     * 獲取key,value值
     *
     * @return List<CodeValuePair>
     */
    public static List<CodeValuePair> getStatusMap() {
        List<CodeValuePair> list = new ArrayList<>();
        for (DeviceTypeEnum balanceTypeEnum : DeviceTypeEnum.values()) {
            list.add(CodeValuePair.builder().code(balanceTypeEnum.name()).value(balanceTypeEnum.getStr()).build());
        }
        return list;
    }

    public String getStr() {
        return str;
    }
}
  • 講解點(diǎn)

1妻导、使用類型枚舉的好處
2逛绵、使用枚舉的注意點(diǎn)(保持枚舉更新的一致性)
3、字符串枚舉的好處(簡述間隙鎖)

  • 注意點(diǎn)

【強(qiáng)制】涉及到類型的必須使用枚舉

三栗竖、增刪改查標(biāo)準(zhǔn)化寫法

1暑脆、controller 層級

/**
 * @author tangn
 * @date 2021/1/9 9:46
 */
@RestController
@RequestMapping("/device")
public class DeviceController {

    @Resource
    private DeviceService deviceService;


    /**
     * 創(chuàng)建設(shè)備
     *
     * @param deviceInfo 設(shè)備信息
     * @param result     校驗(yàn)結(jié)果
     * @return SimpleMessage
     */
    @RequestMapping("/createDevice")
    public SimpleMessage createDevice(@Validated({Insert.class}) DeviceInfo deviceInfo,
                                      BindingResult result) throws Exception {
        if (result.hasErrors()) {
            return new SimpleMessage(ErrorCodeEnum.INVALID_PARAMS, result.getAllErrors().get(0).getDefaultMessage());
        }
        return deviceService.createDevice(deviceInfo);
    }

    /**
     * 獲取設(shè)備列表
     *
     * @param deviceInfoDTO 查詢參數(shù)
     * @param result        校驗(yàn)結(jié)果
     * @return List<DeviceInfoVO>
     */
    @RequestMapping("/getDeviceList")
    public Page<DeviceInfoVO> getDeviceList(@Validated({PageQuery.class}) DeviceInfoDTO deviceInfoDTO,
                                            BindingResult result) {
        if (result.hasErrors()) {
            throw new ErrorCodeException(ErrorCodeEnum.INVALID_PARAMS, result.getAllErrors().get(0).getDefaultMessage());
        }
        return new Page<>(deviceService.getDeviceList(deviceInfoDTO));
    }

    /**
     * 更新設(shè)備信息
     *
     * @param deviceInfo 設(shè)備信息
     * @param result     校驗(yàn)結(jié)果
     * @return SimpleMessage
     */
    @RequestMapping("/updateDevice")
    public SimpleMessage updateDevice(@Validated({Update.class}) DeviceInfo deviceInfo,
                                      BindingResult result) throws Exception {
        if (result.hasErrors()) {
         return new SimpleMessage(ErrorCodeEnum.INVALID_PARAMS, result.getAllErrors().get(0).getDefaultMessage());
        }
        return deviceService.updateDevice(deviceInfo);
    }

    /**
     * 刪除設(shè)備信息
     *
     * @param deviceId 設(shè)備ID
     * @return SimpleMessage
     */
    @RequestMapping("/delDevice")
    public SimpleMessage updateDevice(Integer deviceId) {
        if (Objects.isNull(deviceId)) {
            return new SimpleMessage(ErrorCodeEnum.INVALID_PARAMS);
        }
        return deviceService.delDevice(deviceId);
    }

}
  • 講解點(diǎn)

1、頭部類的 @RequestMapping
2狐肢、@Validated({Update.class}) 根據(jù)接口標(biāo)識類型進(jìn)行參數(shù)校驗(yàn)的規(guī)范
3添吗、return 和 throw 的用法
4、controller層的使用規(guī)范
5份名、分頁查詢的使用方法(搭配DTO碟联、VO 使用)
6、注釋的使用
7僵腺、@Resource 和 @Autowired 的區(qū)別(自行理解)(https://blog.csdn.net/magi1201/article/details/82590106)

  • 注意點(diǎn)

【強(qiáng)制】基本參數(shù)校驗(yàn)必須使用@Validated校驗(yàn)方式
【強(qiáng)制】必須用對return 和 throw 鲤孵,不允許滿足return情況的使用throw
【強(qiáng)制】注釋必須完善,不允許圖省事不寫參數(shù)的意義
【強(qiáng)制】controller 層級不允許出現(xiàn) Dao層的注入辰如,只能注入 Service 層
【強(qiáng)制】無特殊用途普监,必須使用@Resuorce注解,不能使用@Autowired注解
【建議】操作類型的返回琉兜,使用SimpleMessage

2凯正、service 層級

/**
 * @author tangn
 * @date 2021/1/9 9:47
 */
public interface DeviceService {

    /**
     * 創(chuàng)建設(shè)備
     *
     * @param deviceInfo 設(shè)備信息
     * @return SimpleMessage
     * @throws Exception
     */
    SimpleMessage createDevice(DeviceInfo deviceInfo) throws Exception;

    /**
     * 獲取設(shè)備列表
     *
     * @param deviceInfoDTO 查詢參數(shù)
     * @return List<DeviceInfoVO>
     */
    List<DeviceInfoVO> getDeviceList(DeviceInfoDTO deviceInfoDTO);

    /**
     * 更新設(shè)備信息
     *
     * @param deviceInfo 設(shè)備信息
     * @return SimpleMessage
     * @throws Exception
     */
    SimpleMessage updateDevice(DeviceInfo deviceInfo) throws Exception;

    /**
     * 刪除設(shè)備
     *
     * @param deviceId 設(shè)備ID
     * @return SimpleMessage
     * @throws Exception
     */
    SimpleMessage delDevice(Integer deviceId);

}
  • 講解點(diǎn)

為什么要通過接口形式進(jìn)行service層編寫?

2豌蟋、impl 層級

/**
 * @author tangn
 * @date 2021/1/9 9:48
 */
@Service
@Slf4j
public class DeviceServiceImpl implements DeviceService {

    @Resource
    private CategoryDao categoryDao;
    @Resource
    private DeviceDao deviceDao;
    @Resource
    private StoreDevicesDao storeDevicesDao;
    @Resource
    private AdminService adminService;

    /**
     * 創(chuàng)建設(shè)備
     *
     * @param deviceInfo 設(shè)備信息
     * @return SimpleMessage
     */
    @Override
    public SimpleMessage createDevice(DeviceInfo deviceInfo) throws Exception {
        AccountVO currentAccount = adminService.getCurrentAdmin();
        // 二級分類檢測
        if (categoryDao.checkSecondaryCategoryExistById(deviceInfo.getParentId()) == 0) {
            return new SimpleMessage(ErrorCodeEnum.NO, "查詢不到二級分類");
        }
        // 設(shè)置賬戶
        deviceInfo.setCreateNo(currentAccount.getPhoneNo());
        // 創(chuàng)建商品
        deviceDao.createDeviceInfo(deviceInfo);
        return new SimpleMessage(ErrorCodeEnum.OK, "創(chuàng)建設(shè)備成功");
    }

    /**
     * 獲取設(shè)備列表
     *
     * @param deviceInfoDTO 查詢參數(shù)
     * @return List<DeviceInfo>
     */
    @Override
    public List<DeviceInfoVO> getDeviceList(DeviceInfoDTO deviceInfoDTO) {
        PageHelper.startPage(deviceInfoDTO.getPageNumber(), deviceInfoDTO.getPageSize());
        return deviceDao.getDeviceList(deviceInfoDTO);
    }

    /**
     * 更新設(shè)備信息
     *
     * @param deviceInfo 設(shè)備信息
     * @return SimpleMessage
     */
    @Override
    public SimpleMessage updateDevice(DeviceInfo deviceInfo) throws Exception {
        AccountVO currentAccount = adminService.getCurrentAdmin();
        // 設(shè)備檢測
        if (deviceDao.checkDeviceById(deviceInfo.getId()) == 0) {
            return new SimpleMessage(ErrorCodeEnum.NO, "查詢不到該設(shè)備");
        }
        // 二級分類檢測
        if (categoryDao.checkSecondaryCategoryExistById(deviceInfo.getParentId()) == 0) {
            return new SimpleMessage(ErrorCodeEnum.NO, "查詢不到二級分類");
        }
        deviceInfo.setUpdateNo(currentAccount.getPhoneNo());
        // 更新設(shè)備
        deviceDao.updateDeviceInfo(deviceInfo);
        return new SimpleMessage(ErrorCodeEnum.OK, "更新成功");
    }

    /**
     * 刪除設(shè)備
     *
     * @param deviceId 設(shè)備ID
     * @return SimpleMessage
     */
    @Override
    public SimpleMessage delDevice(Integer deviceId) {
        // 設(shè)備檢測
        if (deviceDao.checkDeviceById(deviceId) == 0) {
            return new SimpleMessage(ErrorCodeEnum.NO, "查詢不到該設(shè)備");
        }
        // 檢查設(shè)備是否被門店綁定
        if (storeDevicesDao.checkDeviceBinded(deviceId) > 0) {
            return new SimpleMessage(ErrorCodeEnum.NO, "該設(shè)備已經(jīng)被門店綁定廊散,請先移除");
        }
        deviceDao.delDeviceInfo(deviceId);
        return new SimpleMessage(ErrorCodeEnum.OK, "刪除成功");
    }
}
  • 講解點(diǎn)

1、@Slf4j 日志打印注解
2梧疲、數(shù)據(jù)是否存在校驗(yàn)(count)
3允睹、PageHelper 使用
4运准、注釋的使用

  • 注意點(diǎn)

【強(qiáng)制】參數(shù)的真實(shí)性校驗(yàn)一定要做,不要相信任何傳過來的值
【強(qiáng)制】編寫每句代碼時(shí)缭受,操作每個(gè)數(shù)據(jù)時(shí)胁澳,一定要確定該值是否為空,禁止出現(xiàn)空指針異常
【強(qiáng)制】return 和 throw 一定要使用正確(再次強(qiáng)調(diào)9嵯选L蕖)
【強(qiáng)制】數(shù)據(jù)庫返回的值慢洋,一定要做期望值校驗(yàn)(后面會講解期望值校驗(yàn)注解)
【強(qiáng)制】方法中定義的變量要使用非包裝類型接受塘雳,(例:使用int 不用 Integer )

3、dao層級

/**
 * @author tangn
 * @date 2021/1/19 17:00
 */
@Mapper
public interface DeviceDao {

    /**
     * 根據(jù)二級分類ID檢查設(shè)備是否存在
     *
     * @param parentId 分類ID
     * @return int
     */
    @Select("SELECT COUNT(*) FROM device_info WHERE parent_id = #{parentId}")
    int checkDeviceByParentId(@Param("parentId") Integer parentId);

    /**
     * 檢查設(shè)備信息
     *
     * @param id 設(shè)備ID
     * @return int
     */
    @Select("SELECT COUNT(*) FROM device_info WHERE id = #{id}")
    int checkDeviceById(@Param("id") Integer id);

    /**
     * 創(chuàng)建設(shè)備信息
     *
     * @param deviceInfo 設(shè)備信息
     * @return int
     */
    @Insert("INSERT INTO device_info (" +
            "device_name," +
            "device_code," +
            "parent_id," +
            "icon_url," +
            "sort," +
            "create_time," +
            "create_no) VALUES (" +
            "#{dto.deviceName}," +
            "#{dto.deviceCode}," +
            "#{dto.parentId}," +
            "#{dto.iconUrl}," +
            "#{dto.sort}," +
            "now()," +
            "#{dto.createNo})")
    @ReturnCheck(info = "創(chuàng)建設(shè)備信息失敗")
    int createDeviceInfo(@Param("dto") DeviceInfo deviceInfo);

    /**
     * 獲取設(shè)備信息
     *
     * @param deviceInfoDTO 查詢參數(shù)
     * @return List<DeviceInfoVO>
     */
    @SelectProvider(type = DeviceDaoProvider.class, method = "getDeviceList")
    List<DeviceInfoVO> getDeviceList(@Param("dto") DeviceInfoDTO deviceInfoDTO);

    /**
     * 更新設(shè)備信息
     *
     * @param deviceInfo 設(shè)備信息
     * @return int
     */
    @Update("UPDATE device_info set " +
            "device_name = #{dto.deviceName}," +
            "device_code = #{dto.deviceCode}," +
            "parent_id = #{dto.parentId}," +
            "icon_url = #{dto.iconUrl}," +
            "sort = #{dto.sort}," +
            "update_no = #{dto.updateNo}," +
            "update_time = now() " +
            "WHERE id = #{dto.id} ")
    @ReturnCheck(info = "更新設(shè)備信息失敗")
    int updateDeviceInfo(@Param("dto") DeviceInfo deviceInfo);

    /**
     * 刪除設(shè)備信息
     *
     * @param id 設(shè)備ID
     * @return int
     */
    @Delete("DELETE FROM device_info WHERE id = #{id}")
    @ReturnCheck(info = "刪除設(shè)備失敗")
    int delDeviceInfo(@Param("id") Integer id);

    /**
     * 獲取設(shè)備信息
     *
     * @param id 設(shè)備ID
     * @return DeviceInfo
     */
    @Select("SELECT id,device_name FROM device_info WHERE id = #{id}")
    DeviceInfo getDeviceInfo(@Param("id") Integer id);

}
  • 講解點(diǎn)

1.使用count(*)校驗(yàn)數(shù)據(jù)真實(shí)性
2.@ReturnCheck 注解使用
3.@Select 普筹、@Update 败明、@Delete 的使用(尤其注意@Update 和 @Delete )
3.@SelectProvider 使用 type 和 method
4.利用映射轉(zhuǎn)VO
5.無特殊需求返回值無須添加 as (mybatis配置)

  • 注意點(diǎn)

【強(qiáng)制】插入及更新時(shí)不能忘記創(chuàng)建時(shí)間(人)、更新時(shí)間(人)
【強(qiáng)制】無特殊需求太防,不允許創(chuàng)建時(shí)插入更新時(shí)間
【強(qiáng)制】影響行數(shù)必須使用非包裝類型接受(使用 int 而非 Integer )
【建議】更新時(shí)妻顶,更新依據(jù)字段盡可能落在 主鍵或唯一索引上
【建議】增加@ReturnCheck 注解 和 @ReturnListCheck 注解減少serivce層判斷

3、provider層級

/**
 * @author tangn
 * @date 2021/1/20 17:29
 */
public class DeviceDaoProvider {

    /**
     * 獲取設(shè)備列表
     *
     * @param map 查詢參數(shù)
     * @return String
     */
    public String getDeviceList(HashMap<String, DeviceInfoDTO> map) {
        StringBuilder sql = new StringBuilder();
        DeviceInfoDTO deviceInfoDTO = map.get("dto");
        sql.append("SELECT " +
                " a.id," +
                " a.device_name," +
                " a.device_code," +
                " a.parent_id," +
                " a.icon_url," +
                " a.sort," +
                " d.name as 'createNo'," +
                " e.name as 'updateNo'," +
                " a.create_time," +
                " a.update_time, " +
                " b.category_name AS 'secondaryCategoryName', " +
                " c.category_name AS 'primaryCategoryName'  " +
                "FROM " +
                " `device_info` a " +
                " JOIN device_secondary_category b ON a.parent_id = b.id " +
                " JOIN device_primary_category c ON b.parent_id = c.id  " +
                " LEFT JOIN shiro_account d ON a.create_no = d.phone_no and d.plat_type = 0 " +
                " LEFT JOIN shiro_account e ON a.update_no = e.phone_no and e.plat_type = 0  " +
                "WHERE " +
                " 1 = 1 ");
        // 設(shè)備名稱
        if (StringUtils.isNotBlank(deviceInfoDTO.getDeviceName())) {
            sql.append(" AND INSTR(a.device_name,#{dto.deviceName}) > 0 ");
        }
        // 二級分類
        if (Objects.nonNull(deviceInfoDTO.getParentId())) {
            sql.append(" AND a.parent_id = #{dto.parentId} ");
        }
        sql.append(" ORDER BY a.create_time DESC ");
        return sql.toString();
    }

}
  • 講解點(diǎn)

1.DTO 內(nèi)取值的好處
2.WHERE 1 = 1 的妙處
3.使用工具類進(jìn)行參數(shù)值的判斷
4.使用 INSTR 進(jìn)行模糊查詢

  • 規(guī)范點(diǎn)

【強(qiáng)制】使用工具類對參數(shù)進(jìn)行判別
【建議】使用DTO 作為參數(shù)的攜帶體

四蜒车、來點(diǎn)干的讳嘱?

1、事務(wù)的使用

    /**
     * 確認(rèn)發(fā)放
     *
     * @param shopOrderDTO 參數(shù)
     * @return : com.orangeconvenient.common.entity.MessageBean<java.time.LocalDateTime>
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public MessageBean<String> confirmRelease(ShopOrderDTO shopOrderDTO) {
        ShopOrder shopOrder = Optional.ofNullable(storeOrderDao.getByIdAndStore(shopOrderDTO.getId(), shopOrderDTO.getStoreNo()))
                .orElseThrow(() -> new ErrorCodeException(ErrorCodeEnum.NO, "訂單不存在"));
        if (ShopOrderStatusEnum.RECEIVED.equals(shopOrder.getOrderStatus())) {
            return new MessageBean<>(ErrorCodeEnum.EXT_ASSEMBLE_ORDER_CONSUMED, Objects.nonNull(shopOrder.getSuccessTime()) ? LocalDateTimeUtil.formatDateTime(shopOrder.getSuccessTime()) : null, "此訂單顧客已取貨,請不要重復(fù)發(fā)放!");
        }
        if (ShopOrderStatusEnum.MEMBER_CANCEL.equals(shopOrder.getOrderStatus()) ||
                ShopOrderStatusEnum.STORE_CANCEL.equals(shopOrder.getOrderStatus()) ||
                ShopOrderStatusEnum.RAISE_OVER_CANCEL.equals(shopOrder.getOrderStatus())) {
            return new MessageBean<>(ErrorCodeEnum.NO, "此訂單已被取消,請不要發(fā)放商品!");
        }
        if (!ShopOrderStatusEnum.PENDING.equals(shopOrder.getOrderStatus())) {
            return new MessageBean<>(ErrorCodeEnum.NO, "訂單狀態(tài)不正確");
        }
        shopOrder.setSuccessTime(LocalDateTime.now());
        // 設(shè)置出庫商品的數(shù)量
        shopOrder.setOutGoodsNum(orderDao.getGoodsNumByOrderNo(shopOrder.getOrderNo(), ShopOrderInfoStatusEnum.PENDING));
        orderDao.updateOrderStatus(shopOrder, ShopOrderStatusEnum.RECEIVED);
        List<ShopOrderInfoVO> shopOrderInfoList = orderDao.getCanRefundOrderInfoList(shopOrder.getId(), ShopOrderInfoStatusEnum.PENDING);
        if (CollectionUtils.isEmpty(shopOrderInfoList)) {
            return new MessageBean<>(ErrorCodeEnum.NO, "沒有待自提的訂單明細(xì)");
        }
        if (orderDao.updateInfoStatusByOrderId(shopOrder.getId(), ShopOrderInfoStatusEnum.RECEIVED, ShopOrderInfoStatusEnum.PENDING, null) != shopOrderInfoList.size()) {
            throw new ErrorCodeException(ErrorCodeEnum.NO, "訂單明細(xì)狀態(tài)修改失敗");
        }
        // 更新實(shí)際出庫的訂單詳情
        storeOrderDao.updateInfoOutStatusByIds(shopOrderInfoList.size(), shopOrderInfoList.stream().map(ShopOrderInfo::getId).collect(Collectors.toList()), true);
        User user = Optional.ofNullable(userDao.queryUserById(shopOrder.getUserId())).orElseThrow(() -> new ErrorCodeException(ErrorCodeEnum.NO, "會員不存在"));
        //初始化評價(jià)
        memberCardOrderDao.insertEvaluateRecord(EvaluateRecord.builder()
                .orderNo(shopOrder.getOrderNo())
                .unionId(user.getUnionId())
                .userId(shopOrder.getUserId())
                .consumeTime(shopOrder.getSuccessTime())
                .merchantNo(shopOrder.getStoreNo())
                .totalAmount(shopOrderInfoList.stream().map(ShopOrderInfo::getActualPrice).reduce(BigDecimal.ZERO, BigDecimal::add))
                .evaluateRecordType(EvaluateRecordTypeEnum.MALL_ORDER).build());
        // 獲取門店信息
        Store store = storeDao.getByStoreNo(shopOrder.getStoreNo());
        //初始化售后信息
        ShopOrderAfterSale shopOrderAfterSale = ShopOrderAfterSale.builder()
                .orderId(shopOrder.getId())
                .userId(shopOrder.getUserId())
                .afterSaleOrderNo(Constant.SHOP_ORDER_AFTER_SALE_PREFIX + CheckUtil.fillZero(user.getId().longValue(), 5) + System.nanoTime()).build();
        afterSaleOrderDao.insertAfterSaleOrder(shopOrderAfterSale);
        // 初始化模板消息備注信息
        StringBuilder remark = new StringBuilder(200);
        givenUserIntegral(user, shopOrderInfoList, shopOrder.getStoreNo(), shopOrder.getOrderNo(), shopOrder, remark);
        // 消費(fèi)返券
        consumeReturnCoupon(shopOrder.getId(), user, remark, shopOrderDTO.getStoreNo());
        vitalityChangeService.doVitality(shopOrder.getUserId(), ShopCouponWeeklyContentTypeEnum.SHOP_CONSUME, null);
        //發(fā)送新版評價(jià)
        accessTokenComponent.sendCommonEvaluateMessage(user.getOpenId(),
                user.getUnionId(), shopOrder.getOrderNo(), Objects.nonNull(store) ? store.getBusinessName() : "",
                LocalDateTime.now(), remark);
        return new MessageBean<>(ErrorCodeEnum.OK, "取貨成功,請將商品發(fā)放給顧客");
    }
  • 講解點(diǎn)

1.事務(wù)的使用場景
2.為什么我的事務(wù)不生效酿愧?
3.@Builder使用

  • 注意點(diǎn)

【強(qiáng)制】不要使用編程式事務(wù)
【建議】開啟事務(wù)后沥潭,要考慮在合適的時(shí)機(jī)拋出事務(wù),讓其回滾
【建議】多層事務(wù)嵌套嬉挡,要考慮回滾情況

2钝鸽、for update 使用(悲觀鎖)

    /**
     * 獲取最后一條會員積分記錄
     *
     * @param userId 會員id
     * @return 積分
     */
    @Select("select integral from tbl_user_integral where user_id=#{userId} for update")
    @Options(timeout = 3)
    Long getLastIntegralByUserId(@Param("userId") Long userId);
  • 講解點(diǎn)

當(dāng)涉及到金額、庫存等需要保持一致性的操作時(shí)庞钢,可采用悲觀鎖進(jìn)行相應(yīng)的查詢并執(zhí)行更新

  • 注意點(diǎn)

@Options(timeout = 3) 一定要加拔恰,且不能鎖數(shù)據(jù)時(shí)間過長

3、update 更新狀態(tài)

    /**
     * 更新閑魚訂單狀態(tài)
     * @param outOrderStatus 訂單狀態(tài)
     * @param orderId 訂單ID
     * @param oldStatus 舊狀態(tài)
     * @return int
     */
    @Update("UPDATE xy_receive_order SET " +
            "out_order_status = #{outOrderStatus}," +
            "update_time = now() " +
            "WHERE id = #{orderId} AND " +
            "out_order_status = #{oldStatus} ")
    @ReturnCheck(info = "更新閑魚訂單狀態(tài)失敗")
    int updateOutOrderStatus(@Param("outOrderStatus") Integer outOrderStatus,
                             @Param("orderId") Long orderId,
                             @Param("oldStatus") Integer oldStatus);
  • 講解點(diǎn)

當(dāng)對狀態(tài)相關(guān)數(shù)據(jù)進(jìn)行更新時(shí),需要知道數(shù)據(jù)的原狀態(tài)

  • 注意點(diǎn)

【強(qiáng)制】 更新狀態(tài)時(shí)蛇受,必須限定原狀態(tài)

4斑匪、update 更新數(shù)值

    /**
     * 扣款
     *
     * @param id          代金券接收ID
     * @param orderAmount 訂單金額
     * @return int
     */
    @Update("UPDATE cash_voucher_receiver  " +
            "SET left_amount = ( left_amount - #{orderAmount} ), " +
            "update_time = now( ) " +
            "WHERE " +
            " id = #{id}  " +
            " AND (left_amount - #{orderAmount}) >= 0 ")
    @ReturnCheck(info = "核銷失敗")
    int consumeVoucher(@Param("id") Long id, @Param("orderAmount") BigDecimal orderAmount);
  • 講解點(diǎn)
  1. WHERE 前使用數(shù)據(jù)庫增減余額(庫存)
    2.WHERE 后使用計(jì)算結(jié)果>0 進(jìn)行校驗(yàn),防止多扣(允許負(fù)庫存例外)
  • 注意點(diǎn)

【強(qiáng)制】禁止在service層級計(jì)算好結(jié)果直接更新到庫

5河爹、批量操作 in 的使用方法

    /**
     * 批量插入用戶信息
     *
     * @param userInfoList 用戶信息列表
     * @return 影響的行數(shù)
     */
    @Insert("<script>" +
            " insert into tbl_user_info " +
            " (user_id, recent_consume_date, store_consume_time, " +
            " shop_consume_time, assemble_consume_time, " +
            " store_consume_date_collect, shop_consume_date_collect, " +
            " assemble_consume_date_collect, create_time) " +
            " values " +
            "  <foreach collection=\"list\" index=\"index\" item=\"info\" open=\"\" separator=\",\" close=\"\"> " +
            "  ( " +
            "      #{info.userId}, " +
            "      #{info.recentConsumeDate}, " +
            "      #{info.storeConsumeTime}, " +
            "      #{info.shopConsumeTime}, " +
            "      #{info.assembleConsumeTime}, " +
            "      #{info.storeConsumeDateCollect}, " +
            "      #{info.shopConsumeDateCollect}, " +
            "      #{info.assembleConsumeDateCollect}, " +
            "      NOW() " +
            "    )" +
            "  </foreach>" +
            "</script>")
    int insertUserInfo(@Param("list") List<UserInfo> userInfoList);
  • 講解點(diǎn)

1.<script> 標(biāo)簽 替代 xml 文件
2.<foreach> 標(biāo)簽的使用
3.使用批量插入的好處
4.使用批量插入的風(fēng)險(xiǎn)點(diǎn)

  • 注意點(diǎn)

【強(qiáng)制】在使用in進(jìn)行動態(tài)sql拼接時(shí),一定要考慮應(yīng)用場景插入的條數(shù)揪阶,必要時(shí)昌抠,需要在sevice層做分割。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鲁僚,一起剝皮案震驚了整個(gè)濱河市炊苫,隨后出現(xiàn)的幾起案子裁厅,更是在濱河造成了極大的恐慌,老刑警劉巖侨艾,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件执虹,死亡現(xiàn)場離奇詭異,居然都是意外死亡唠梨,警方通過查閱死者的電腦和手機(jī)袋励,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來当叭,“玉大人茬故,你說我怎么就攤上這事∫媳睿” “怎么了磺芭?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長醉箕。 經(jīng)常有香客問我钾腺,道長,這世上最難降的妖魔是什么讥裤? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任放棒,我火速辦了婚禮,結(jié)果婚禮上己英,老公的妹妹穿的比我還像新娘间螟。我一直安慰自己,他們只是感情好剧辐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布寒亥。 她就那樣靜靜地躺著,像睡著了一般荧关。 火紅的嫁衣襯著肌膚如雪溉奕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天忍啤,我揣著相機(jī)與錄音加勤,去河邊找鬼。 笑死同波,一個(gè)胖子當(dāng)著我的面吹牛鳄梅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播未檩,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼戴尸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了冤狡?” 一聲冷哼從身側(cè)響起孙蒙,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤项棠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后挎峦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體香追,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年坦胶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了透典。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡顿苇,死狀恐怖峭咒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情岖圈,我是刑警寧澤讹语,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布钙皮,位于F島的核電站蜂科,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏短条。R本人自食惡果不足惜导匣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茸时。 院中可真熱鬧贡定,春花似錦、人聲如沸可都。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽渠牲。三九已至旋炒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間签杈,已是汗流浹背瘫镇。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留答姥,地道東北人铣除。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像鹦付,于是被迫代替她去往敵國和親尚粘。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容