做Java開發(fā),你需要了解這些

前言:

在開發(fā)中,我們寫的代碼肯定是越少越好,代碼層次越清晰越好。那么下面就介紹一些可以減少代碼量、可以讓結(jié)構(gòu)更清晰的好東西。本文涉及vo、dto的使用、全局異常處理、表單驗(yàn)證以及一些小工具的使用。


歡迎大家關(guān)注我的公眾號(hào) javawebkf,目前正在慢慢地將簡書文章搬到公眾號(hào),以后簡書和公眾號(hào)文章將同步更新,且簡書上的付費(fèi)文章在公眾號(hào)上將免費(fèi)。


一、lombok的使用:

lombok是一個(gè)可以減少代碼量的小工具,使用非常簡單,只需要添加如下依賴:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

如果開發(fā)工具為idea,那么再安裝一下lombok插件即可。安裝方法:
setting ---> plugins ---> browse repositories,然后搜索lombok。


image.png

如果開發(fā)工具是eclipse,安裝lombok插件就麻煩一些,百度上有很多教程,此處就不再搬運(yùn)了。
添加了依賴,安裝好了插件,就可以使用了。下面主要介紹它的兩個(gè)注解。

1、@Data:
我們在寫實(shí)體類時(shí),每個(gè)實(shí)體類都要寫set、get方法以及toString方法等,雖然編輯器可以自動(dòng)生成,但還是有些麻煩,而且大量的set、get方法使代碼看起來不清爽。那么可以使用@Data來搞定。在實(shí)體類上加上此注解,就相當(dāng)于寫了set、get、toString、equals等方法。

@Entity
@Data
public class OrderDetail {
    @Id
    private String detailId;
    private String orderId;
    private String productId;
    private String productName;
    private BigDecimal productPrice;
    private Integer productQuantity;
    private String productIcon;
}

這樣的代碼看起來就干凈清爽多了。

2、@Slf4j:
這也是一個(gè)常用的注解。一般我們需要用日志都會(huì)像下面這樣寫:

Logger log = (Logger) LoggerFactory.getLogger(當(dāng)前類.class);
log.error("【查詢商品】商品不存在,productId={}",productId);

而加了這個(gè)注解,就不用自己創(chuàng)建log對象了,要用時(shí)直接用log調(diào)用對應(yīng)方法就行了。

log.error("【查詢商品】商品不存在,productId={}",productId);

關(guān)于lombok還有好多注解,暫且先介紹這兩個(gè)常用的。

二、createTime和updateTime問題:

一般的表中,我們都會(huì)加上createTime和updateTime兩個(gè)字段。然后有記錄存入數(shù)據(jù)庫時(shí),要實(shí)體.setCreateTime(new Date())來賦值,更新時(shí)就要實(shí)體.setUpdateTime(new Date())。每次有新增記錄或有更新時(shí)都要這樣set一下,有些麻煩。其實(shí)這兩個(gè)時(shí)間字段可以交給數(shù)據(jù)庫管理。建表時(shí)createTime和updateTime字段這樣寫:

create table 'product_category'(
   ......
   `create_time` timestamp not null default current_timestamp comment '創(chuàng)建時(shí)間',
   `update_time` timestamp not null default current_timestamp 
                 on update current_timestamp comment '修改時(shí)間',
   ......
)

然后在對應(yīng)的實(shí)體類上加@DynamicUpdate注解即可實(shí)現(xiàn)這兩個(gè)字段的自動(dòng)更新。創(chuàng)建時(shí)不用setCreateTime,更新時(shí)也不用setUpdateTime了。

@Entity
@DynamicUpdate // 動(dòng)態(tài)更新
@Data
public class ProductCategory {
    ......
    private Date createTime;
    private Date updateTime;
}

三、枚舉類的使用:

