Java中如何存储金额的问题

背景分析:
在实际开发过程中,对于金额(一般是元为单位)前端输入一般为小数点两位,比如:1.10,小数点第二位到分。而且数据库的存储粒度可以为分或者元,如果为分,则传入的值需要乘以100。
解决方法:
前端传入的的为小数点2位(小数点合法的位数是2位,大于2位前端和服务端都要校验),服务端这边用Decimal来接收金额类型数据,数据库存储的类型可以为bigInt(此时传入的Double或者Decimal要转为Long),或者为Decimal(此时传入的是Double);
注意:Spring MVC支持参数BigDecimal直接接收整数或者小数点的数
方法一:(传入的是BigDecimal,数据库保存为DECIMAL)

数据库字段定义如下:

`balance` DECIMAL(18, 2) NOT NULL DEFAULT '0.00' COMMENT '账户余额'
方法二:(传入的是BigDecimal,数据库保存为BigInt)
数据库字段定义如下:
`total_amount` bigint(20) NOT NULL DEFAULT '0' COMMENT '总金额(单位: 分)',
服务端校验BigDecimal的小数点为两位的方法:
代码示例如下:
/**
     * 判断输入的值value的小数点数
     * @param value
     * @return
     */
    public static int getDoublePrecision(BigDecimal bigDecimal) {
        String valueStr = bigDecimal.toString();
        int indexOf = valueStr.indexOf(".");
        if (indexOf > 0) {
            doublePrecision = valueStr.length() - 1 -indexOf;
        }
        return doublePrecision;
    }
补充:BigDecimal的用法:
提供一个BigDecimal的加减乘除的用法( 注意:不要用BigDecimal直接接收Double类型的值(使用BigDecimal中的String来接收) )
// 注意:不要用BigDecimal直接接收Double类型的值(使用BigDecimal中的String来接收)
BigDecimal bigDecimal1 = new BigDecimal(a);
log.info("bigDecimal1 is:{}", bigDecimal1);  // 输出:bigDecimal1 is:1.1100000000000000976996261670137755572795867919921875

// 即用BigDecimal中String来接收
BigDecimal bigDecimal2 = new BigDecimal(Double.toString(a));   // 推荐
BigDecimal bigDecimal3 = new BigDecimal(a.toString());
log.info("bigDecimal2 is:{}, bigDecimal3 is:{}", bigDecimal2, bigDecimal3);

/**
 * 加减乘除 demo
 */
// 加法
BigDecimal add1 = new BigDecimal("1.22");
BigDecimal add2 = new BigDecimal("2.33");
BigDecimal bigDecimalAdd = add1.add(add2);
Double valueAdd = bigDecimalAdd.doubleValue();
log.info("BigDecimal add is:{}", valueAdd);

// 减法
BigDecimal sub1 = new BigDecimal("4.55");
BigDecimal sub2 = new BigDecimal("2.13");
BigDecimal bigDecimalSub = sub1.subtract(sub2);
Double valueSub = bigDecimalSub.doubleValue();
log.info("BigDecimal sub is:{}", bigDecimalSub);

// 乘法
BigDecimal mul1 = new BigDecimal("1.33");
BigDecimal mul2 = new BigDecimal("6.41");
BigDecimal bigDecimalMul = mul1.multiply(mul2);
Double valueMul = bigDecimalMul.doubleValue();
log.info("BigDecimal multiply is:{}", valueMul);

// 除法
int scale = 2; // 保留两位小数
BigDecimal div1 = new BigDecimal("3.34");
BigDecimal div2 = new BigDecimal("1.37");
BigDecimal bigDecimalDiv = div1.divide(div2, scale, BigDecimal.ROUND_HALF_UP);   // 四舍五入
Double valueDiv = bigDecimalDiv.doubleValue();
log.info("BigDecimal divide is:{}", bigDecimalDiv.doubleValue());
补充一个基于Java注解BigDecimal精度判断:
用法示例:
@Data
public class ValidParameterVo {

    @BigDecimalMaxPrecision(value = 2, message = "输入金额的最大精度不能超过小数点2位")
    private BigDecimal money;
}
这里用@Validated验证模块,当输入的精度大于2位小数,会提示如下错误:
{
    "data": {
        "errorCode": 1,
        "message": "输入金额的最大精度不能超过小数点2位"
    },
    "status": 1
}
具体注解实现为两个类:BigDecimalMaxPrecision 和 BigDecimalMaxPrecisionValidator.java,代码如下:
BigDecimalMaxPrecision
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = {BigDecimalMaxPrecisionValidator.class})
@Documented
public @interface BigDecimalMaxPrecision {
    
    int value() default 2;
    
    String message() default "{com.sankuai.meituan.donation.common.validate.BigDecimalPrecision.message}";
    
    Class<?>[] groups() default { };
    
    Class<? extends Payload>[] payload() default { };

    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        BigDecimalMaxPrecision[] value();
    }
}
BigDecimalMaxPrecisionValidator.java如下:
import lombok.extern.slf4j.Slf4j;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.math.BigDecimal;

@Slf4j
public class BigDecimalMaxPrecisionValidator implements ConstraintValidator<BigDecimalMaxPrecision, BigDecimal> {

    private int value;
    private String message;
    
    @Override
    public void initialize(BigDecimalMaxPrecision constraintAnnotation) {
        this.value = constraintAnnotation.value();
        this.message = constraintAnnotation.message();
        
    }

    @Override
    public boolean isValid(BigDecimal bigDecimal, ConstraintValidatorContext context) {
        if (bigDecimal == null) {
            return true;
        }
        String bigDecimalStr = bigDecimal.toString();
        int indexOf = bigDecimalStr.indexOf(".");
        int doublePrecision;
        if (indexOf > 0) {
            doublePrecision = bigDecimalStr.length() - 1 -indexOf;
            if (doublePrecision > value) {
                log.warn("input bigDecimal value is:{}, precision is:{}, set max precision is:{}", bigDecimal, doublePrecision, value);
                return false;
            }
        }
        
        return true;
    }
}

猜你喜欢

转载自blog.csdn.net/timchen525/article/details/80933168