Open uded opened 6 years ago
Which mapping did you try, Jadira?
First of all, SQL has a CAST operator which can be used to convert varchar to number so you’ll be able to use agregated function SUM, MIN etc. But better not to do that and use a number field and then use Jadira Usertypes library to map the field to a Money object. See http://jadira.sourceforge.net/usertype-userguide.html for details. I don’t see that this issue is related to moneta library itself, so let’s close it
Not sure, how you came to SourceForge with Jadira, maybe it still uses that for downloads, but the Issue tracker is https://github.com/JadiraOrg/jadira/issues. Would someone consider filing an issue there?
This is not an issue. It looks like uded wanted to use hibernate’s composition mapping which needs for setters. Jadira will help you.
Jadira is a good solution. If you don't want to use it, you could just add this class
public class PersistentMoneyAmountAndCurrency implements CompositeUserType { public String[] getPropertyNames() { // ORDER IS IMPORTANT! it must match the order the columns are defined in the property mapping return new String[]{"currency", "amount"}; } public Type[] getPropertyTypes() { return new Type[]{StringType.INSTANCE, BigDecimalType.INSTANCE}; } @Override public Class returnedClass() { return Money.class; } public Object getPropertyValue(Object component, int propertyIndex) { if (component == null) { return null; } final Money money = (Money) component; switch (propertyIndex) { case 0: return money.getCurrency().getCurrencyCode(); case 1: return money.getNumber().numberValue(BigDecimal.class); default: throw new HibernateException("Invalid property index [" + propertyIndex + "]"); } } public void setPropertyValue(Object component, int propertyIndex, Object value) { if (component == null) { return; } throw new HibernateException("Called setPropertyValue on an immutable type {" + component.getClass() + "}"); } @Override public Object nullSafeGet(ResultSet resultSet, String[] names, SharedSessionContractImplementor session, Object object) throws SQLException { assert names.length == 2; //owner here is of type TestUser or the actual owning Object Money money = null; final String currency = resultSet.getString(names[0]); //Deferred check after first read if (!resultSet.wasNull()) { final BigDecimal amount = resultSet.getBigDecimal(names[1]); money = (Money) MoneyUtils.amount(amount, currency); } return money; } @Override public void nullSafeSet(PreparedStatement preparedStatement, Object value, int property, SharedSessionContractImplementor session) throws SQLException { if (null == value) { preparedStatement.setNull(property, StringType.INSTANCE.sqlType()); preparedStatement.setNull(property + 1, BigDecimalType.INSTANCE.sqlType()); } else { final Money amount = (Money) value; preparedStatement.setString(property, amount.getCurrency().getCurrencyCode()); preparedStatement.setBigDecimal(property + 1, amount.getNumber().numberValue(BigDecimal.class)); } } /** * Used while dirty checking - control passed on to the {@link MonetaryAmount} */ @Override public boolean equals(final Object o1, final Object o2) { return Objects.equals(o1, o2); } @Override public int hashCode(final Object value) { return value.hashCode(); } /** * Helps hibernate apply certain optimizations for immutable objects */ @Override public boolean isMutable() { return false; } /** * Used to create Snapshots of the object */ @Override public Object deepCopy(final Object value) { return value; //if object was immutable we could return the object as its is } /** * method called when Hibernate puts the data in a second level cache. The data is stored * in a serializable form */ @Override public Serializable disassemble(final Object value, final SharedSessionContractImplementor paramSessionImplementor) { //Thus the data Types must implement serializable return (Serializable) value; } /** * Returns the object from the 2 level cache */ @Override public Object assemble(final Serializable cached, final SharedSessionContractImplementor sessionImplementor, final Object owner) { //would work as the class is Serializable, and stored in cache as it is - see disassemble return cached; } /** * Method is called when merging two objects. */ @Override public Object replace(final Object original, final Object target, final SharedSessionContractImplementor paramSessionImplementor, final Object owner) { return original; // if immutable use this } }
Then use it like this in your entity
@TypeDef(name = "persistentMoneyAmountAndCurrency", typeClass = PersistentMoneyAmountAndCurrency.class)
@Columns(columns = {@Column(name = "amount_currency", length = 3), @Column(name = "amount_value", precision = 19, scale = 5)})
@Type(type = "persistentMoneyAmountAndCurrency")
private MonetaryAmount amount;
In orousseil's comment. Does anyone know what an equivalent to this statement is
money = (Money) MoneyUtils.amount(amount, currency);
javamoney.moneta.spi.MoneyUtils does not seem to have an amount method?
Edit: Of course this should be:
money = Money.of(amount, currency);
Kindly, Greg
@rollenwiese Would this new method in MoneyUtils help with Hibernate? I'm almost inclined to move it to another repo, especially @orousseil's older comment sounds like something we may cover e.g. in javamoney-lib or a similar place.
On the code I posted, I was using my own MoneyUtils
class. That's why @rollenwiese sayed you have to use money = Money.of(amount, currency)
instead. Yes the class PersistentMoneyAmountAndCurrency
could be in another lib coz it's specific to Hibernate, and not really java money.
It would be such a great if there will be official Hibernate Custom type implementation in this package. 🙏
At least hibernate-validator already supports validation of MonetaryAmount
, so why not ask the makers of hibernate-orm about it, too. Until then Jadira might be the way to go, or ask @vladmihalcea about additional types in his hibernate-types, which seems a little more active than Jadira lately.
The Hibernate Types project is OSS. Anyone can provide new Types. The same with Hibernate. If you wait for the core maintainers to implement all possible features, you will have to wait a very very long time.
Same goes for the core maintainers of the Money JSR which is effectively in Maintenance mode ;-) So pointing to hibernate-types was mainly a possible alternative to Jadira, if @landsman or others involved in this thread were able to help those libraries, it would be great.
OK, I went through the API as much as I could and as I do fully understand why it is created the way it is - it causes a problem for JPA/Hibernate mapping.
The easiest and, possibly, the safest way is to use
toString
/Parse
to represent the amount and currency in the DB. But this is far from perfect if one would like, well I do not know... like operate with SQL on this representation?sum
,min
,max
... none will work onVARCHAR
the way I would like to. But I do not see alternatives as I do not have setters to operate by property index on the object. Meaning either I have all data or I won't create an object that I can pass on by reference... or maybe I am missing something.Any bits of advice? Thoughts on this? I would love to store FastMoney as two separate columns, but I do not see a reasonable option for this...