近期开发经验汇总

672 阅读7分钟

我正在参与掘金创作者训练营第6期,点击了解活动详情

BigDecimal相关问题

BigDecimal比较大小

比较相等的时候,需要注意BigDecimal的存储是由原数和小数位数(scale)组成的,所以推荐使用 compareTo() 方法,如果使用equals()比较相等,即要求比较双方数值小数位数都一致,阿里规范禁止使用equals()比较BigDecimal

BigDecimal 除不尽的问题

遇到异常:java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal

原因是:计算结果是无限循环小数...

要设置舍入模式方法,设置为 BigDecimal.ROUND_HALF_DOWNRoundingMode.DOWN。同时要注意在进行一些金额、数量、百分比计算的时候,都建议涉及舍入模式,需要根据自己的业务规范制定,比如我们的业务规定数据库存储金额的小数位是4位,百分比是2位,计算金额和优惠率的结果都设置小数位为2位,并且全部向0舍去(RoundingMode.DOWN)

new BigDecimal(16).divide(new BigDecimal(233), 2, RoundingMode.DOWN)

其余几种舍入模式参考 Bigdecimal 八种舍入模式

BigDecimal 的参数校验

使用 java validated 校验注解校验BigDecimal的请求参数,在保存优惠率、金额的时候会使用,完整使用如下:

/**
 * 总金额
 * totalMoney 类型是 BigDecimal, @Digits@DecimalMin 是针对数值的校验
 * 使用字符串传值,校验也能生效,只要字符串里面是数字就行 
 */ 
@Digits(integer = 9, fraction=4, message = "金额格式不正确") 
@DecimalMin(value = "0.00", message = "金额请勿小于0.00") 
@DecimalMax(value = "100000000.00", message = "金额请勿超过1亿") 
@NotNull(message = "请传递总金额") 
private BigDecimal totalMoney;

该参数的请求对象前需加上@Validated@Valid 注解,如果该对象嵌套在 请求对象的子对象里面,需要使用@Valid @Validated 来实现嵌套参数校验。

@Digits(integer = 9, fraction=4, message = "金额格式不正确") 

digit是数位的意思,这里的integer意思整数最多有几位,fraction意思小数最多有几位,只是划定了传入参数的范围,前端传值的时候可以用字符串(只要字符串里面都是数字就行)、数字;

@DecimalMin(value = "0.00", message = "金额请勿小于0.00") 
@DecimalMax(value = "100000000.00", message = "金额请勿超过1亿") 

decimal的最小值 和最大值 ,传入参数必须在两个注解的value范围内 ;

@NotNull(message = "amount不为空")

传入参数不为空,对于BigDecimal、Integer、Long等数值类型,用@NotNull注解,如果传参是空字符串或者只有空格的字符串,也无法通过校验。如果字段的类型是String,建议使用@NotBlank注解

使用 easyExcel 导出时的问题

使用 cglib_使用easyexcel时遇到Could not initialize class cglib.beans.BeanMap,其实是底层的asm依赖冲突,可以通过 maven 插件查看冲突的 asm 依赖

image.png

解决办法:都排除掉,导入一个统一版本的依赖即可!

image.png

Latex 如何让等号对齐

\begin{equation*} 
  \begin{aligned} 
    f(n) &= f(n-1) + f(n-2) \\ 
      &= f(n-2) + f(n-3) + f(n-2) \\ 
      &= 2f(n-2) + f(n-3) \\ 
  \end{aligned}
\end{equation*}

image.png

Stream 只能用一次,如何重用?

在开发中,有时会对一个列表做很多操作,如果使用Stream ,我们习惯性的将 Stream 抽离出来,然后对中间状态的流做不同的操作,这样会抛出异常,为什么呢?

这里回顾下流的特性:

  1. 流不会储存元素,所以每个Stream 流只能使用一次(每次调用方法后都会销毁)
  2. 流不会修改其数据源,只是在原数据集上定义了一组操作。不存储数据,也不修改原始源。
  3. 流执行具有延迟特性,即每当访问到流中的一个元素,才会在此元素上执行这一系列操作。
  4. 当执行流的终结操作的时候,整个流才会被执行

知道了流的特性,如果我们复用流,就会抛出异常,请看下面的例子