關(guān)于枚舉類,我之前只是在學(xué)Java基礎(chǔ)的時(shí)候了解過,后來老師教的一些demo中都沒用到過,最近看的一個(gè)項(xiàng)目用到了,在此記錄其用法。先看下面的代碼:

@Entity
@Data
public class ProductInfo {
    ......
    private Integer productStatus;//商品狀態(tài)(0正常,1下架)
}

有一個(gè)商品類,productStatus是其狀態(tài),0是上架,1是下架。如果現(xiàn)在要查詢所有上架商品,那么做法如下:

 List<ProductInfo> productInfoList = 
       productInfoRepository.findByProductStatus(0);

這里可能看得還挺清楚,0表示上架,查詢上架的,那么就是productStatus = 0 的,所以傳入0即可。但是在項(xiàng)目中,實(shí)體類一多,用0和1表示的東西一多,就很容易搞錯(cuò),到時(shí)候自己都要看半天才知道0代表什么1代表什么。這種情況就可以用枚舉類來處理。新建一個(gè)ProductStatusEnum枚舉類:

@Getter
public enum ProductStatusEnum {
    UP(0,"上架"),
    DOWN(1,"下架")
    ;
    private Integer code;
    private String message;
    ProductStatusEnum(Integer code,String message) {
        this.code = code;
        this.message = message;
    }
}

那么查詢方法就可以這樣寫了:

 List<ProductInfo> productInfoList = 
       productInfoRepository.findByProductStatus(ProductStatusEnum.UP.getCode());

ProductStatusEnum.UP.getCode()就是0,這樣一看就知道是查上架的商品。

四、VO的使用:

VO是view object的簡稱,中文意思是視圖對象,也就是我們在controller中返回給前端的內(nèi)容。一般開發(fā)時(shí),我們要按照前端給的文檔給前端返回相應(yīng)的內(nèi)容,比如現(xiàn)有api如下:

路由:GET /sell/buyer/product/list
參數(shù):無
返回:

{
    "code": 0,
    "msg": "成功",
    "data": [
        {
            "name": "類目名1",
            "type": 1,
            "foods": [
                {
                    "id": "123456",
                    "name": "商品名1",
                    "price": 4.5,
                    "description": "xxxxx",
                    "icon": "http://xxx.com",
                }
            ]
        },
        {
            "name": "類目名2",
            "type": 2,
            "foods": [
                {
                    "id": "123457",
                    "name": "商品名2",
                    "price": 10.9,
                    "description": "xxxxx",
                    "icon": "http://xxx.com",
                },
                {
                    "id": "123457",
                    "name": "商品名3",
                    "price": 10.9,
                    "description": "xxxxx",
                    "icon": "http://xxx.com",
                }
            ]
        }
    ]
}

咋一看很復(fù)雜,其實(shí)不然。一個(gè)中括號(hào)就表示里面的是list。那么可以知道,最外層是由code、msg和類目的list組成;第二層就是由類目名name、type和商品的list組成;商品的list就包含了商品的信息。那么要如何構(gòu)造這樣的返回對象呢?先從最外層開始寫。根據(jù)最外層的三個(gè)字段,可以寫出ResultVo類:

/**
 * 返回給前端的最外層對象
 * Create by zhu on 2018/10/7
 */
@Data
public class ResultVo<T> {
    private Integer code;//錯(cuò)誤碼
    private String msg;//提示信息
    private T data;//返回的內(nèi)容
}

這里data定義為泛型,這樣就可以通用。
現(xiàn)在將ResultVo對象返回給前端:

 @GetMapping("/test")
 public ResultVo test(){
        ResultVo resultVo = new ResultVo();
        resultVo.setCode(0);
        resultVo.setMsg("test");
        resultVo.setData("這是內(nèi)容");
        return resultVo;
 }

就會(huì)得到如下效果:


image.png

說明最外層是沒有錯(cuò)的,根據(jù)api提供的信息又可以寫出如下vo:

@Data
public class ProductVo {
    //這里寫的字段名與前端api需要的不一致沒關(guān)系,加上如下注解,直接里面寫api需要的字段名
    @JsonProperty("name")
    private String categoryName;
    @JsonProperty("type")
    private Integer categoryType;
    @JsonProperty("foods")//這個(gè)foods是一個(gè)商品的list
    private List<ProductInfoVo> productInfoVoList;
}
//這個(gè)就是最里層的商品對象
@Data
public class ProductInfoVo {
    @JsonProperty("id")
    private String productId;
    @JsonProperty("name")
    private String productName;
    @JsonProperty("price")
    private BigDecimal productPrice;
    @JsonProperty("description")
    private String productDescription;
    @JsonProperty("icon")
    private String productIcon;
}

注意,前端需要的name其實(shí)是類目名,如果這個(gè)vo也直接定義變量name,到時(shí)候會(huì)搞不清楚到底是商品的name還是類目的name。解決方案是:這里應(yīng)該是什么就寫什么,然后通過@JsonProperty("xxx")來指定返回給前端時(shí)的名字。

寫好后再去controller中:

  @GetMapping("/test")
    public ResultVo test() {
        ResultVo resultVo = new ResultVo();
        ProductVo productVo = new ProductVo();
        ProductInfoVo productInfoVo = new ProductInfoVo();

        productInfoVo.setProductName("商品1");
        productVo.setProductInfoVoList(Arrays.asList(productInfoVo));
        productVo.setCategoryName("類目1");

        resultVo.setData(Arrays.asList(productVo));
        resultVo.setMsg("成功");
        resultVo.setCode(0);

        return resultVo;
    }

把最里層的賦好值設(shè)置給中間層,中間層賦好值設(shè)置給最外層,再把最外層返回給前端,效果如下:


image.png

這樣返回的格式就對了,和api一致。接下來要做的事就是從數(shù)據(jù)庫查出相應(yīng)的記錄,然后賦給這三個(gè)對象就行了。
從上面的controller中可以發(fā)現(xiàn),我們每次都要new一個(gè)最外層的ResultVo對象,然后setCode、setMsg、setData。每個(gè)方法中都new一個(gè)還是很麻煩的,所以可以封裝一下:

public class ResultVoUtil {
    /** 成功時(shí)使用 */
    public static ResultVo success(Object object){
        ResultVo resultVo = new ResultVo();
        resultVo.setData(object);
        resultVo.setCode(0);
        resultVo.setMsg("成功");
        return resultVo;
    }

    /** 成功時(shí)且不需要返回值時(shí)使用 */
    public static ResultVo success(){
        return success(null);
    }

    /** 請求錯(cuò)誤時(shí)使用 */
    public static ResultVo error(Integer code,String msg){
        ResultVo resultVo = new ResultVo();
        resultVo.setCode(code);
        resultVo.setMsg(msg);
        return resultVo;
    }
}

那么上面的controller就可以改成:

 @GetMapping("/test")
    public ResultVo test() {
        ProductVo productVo = new ProductVo();
        ProductInfoVo productInfoVo = new ProductInfoVo();

        productInfoVo.setProductName("商品1");
        productVo.setProductInfoVoList(Arrays.asList(productInfoVo));
        productVo.setCategoryName("類目1");

        return ResultVoUtil.success(Arrays.asList(productVo));
    }

這樣代碼看起來就簡潔多了,而且這個(gè)ResultVoUtil也是通用的,傳入相應(yīng)的Object對象即可。

五、使用BeanUtils進(jìn)行屬性拷貝:

上面說到把從數(shù)據(jù)庫查到的productInfo的屬性的值賦給productInfoVo對應(yīng)的屬性,這里說一下賦值的問題:
ProductInfo類如下:

@Entity
@Data
public class ProductInfo {
    @Id
    private String productId;
    private String productName;
    private BigDecimal productPrice;
    private Integer productStock;//庫存
    private String productDescription;//描述
    private String productIcon;//小圖
    private Integer productStatus;//商品狀態(tài)(0正常,1下架)
    private Integer categoryType;//類目編號(hào)
}

ProductInfoVo類上面已給出,對比可以發(fā)現(xiàn),ProductInfoVo與ProductInfo相比,就是少幾個(gè)屬性。我們現(xiàn)在從數(shù)據(jù)庫查出來的是productInfo,而前端需要的是productInfoVo,所以需要將productInfo里的值設(shè)置到productInfoVo里去。常規(guī)做法是先從productInfo中g(shù)et再set到productInfoVo中去:

ProductInfo productInfo = productService.findOne(productId);
ProductInfoVo productInfoVo = new ProductInfoVo();
productInfoVo.setProductName(productInfo.getProductName());
productInfoVo.setProductPrice(productInfo.getProductPrice());
......

如果屬性少問題也不大,如果屬性很多,那么就要寫一大堆這樣的代碼。可以使用spring提供的一個(gè)屬性拷貝工具,不管多少個(gè)屬性,只需一行代碼:

BeanUtils.copyProperties(productInfo, productInfoVo);

這就表示把productInfo的屬性拷貝到productInfoVo對象中去。
注意:使用這個(gè)工具有兩點(diǎn)要注意,第一個(gè)就是這兩個(gè)對象的屬性名要一致;第二就是null值也會(huì)拷貝進(jìn)去,所以如果productInfo中有個(gè)屬性值為null,進(jìn)行拷貝后productInfoVo對應(yīng)的屬性值也會(huì)是null,就算拷貝之前設(shè)置了值也會(huì)覆蓋掉,所以要先拷貝再賦值。

六、dto的使用:

dto全稱是data transfer object,中文意思為數(shù)據(jù)傳輸對象。那么dto有什么作用?什么時(shí)候該用dto?如何使用呢?看下面的例子:
假如我數(shù)據(jù)庫有兩張表,一張學(xué)生表student,一張班級(jí)表class。它們對應(yīng)的實(shí)體類如下:

@Data
public class Student {
    @Id
    private String studentId;
    private String classId;
    private String name;
}

@Data
public class Class {
    @Id
    private String classId;
    private String  className;
}

假如我現(xiàn)在要查一個(gè)班級(jí)的信息,班級(jí)應(yīng)該是包含了多個(gè)學(xué)生的,因?yàn)橐粋€(gè)班級(jí)有多個(gè)學(xué)生,那么通常class實(shí)體類應(yīng)該這樣設(shè)計(jì):

@Data
public class Class {
    @Id
    private String classId;
    private String  className;
    private List<Student> studentList;
}

那么問題來了,這樣實(shí)體類和表就對應(yīng)不上了,因?yàn)樵赾lass表中沒有與studentList這個(gè)屬性對應(yīng)的字段。雖然可以在studentList上加上@Transient注解,這樣jpa在與數(shù)據(jù)表對應(yīng)時(shí)就會(huì)忽略這個(gè)屬性。但是這樣不好,感覺就是污染了這個(gè)與數(shù)據(jù)表對應(yīng)的實(shí)體類,我們還是要讓實(shí)體類與數(shù)據(jù)表 一 一對應(yīng),所以class類不能增加這個(gè)字段。那么我們就新建一個(gè)實(shí)體類,叫ClassDto:

@Data
public class ClassDto {
    private String classId;
    private String  className;
    private List<Student> studentList;
}

這就是dto的作用,最常用的就是當(dāng)某兩個(gè)實(shí)體類存在關(guān)系時(shí),而數(shù)據(jù)表對應(yīng)的實(shí)體類為了跟數(shù)據(jù)表一致,沒有體現(xiàn)這種關(guān)系,那么就可以使用dto。dto不對應(yīng)數(shù)據(jù)表,所@Id注解也不需要了。

