前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >程序员再也不用担心金额转换的问题了

程序员再也不用担心金额转换的问题了

作者头像
胖虎
发布2019-06-26 17:12:11
2K0
发布2019-06-26 17:12:11
举报
文章被收录于专栏:晏霖晏霖

前言

想必同学们在开发当中一定涉及到金钱的属性,一旦涉及到钱就必须要保证不失精度,无论怎么转换一分钱也不能差,如果因为代码的疏忽,金额因为四舍五入或者类型转换时差 1 分的话,想象一下 10000 个订单至少差距 100 块钱,这个损失客户和企业都承担不起。所以今天就来给大家介绍一个工具 Joda,joda有很多种,比如Joda-Money,Jode-Time。今天就来介绍一下SpringBoot中使用Joda-Money来帮助我们解决金额转化问题。

官方文档:https://www.joda.org/joda-money/

正文

使用须知

  • Joda-Money提供了一个存储金额的类库。
  • JDK提供标准货币类,但不是货币的标准表示。Joda-Money填补了这一空白,提供了代表金钱的价值类型。
  • Joda-Money需要Java SE 8或更高版本,并且没有依赖项。

本文的目的不是让大家以后碰到金钱属性的时候使用Joda-Money,而是简单介绍一下在springboot项目中怎么使用而已。我也从朋友那了解到,做金融行业的跟金钱打交道很多,尤其在换算利率的时候必须保证不失精度,通常最好的做法就是数据库设计成bigint类型,单位是分,入库扩大 100 倍 ,出库缩小 100 倍,如果想要更精确就改成 10000倍。当然我也有一些关于钱的转换的一些工具包,如果大家感兴趣可以加微信找我要,这里就不介绍了。

在springboot中使用Joda-Money

Demo使用的是springboot、mybatis、h2、HikariPC、lombok

下面直接贴代码

首先我们想使用Joda-Money,就要有一个依赖joda,目前官网只有一个版本,我会把pom所用到的依赖都贴出来,方便大家看。

代码语言:javascript
复制
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.joda</groupId>
            <artifactId>joda-money</artifactId>
            <version>LATEST</version>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

接下来创建一个商品的model。

代码语言:javascript
复制
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Goods {
    private Long id;
    private String name;
    private Money price;
    private Date createTime;
    private Date updateTime;

}

接下里编写mapper,

目前mybatis都使用注解都方式了,我也喜欢注解都方式,因为我看一个个的xml里的标签有点晕。

如果有哪里的写法不明白请在下方联系作者。

代码语言:javascript
复制
@Mapper
public interface GoodsMapper {
    @Insert("insert into goods (name, price, create_time, update_time)"
            + "values (#{name}, #{price}, now(), now())")
    @Options(useGeneratedKeys = true)
    Long save(Goods goods);

    @Select("select * from goods where id = #{id}")
    @Results({
            @Result(id = true, column = "id", property = "id"),
            @Result(column = "create_time", property = "createTime"),
            // map-underscore-to-camel-case = true 可以实现一样的效果
            // @Result(column = "update_time", property = "updateTime"),
    })
    Goods findById(@Param("id") Long id);
}

下面就到了最重要的核心类了,金额的转换类。

这里有必要简单解释一下BaseTypeHandler

这个类是MyBatis为我们提供来应对Java和jdbc字段类型不匹配的情况。也就是说你model声明的字段和数据库不匹配时需要在这个环节处理,在我们启用了我们自定义的这个TypeHandler之后,数据的读写都会被这个类所过滤。写入通过setNonNullParameter方法过滤,读通过getNullableResult方法过滤,parseMoney方法读作用是读取金额从分转成元。

代码语言:javascript
复制
/**
 * 在 Money 与 Long 之间转换的 TypeHandler,处理 CNY 人民币
 */
public class MoneyTypeHandler extends BaseTypeHandler<Money> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Money parameter, JdbcType jdbcType) throws SQLException {
        System.out.println("setNonNullParameter======"+ps+"--"+i+"--"+parameter+"--"+jdbcType);
        ps.setLong(i, parameter.getAmountMinorLong());
    }

    @Override
    public Money getNullableResult(ResultSet rs, String columnName) throws SQLException {
        System.out.println("getNullableResult======"+rs.toString()+"--"+columnName);
        return parseMoney(rs.getLong(columnName));
    }

    @Override
    public Money getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return parseMoney(rs.getLong(columnIndex));
    }

    @Override
    public Money getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return parseMoney(cs.getLong(columnIndex));
    }

    private Money parseMoney(Long value) {
        System.out.println("value===="+value);
        return Money.of(CurrencyUnit.of("CNY"), value / 100.0);
    }
}

然后我们编写启动类

小小说明

  • 在项目启动的时候立刻执行某些方法。我们可以通过实现ApplicationRunner和CommandLineRunner,来实现,他们都是在SpringApplication 执行之后开始执行的。
  • 我们通过Money.of将金额单位和金额传进去。
  • Money有很多方法可以使用,但是我们最常用他来处理金额转换。
代码语言:javascript
复制
@SpringBootApplication
@MapperScan("cn.mapper")
@Slf4j
public class MoneyDemoApplication implements ApplicationRunner {
    @Autowired
    private GoodsMapper goodsMapper;

    public static void main(String[] args) {
        SpringApplication.run(MoneyDemoApplication.class, args);

    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Goods goods = Goods.builder().name("白桃薯条").price(Money.of(CurrencyUnit.of("CNY"), 20)).build();
        Long id = goodsMapper.save(goods);
        log.info("Goods {} => {}", id, goods);

        goods = goodsMapper.findById(id);
        log.info("Goods {}", goods);
    }
}

因为写demo我就没有去创建实体数据库,我采用的java关系型数据库h2,所以会多出来一个创建表的配置类

代码语言:javascript
复制
create table goods (
    id bigint not null auto_increment,
    name varchar(255),
    price bigint not null,
    create_time timestamp,
    update_time timestamp,
    primary key (id)
);

还有一个配置类

  • 第一个配置是指定handler包位置。
  • 第二个配置如果开启那么你实体里和数据库对于下划线的处理,例如create_time 和createTime是对等的,我在前面mapper代码注释写过。
代码语言:javascript
复制
mybatis.type-handlers-package=cn.handler
mybatis.configuration.map-underscore-to-camel-case=true

项目结构如下

一切准备就绪就让我们启动项目吧

控制台信息如下,截图比较小,我上面在typeHandler中输出里value的值是2000,但是通过我的joda 和typehandler的结合可以方便的对金额进行转换了。

截图中我们可以看得出来,springboot2.0后默认使用的是HikariPC的数据库连接池,据说比druid性能更好,感兴趣的可以了解下。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-03-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 晏霖 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 正文
    • 在springboot中使用Joda-Money
    相关产品与服务
    数据库
    云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档