Search code examples
javaspring-bootspring-data-jpajava-money

Convert @Query MonetaryAmount result


I just converted all of my java.math.BigDecimal usages to javax.money.MonetaryAmount (where they represented money). I am currently "assuming" the currency to be USD with the help of a MoneyUtils to avoid adding extra database columns that will only contain "USD" in them. This can be seen in the next 2 snippets.

@Converter(autoApply = true)
public class MonetaryAmountConverter
        implements AttributeConverter<MonetaryAmount, BigDecimal> {

    @Override
    public BigDecimal convertToDatabaseColumn(final MonetaryAmount monetaryAmount) {
        return DefaultUtils.defaultOrNull(
                monetaryAmount,
                ma -> ma.getNumber().numberValue(BigDecimal.class));
    }

    @Override
    public MonetaryAmount convertToEntityAttribute(final BigDecimal monetaryAmountValue) {
        if (monetaryAmountValue == null) {
            return null;
        } else {
            return MoneyUtils.of(monetaryAmountValue);
        }
    }
}
public final class MoneyUtils {
    public static final CurrencyUnit DOLLAR = Monetary.getCurrency(Locale.US);
    public static MonetaryAmount of(@NonNull final BigDecimal value) {
        return Money.of(value, MoneyUtils.DOLLAR);
    }
}

I have this @Repository method that should give me a sum, which could be null if no rows match:

@Query("query that can return null or a numeric")
MonetaryAmount sumSomeData();

The problem is that it doesn't go through my MonetaryAmountConverter. I tried adding a @Convert directly to the method but it didn't work.

@Query("query that can return null or a numeric")
@Convert(converter = MonetaryAmountConverter.class)
MonetaryAmount sumSomeData();

So it appears that an AttributeConverter with @Converter(autoApply = true) applies to JPA Entity fields, but not to method return types, even when @Convert is added to the method signature.

  • Is there something I am missing?
  • Is there a way to do this without needing to do the conversion manually in the caller of this repository method?

Solution

  • You can use a constructor expression:

    The SELECT clause identifies which objects and values to return as the query results. The expressions discussed in Section 11.4, “Expressions” are all valid select expressions, except where otherwise noted. See the section Section 11.10, “Query API” for information on handling the results depending on the types of values specified in the SELECT clause.

    There is a particular expression type that is only valid in the select clause. Hibernate calls this “dynamic instantiation”. JPQL supports some of that feature and calls it a “constructor expression”

    General example:

    select new Person(user.name, address)
    from User as user
    left join user.address as address
    

    Before you ask, no, you cannot call methods, only constructors. Also, you'll need to consider the fact that your query may return null. You could write a wrapper class.

    public final class MonetaryAmountWrapper {
      public MonetaryAmountWrapper(final BigDecimal value) {
        this.monetaryAmount = Money.of(value, MoneyUtils.DOLLAR);
      }
    
      private final MonetaryAmount monetaryAmount;
    }