如何使用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的映射:
- 首先添加Maven依赖(版本根据你的Hibernate版本调整):
<dependency> <groupId>com.vladmihalcea</groupId> <artifactId>hibernate-types-52</artifactId> <version>2.19.0</version> </dependency>
- 在实体类上注册类型并使用:
@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
相关产品推荐
相关产品推荐