/**
 * 计算 BigDecimal 的计算
 *
 * @date 2022/4/11 14:45:25
 */
public static void main(String[] args) {

    ArrayList<Math> maths = new ArrayList<>();

    maths.add(new Math(new BigDecimal("1"), new BigDecimal("13")));

    maths.add(new Math(new BigDecimal("2"), BigDecimal.TEN));

    // 计算 math 这个对象 里面的两个 BigDecimal 的 乘积
    BigDecimal multiplyResult = maths.stream()
            // 模拟 过滤操作
            .filter(math -> true)
            // 乘法
            .map(accessory -> Optional.ofNullable(accessory.getPrice())
                    .orElse(BigDecimal.ZERO).multiply(Optional.ofNullable(accessory.getQty()).orElse(BigDecimal.ZERO)))
            .reduce(BigDecimal.ZERO, BigDecimal::add);

    // 计算 math 这个对象 里面的两个 BigDecimal 的 加法
    BigDecimal addResult = maths.stream()
            // 模拟 过滤操作
            .filter(math -> true)
            // 加法
            .map(accessory -> Optional.ofNullable(accessory.getPrice())
                    .orElse(BigDecimal.ZERO).add(Optional.ofNullable(accessory.getQty()).orElse(BigDecimal.ZERO)))
            .reduce(BigDecimal.ZERO, BigDecimal::add);

    System.out.println(multiplyResult);

    System.out.println(addResult);
}


这个例子里面,都要获取到 maths 这个 列表的 Stream ,然后去做不同的操作,这样写会有很多重复代码,改一下代码,两个操作复用一个流的话:

/**
 * 计算 BigDecimal 的计算
 *
 * @author jy
 * @date 2022/4/11 14:45:25
 */
public static void main(String[] args) {

    ArrayList<Math> maths = new ArrayList<>();

    maths.add(new Math(new BigDecimal("1"), new BigDecimal("13")));

    maths.add(new Math(new BigDecimal("2"), BigDecimal.TEN));

    // 提取出公共代码来 
    Stream<Math> mathStream = maths.stream()
            // 模拟 过滤操作
            .filter(math -> true);

    // 计算 math 这个对象 里面的两个 BigDecimal 的 乘积
    BigDecimal multiplyResult = mathStream
            // 乘法
            .map(accessory -> Optional.ofNullable(accessory.getPrice())
                    .orElse(BigDecimal.ZERO).multiply(Optional.ofNullable(accessory.getQty()).orElse(BigDecimal.ZERO)))
            .reduce(BigDecimal.ZERO, BigDecimal::add);

    // 计算 math 这个对象 里面的两个 BigDecimal 的 加法
    BigDecimal addResult = mathStream
            // 加法
            .map(accessory -> Optional.ofNullable(accessory.getPrice())
                    .orElse(BigDecimal.ZERO).add(Optional.ofNullable(accessory.getQty()).orElse(BigDecimal.ZERO)))
            .reduce(BigDecimal.ZERO, BigDecimal::add);

    System.out.println(multiplyResult);

    System.out.println(addResult);

}

提取出来一个 mathStream ,此时执行第二个使用 mathStream 的时候,就会抛出如下异常:

image.png

Stream has already been operated upon or closed 流已经被操作或关闭

说明我们创建的 Stream 只能使用一次,每次要重新创建,可以使用生产者函数式接口 (Supplier)复用流,因为 Lambda 表达式的特性,我们可以将生成 Stream 的操作放在 Supplier 的 Lambda 表达式中,从而复用。

/**
 * 计算 BigDecimal 的计算
 *
 * @author jy
 * @date 2022/4/11 14:45:25
 */