七、異常處理:

平時(shí)我們用異??赡苤苯觮hrow一個(gè)exception就完事了,但是這樣不好,因?yàn)檫@樣拋出去自己也看不懂是什么異常,所以可以像下面這樣處理:
自定義一個(gè)異常類繼承RuntimeException:

public class GlobalException extends RuntimeException{
    private Integer code;
    public GlobalException(ExceptionEnum exceptionEnum ){
        super(resultEnum.getMessage());
        this.code = resultEnum.getCode();
    }

    public GlobalException(Integer code,String message){
        super(message);
        this.code = code;
    }

}

用到的枚舉類:

@Getter
public enum ExceptionEnum {
    PRODUCT_NOT_EXIST(10,"商品不存在"),
    PRODUCT_STOCK_ERROR(11,"庫存不足"),
    ;
    private Integer code;
    private String message;

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

自定義一個(gè)異常類,搭配枚舉一起使用。那么在拋異常的時(shí)候就可以這樣寫:

throw new GlobalException(ExceptionEnum.PRODUCT_NOT_EXIST);

這樣前端就可以看到“商品不存在”這樣的提示,而不是一串看不懂的異常。枚舉類種我只是列舉了兩個(gè)例子,有異常就可以往枚舉種添加,然后像上面那樣用就行了。

八、生成隨機(jī)數(shù):

有時(shí)候數(shù)據(jù)表的Id沒有設(shè)置自增,需要我們自己設(shè)置Id。Id要求是必須唯一,提供如下工具類:

public class KeyUtil {
    /**
     * 生成唯一主鍵
     * 格式:當(dāng)前時(shí)間+隨機(jī)數(shù)
     */
    public static synchronized String genUniqueKey(){
        Random random = new Random();
        //Integer a =random.nextInt(90) + 10;//生成兩位隨機(jī)數(shù)
        //生成六位隨機(jī)數(shù)
        Integer number =random.nextInt(900000) + 100000;
        return  System.currentTimeMillis() + String.valueOf(number);

    }
}

九、表單驗(yàn)證:

前端給后臺(tái)傳參數(shù)的時(shí)候,我們要在controller中獲取前端傳入的參數(shù),一般有以下幾種做法:

  • HttpServletRequest:
    用這個(gè)一般要編寫一個(gè)工具類,用來獲取指定類型的參數(shù):
public class HttpServletRequestUtil {
    public static int getInt(HttpServletRequest request, String name) {
        try {
            return Integer.decode(request.getParameter(name));
        } catch (Exception e) {
            return -1;
        }
    }

    public static long getLong(HttpServletRequest request, String name) {
        try {
            return Long.valueOf(request.getParameter(name));
        } catch (Exception e) {
            return -1;
        }
    }

    public static Double getDouble(HttpServletRequest request, String name) {
        try {
            return Double.valueOf(request.getParameter(name));
        } catch (Exception e) {
            return -1d;
        }
    }

    public static Boolean getBoolean(HttpServletRequest request, String name) {
        try {
            return Boolean.valueOf(request.getParameter(name));
        } catch (Exception e) {
            return false;
        }
    }

    public static String getString(HttpServletRequest request, String name) {
        try {
            String result = request.getParameter(name);
            if (result != null) {
                result = result.trim();
            }
            if ("".equals(result))
                result = null;
            return result;
        } catch (Exception e) {
            return null;
        }
    }
}

然后在controller中這樣用:

@RequestMapping(value = "/getproductlistbyshop")
public ResultVo list(HttpServletRequest request) {
        // 獲取前臺(tái)傳過來的頁碼
        int pageIndex = HttpServletRequestUtil.getInt(request, "pageIndex");
        // 獲取前臺(tái)傳過來的每頁顯示的數(shù)量
        int pageSize = HttpServletRequestUtil.getInt(request, "pageSize");
                ......
}

但是這樣獲取參數(shù),如果要校驗(yàn)的話需要自己寫if語句來判斷,比如:

if (pageIndex == null || pageSize == null){
    log.error(...);
    throw new GlobalException(...);
}

看第二種獲取前端參數(shù)的方法:

