Skip to content

Commit

Permalink
Add support for JavaMoney (JSR 354) with the MonetaryAmountType #450
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrooo authored and vladmihalcea committed Jul 21, 2022
1 parent 490a2dd commit 94b8340
Show file tree
Hide file tree
Showing 15 changed files with 986 additions and 0 deletions.
10 changes: 10 additions & 0 deletions hibernate-types-4/pom.xml
Expand Up @@ -47,6 +47,15 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>${moneta.version}</version>
<type>pom</type>
<scope>provided</scope>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
Expand All @@ -70,6 +79,7 @@

<jackson.version>2.7.9.6</jackson.version>
<guava.version>12.0</guava.version>
<moneta.version>1.4.2</moneta.version>
</properties>

<build>
Expand Down
10 changes: 10 additions & 0 deletions hibernate-types-43/pom.xml
Expand Up @@ -47,6 +47,15 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>${moneta.version}</version>
<type>pom</type>
<scope>provided</scope>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
Expand All @@ -70,6 +79,7 @@

<jackson.version>2.7.9.6</jackson.version>
<guava.version>12.0</guava.version>
<moneta.version>1.4.2</moneta.version>
</properties>

<build>
Expand Down
10 changes: 10 additions & 0 deletions hibernate-types-5/pom.xml
Expand Up @@ -47,6 +47,15 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>${moneta.version}</version>
<type>pom</type>
<scope>provided</scope>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
Expand All @@ -70,6 +79,7 @@

<jackson.version>2.7.9.6</jackson.version>
<guava.version>12.0</guava.version>
<moneta.version>1.4.2</moneta.version>
</properties>

<build>
Expand Down
10 changes: 10 additions & 0 deletions hibernate-types-52/pom.xml
Expand Up @@ -46,6 +46,15 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>${moneta.version}</version>
<type>pom</type>
<scope>provided</scope>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
Expand Down Expand Up @@ -74,6 +83,7 @@
<jackson-databind.version>2.12.6.1</jackson-databind.version>
<jackson-module-jaxb-annotation>2.12.6</jackson-module-jaxb-annotation>
<guava.version>31.1-jre</guava.version>
<moneta.version>1.4.2</moneta.version>

</properties>

Expand Down
@@ -0,0 +1,128 @@
package com.vladmihalcea.hibernate.type.money;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.BigDecimalType;
import org.hibernate.type.StringType;
import org.hibernate.type.Type;
import org.hibernate.usertype.CompositeUserType;
import org.javamoney.moneta.Money;

import javax.money.MonetaryAmount;
import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Objects;

import static java.sql.Types.DECIMAL;
import static java.sql.Types.VARCHAR;

/**
* Maps a {@link MonetaryAmount} object type to composite columns (with amount and with currency).
*
* @author Piotr Olaszewski
*/
public class MonetaryAmountType implements CompositeUserType {

@Override
public String[] getPropertyNames() {
return new String[]{"amount", "currency"};
}

@Override
public Type[] getPropertyTypes() {
return new Type[]{BigDecimalType.INSTANCE, StringType.INSTANCE};
}

@Override
public Object getPropertyValue(Object component, int property) throws HibernateException {
MonetaryAmount monetaryAmount = (MonetaryAmount) component;
if (property == 0) {
return monetaryAmount.getNumber().numberValue(BigDecimal.class);
} else if (property == 1) {
return monetaryAmount.getCurrency();
}

throw new IllegalArgumentException("Invalid property index " + property + " for class " + component.getClass());
}

@Override
public void setPropertyValue(Object component, int property, Object value) throws HibernateException {
throw new HibernateException("Call setPropertyValue on immutable type " + component.getClass());
}

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

@Override
public boolean equals(Object x, Object y) throws HibernateException {
return Objects.equals(x, 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 {
if (rs.wasNull()) {
return null;
}

String amountColumnName = names[0];
String currencyColumnName = names[1];

BigDecimal amount = rs.getBigDecimal(amountColumnName);
String currency = rs.getString(currencyColumnName);

return Money.of(amount, currency);
}

@Override
public void nullSafeSet(PreparedStatement st, Object value, int amountColumnIndex, SharedSessionContractImplementor session) throws HibernateException, SQLException {
int currencyColumnIndex = amountColumnIndex + 1;

if (value == null) {
st.setNull(amountColumnIndex, DECIMAL);
st.setNull(currencyColumnIndex, VARCHAR);
} else {
MonetaryAmount monetaryAmount = (MonetaryAmount) value;

BigDecimal amount = monetaryAmount.getNumber().numberValue(BigDecimal.class);
String currency = monetaryAmount.getCurrency().getCurrencyCode();

st.setBigDecimal(amountColumnIndex, amount);
st.setString(currencyColumnIndex, currency);
}
}

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

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

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

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

@Override
public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner) throws HibernateException {
return original;
}
}
@@ -0,0 +1,98 @@
package com.vladmihalcea.hibernate.type.money;

import com.vladmihalcea.hibernate.util.AbstractMySQLIntegrationTest;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.javamoney.moneta.Money;
import org.junit.Test;

import javax.money.MonetaryAmount;
import javax.persistence.*;
import java.math.BigDecimal;

import static org.junit.Assert.assertEquals;

/**
* @author Piotr Olaszewski
*/
public class MySQLMonetaryAmountTypeTest extends AbstractMySQLIntegrationTest {
@Override
protected Class<?>[] entities() {
return new Class[]{
Salary.class
};
}

@Test
public void testPersistAndReadMoney() {
Salary _salary = doInJPA(entityManager -> {
Salary salary = new Salary();
salary.setSalary(Money.of(new BigDecimal("10.23"), "USD"));

entityManager.persist(salary);

return salary;
});

doInJPA(entityManager -> {
Salary salary = entityManager.find(Salary.class, _salary.getId());

assertEquals(salary.getSalary(), Money.of(new BigDecimal("10.23"), "USD"));
});
}

@Test
public void testSearchByMoney() {
doInJPA(entityManager -> {
Salary salary1 = new Salary();
salary1.setSalary(Money.of(new BigDecimal("10.23"), "USD"));
entityManager.persist(salary1);

Salary salary2 = new Salary();
salary2.setSalary(Money.of(new BigDecimal("20.23"), "EUR"));
entityManager.persist(salary2);
});

doInJPA(entityManager -> {
Money money = Money.of(new BigDecimal("10.23"), "USD");
Salary salary = entityManager.createQuery("select s from Salary s where salary = :salary", Salary.class)
.setParameter("salary", money)
.getSingleResult();

assertEquals(Long.valueOf(1), salary.getId());
});
}

@Entity(name = "Salary")
@Table(name = "salary")
@TypeDef(name = "monetary-amount-currency", typeClass = MonetaryAmountType.class, defaultForType = MonetaryAmount.class)
public static class Salary {
@Id
@GeneratedValue
private Long id;

@Columns(columns = {
@Column(name = "salary_amount"),
@Column(name = "salary_currency")
})
@Type(type = "monetary-amount-currency")
private MonetaryAmount salary;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public MonetaryAmount getSalary() {
return salary;
}

public void setSalary(MonetaryAmount salary) {
this.salary = salary;
}
}
}

0 comments on commit 94b8340

Please sign in to comment.