public static void main(String[] args) {

    ArrayList<Math> maths = new ArrayList<>();

    maths.add(new Math(new BigDecimal("1"), new BigDecimal("13")));

    maths.add(new Math(new BigDecimal("2"), BigDecimal.TEN));

    // 用 lambda 进行封装
    Supplier<Stream<Math>> mathSupplier  = () -> maths.stream()
            // 模拟 过滤操作
            .filter(math -> true);

    // 计算 math 这个对象 里面的两个 BigDecimal 的 乘积
    BigDecimal multiplyResult = mathSupplier.get()
            // 乘法
            .map(accessory -> Optional.ofNullable(accessory.getPrice())
                    .orElse(BigDecimal.ZERO).multiply(Optional.ofNullable(accessory.getQty()).orElse(BigDecimal.ZERO)))
            .reduce(BigDecimal.ZERO, BigDecimal::add);

    // 计算 math 这个对象 里面的两个 BigDecimal 的 加法
    BigDecimal addResult = mathSupplier.get()
            // 加法
            .map(accessory -> Optional.ofNullable(accessory.getPrice())
                    .orElse(BigDecimal.ZERO).add(Optional.ofNullable(accessory.getQty()).orElse(BigDecimal.ZERO)))
            .reduce(BigDecimal.ZERO, BigDecimal::add);

    System.out.println(multiplyResult);

    System.out.println(addResult);

}

通过控制 import * 降低项目打包复杂度

当项目引入了很多同一个包下的类时,idea默认会使用 import * 来注入这个包,会增加打包后的项目体积,在设置中可以设置同一个包使用 import * 注入的限制:

image.png 之前我设置的是 5,是这样的

image.png

设置成 100 以后,是这样的

image.png

MySql中int(10)大小问题

开发中我需要存储一个时间戳在库中,是用来记录延时任务时间间隔的,所以我想使用int(10)存储。

864000000 是 以毫秒为单位 的 10 天,

int 类型的 范围是 23212^{32}-1232+1-2^{32} + 1

所以当我设置更大值的时候,系统自动帮我设定为了

image.png

Lombok 的 @Builder 注解

使用时建议配合 @NoArgsConstructor@AllArgsConstructor 使用,就不存在一系列的奇葩问题了

忽略接口返回的某个参数

  1. 从 swagger 文档中忽略
// 在ApiModelProperty中添加hidden=true
@ApiModelProperty(value = "所属机构", hidden=true)
private String createUserOrgNo;
  1. 不返回这个字段
// 增加 @JsonIgnore注解
@ApiModelProperty(value = "自增序列")
@JsonIgnore
private Integer seq;

OpenFeign遇到的问题

请求 的压缩最大大小的问题

为查询的对象过大,导致传输数据不全,json反序列化失败

下图属性控制的,改稍微大些即可解决

image.png

接口返回列表的时候,Content-Type类型不一致

报错如下:

_Could not extract response: no suitable HttpMessageConverter found for response type [java.util.List<java.util.Ma
p<java.lang.String, java.lang.Object>>] and content type [application/xml;charset=UTF-8]
feign.codec.DecodeException: Could not extract response: no suitable HttpMessageConverter found for response type [java.util.List<java.util.Map<java.lang.String, java.lang.Object>>] and content typ
e [application/xml;charset=UTF-8]

解决办法是将调用方和被调用方的 接口显式指定类型:

被调用方 指定 produces = "application/json;charset=utf-8"


/**
 * 获取 orgGuid 下的 userGuid list
 *
 * @param orgGuid 组织主键
 * @return userGuid list
 */
@IgnoreToken
@ApiVersion
@GetMapping(value = "/{version}/queryUserGuidListByOrgGuid", produces = "application/json;charset=utf-8")
@ApiOperation(value = "queryUserGuidListByOrgGuid", httpMethod = "GET", tags = "获取 orgGuid 下的 userGuid list")
public RestResponse<List<String>> queryUserGuidListByOrgGuid(@RequestParam("orgGuid") String orgGuid) {
    return RestResponse.ok(organizationService.queryUserGuidListByOrgGuid(orgGuid));
}

调用方指定 produces = "application/json;charset=utf-8"

/**
 * 获取 orgGuid 下的 userGuid list
 *
 * @param orgGuid 组织主键
 * @return userGuid list
 */
@GetMapping(value = "/serverapi/organization/v1/queryUserGuidListByOrgGuid", produces = "application/json;charset=utf-8")
RestResponse<List<String>> queryUserGuidListByOrgGuid(@RequestParam("orgGuid") String orgGuid);

Get请求的 @RequestParam

feign调用的 get请求在接口直接加参数的时候需要用该注解,不可不加,否则会抛出异常

参考文章