  • @RequestParam:
 @GetMapping("/list")
 public ResultVo list(@RequestParam(value = "page",defaultValue = "0") Integer page,
                      @RequestParam(value = "size",defaultValue = "10") Integer size){
       ......    
    }

用這個(gè)還可以用defaultValue指定默認(rèn)值,當(dāng)前端沒傳時(shí)默認(rèn)就為defaultValue指定的值。這種方法呢其實(shí)就是第一種方法的注解形式,如果要對獲取的參數(shù)判斷,還是要自己寫if語句。
接下來看第三種方法:

  • 表單對象:
    如果前端傳過來的參數(shù)很多,用上面兩種方法寫未免有些麻煩,而且還要自己一個(gè)個(gè)的判斷傳過來的參數(shù)是否為空,為空的話又要給前端什么提示。我們可以把前端的參數(shù)封裝成一個(gè)對象,然后在controller中直接獲取該對象即可,而且對于參數(shù)的驗(yàn)證都可以在封裝的這個(gè)對象中完成,這就是springmvc提供的表單驗(yàn)證??蠢樱?/li>
@Data
public class OrderForm {
    @NotEmpty(message = "姓名必填")
    private String name;
    @NotEmpty(message = "手機(jī)號(hào)必填")
    private String phone;
    @NotEmpty(message = "地址必填")
    private String address;
    @NotEmpty(message = "openid必填")
    private String openid;
    @NotEmpty(message = "購物車不能為空")
    private String items;
}

比如從前端獲取的參數(shù)有這么多,那么就可以封裝成這樣一個(gè)OrderForm表單對象。加上@NotEmpty注解就表示這個(gè)參數(shù)不能為空,里面的message就是當(dāng)該參數(shù)為空時(shí)給前端的提示。接下來看如何在controller中使用該對象:

@PostMapping("/create")
 public ResultVo create(@Valid OrderForm orderForm, BindingResult bindingResult){
      //判斷表單校驗(yàn)后有沒有錯(cuò)誤
      if (bindingResult.hasErrors()) {
          log.error("【創(chuàng)建訂單】參數(shù)不正確,orderForm={}",orderForm);
          throw new GlobalException(ExceptionEnum.PARAM_ERROR.getCode(),
                    bindingResult.getFieldError().getDefaultMessage());
      }
      ......
}

@Valid注解就可以使用該對象,bindingResult就是驗(yàn)證的結(jié)果,如果驗(yàn)證參數(shù)不正確,通過bindingResult.getFieldError().getDefaultMessage()就可以獲取到剛才@NotEmpty注解里面message的內(nèi)容,配合全局異常使用,就可以把這個(gè)message返回給前端。

總結(jié):

上面的介紹的lombok、創(chuàng)建時(shí)間和更新時(shí)間的處理、BeanUtils的使用都可以減少代碼量,而dto、vo、全局異常處理、表單驗(yàn)證等可以使代碼結(jié)構(gòu)更加清晰,使程序更加健壯。希望大家喜歡!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 文章核心 其實(shí),本不想把標(biāo)題寫的那么恐怖,只是發(fā)現(xiàn)很多人干了幾年java以后,都自認(rèn)為是一個(gè)不錯(cuò)的java程序員了...
    java菜閱讀 1,977評論 6 49
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,733評論 25 709
  • 人生最美的風(fēng)景不在于彼岸的鮮花與掌聲,而在于來時(shí)的路。這句話說的沒錯(cuò),但是人的一生的奮斗,大多不都為了將來得到一個(gè)...
    莫易念閱讀 258評論 0 1

友情鏈接更多精彩內(nèi)容