You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Hibernate正确映射MonetaryAmount?解决MySQL映射报错问题

解决Hibernate映射javax.money.MonetaryAmount的问题

你遇到的错误是因为Hibernate默认不认识MonetaryAmount这个自定义类型,没办法自动将它映射到MySQL的列。下面给你几种可行的解决方案,从简单到灵活按需选择:

方案1:拆分字段(最直接,无需额外依赖)

把MonetaryAmount拆成数据库原生支持的两个字段:金额数值(BigDecimal)和货币代码(String),然后在实体类里封装成MonetaryAmount的getter/setter:

@Entity
public class Expenditure implements Comparable<Expenditure> {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    private String description;
    
    // 数据库直接存储的字段
    @Column(precision = 19, scale = 4) // 根据业务调整精度
    private BigDecimal amount;
    @Column(length = 3) // ISO 4217货币代码是3位
    private String currencyCode;
    
    private LocalDate date;
    private ExpenditureType type;
    @OneToOne
    private User client;

    // 封装成MonetaryAmount的访问方法
    public MonetaryAmount getMonetaryAmount() {
        if (amount == null || currencyCode == null) {
            return null;
        }
        return Monetary.getDefaultAmountFactory()
                .setNumber(amount)
                .setCurrency(currencyCode)
                .create();
    }

    public void setMonetaryAmount(MonetaryAmount monetaryAmount) {
        if (monetaryAmount == null) {
            this.amount = null;
            this.currencyCode = null;
            return;
        }
        this.amount = monetaryAmount.getNumber().numberValue(BigDecimal.class);
        this.currencyCode = monetaryAmount.getCurrency().getCurrencyCode();
    }

    // 其他原有方法...
    @Override
    public int compareTo(Expenditure o) {
        // 你的实现
        return 0;
    }
}

这种方式不需要自定义Hibernate类型,代码简单易维护,适合大多数场景。

方案2:使用@Embedded嵌入类(结构化封装)

如果想保持MonetaryAmount的封装性,可以把金额和货币代码封装成一个可嵌入的类,再映射到实体中:

首先创建嵌入类:

@Embeddable
public class MonetaryAmountEmbeddable {
    @Column(precision = 19, scale = 4)
    private BigDecimal amount;
    @Column(length = 3)
    private String currencyCode;

    // Hibernate需要无参构造函数
    public MonetaryAmountEmbeddable() {}

    // 从MonetaryAmount构造
    public MonetaryAmountEmbeddable(MonetaryAmount monetaryAmount) {
        if (monetaryAmount != null) {
            this.amount = monetaryAmount.getNumber().numberValue(BigDecimal.class);
            this.currencyCode = monetaryAmount.getCurrency().getCurrencyCode();
        }
    }

    // 转换回MonetaryAmount
    public MonetaryAmount toMonetaryAmount() {
        if (amount == null || currencyCode == null) {
            return null;
        }
        return Monetary.getDefaultAmountFactory()
                .setNumber(amount)
                .setCurrency(currencyCode)
                .create();
    }

    // Getters和Setters(Hibernate需要)
    public BigDecimal getAmount() { return amount; }
    public void setAmount(BigDecimal amount) { this.amount = amount; }
    public String getCurrencyCode() { return currencyCode; }
    public void setCurrencyCode(String currencyCode) { this.currencyCode = currencyCode; }
}

然后修改Expenditure类:

@Entity
public class Expenditure implements Comparable<Expenditure> {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    private String description;
    
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "amount", column = @Column(name = "expense_amount")),
        @AttributeOverride(name = "currencyCode", column = @Column(name = "currency_code"))
    })
    private MonetaryAmountEmbeddable monetaryAmountWrapper;
    
    private LocalDate date;
    private ExpenditureType type;
    @OneToOne
    private User client;

    // 封装MonetaryAmount的访问方法
    public MonetaryAmount getMonetaryAmount() {
        return monetaryAmountWrapper != null ? monetaryAmountWrapper.toMonetaryAmount() : null;
    }

    public void setMonetaryAmount(MonetaryAmount monetaryAmount) {
        this.monetaryAmountWrapper = new MonetaryAmountEmbeddable(monetaryAmount);
    }

    // 其他原有方法...
    @Override
    public int compareTo(Expenditure o) {
        // 你的实现
        return 0;
    }
}

这种方式把金额相关逻辑封装在单独的类里,代码更整洁,适合复杂业务场景。

方案3:使用自定义Hibernate UserType(灵活但需额外代码)

如果需要更灵活的映射逻辑,可以实现Hibernate的UserType接口,自定义类型转换规则:

public class MonetaryAmountUserType implements UserType {

    @Override
    public int[] sqlTypes() {
        // 返回对应的SQL类型:BigDecimal和VARCHAR
        return new int[]{Types.NUMERIC, Types.VARCHAR};
    }

    @Override
    public Class<?> returnedClass() {
        return MonetaryAmount.class;
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        if (x == y) return true;
        if (x == null || y == null) return false;
        return x.equals(y);
    }

    @Override
    public int hashCode(Object x) throws HibernateException {
        return x.hashCode();
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
        BigDecimal amount = rs.getBigDecimal(names[0]);
        String currencyCode = rs.getString(names[1]);
        if (rs.wasNull()) {
            return null;
        }
        return Monetary.getDefaultAmountFactory()
                .setNumber(amount)
                .setCurrency(currencyCode)
                .create();
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, Types.NUMERIC);
            st.setNull(index + 1, Types.VARCHAR);
            return;
        }
        MonetaryAmount amount = (MonetaryAmount) value;
        st.setBigDecimal(index, amount.getNumber().numberValue(BigDecimal.class));
        st.setString(index + 1, amount.getCurrency().getCurrencyCode());
    }

    @Override
    public Object deepCopy(Object value) throws HibernateException {
        // MonetaryAmount是不可变的,直接返回即可
        return value;
    }

    @Override
    public boolean isMutable() {
        return false;
    }

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) value;
    }

    @Override
    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return cached;
    }

    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }
}

然后在实体类的字段上指定这个自定义类型:

@Entity
@TypeDef(name = "monetaryAmount", typeClass = MonetaryAmountUserType.class)
public class Expenditure implements Comparable<Expenditure> {
    // ...其他字段
    
    @Type(type = "monetaryAmount")
    @Columns(columns = {
        @Column(name = "amount"),
        @Column(name = "currency_code")
    })
    private MonetaryAmount monetaryAmount;
    
    // ...其他方法
}

这种方式最灵活,但需要编写和维护自定义类型的代码,适合特殊业务需求。

方案4:使用第三方Hibernate类型库(快捷省心)

如果不想自己写自定义类型,可以使用第三方库(比如hibernate-types),它已经实现了MonetaryAmount的映射:

  1. 首先添加Maven依赖(版本根据你的Hibernate版本调整):
<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>2.19.0</version>
</dependency>
  1. 在实体类上注册类型并使用:
@Entity
@TypeDef(name = "monetary-amount", typeClass = MonetaryAmountType.class)
public class Expenditure implements Comparable<Expenditure> {
    // ...其他字段
    
    @Type(type = "monetary-amount")
    @Columns(columns = {
        @Column(name = "amount"),
        @Column(name = "currency")
    })
    private MonetaryAmount monetaryAmount;
    
    // ...其他方法
}

这种方式最省心,不用自己写类型转换代码,适合快速开发。


内容的提问来源于stack exchange,提问作者janlan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 03:54:39