From b868a73b6e5e675f7f77000bfb4f5ede352505c4 Mon Sep 17 00:00:00 2001 From: Piotr Olaszewski Date: Mon, 11 Jul 2022 22:05:51 +0200 Subject: [PATCH] Add very first money types handling --- hibernate-types-4/pom.xml | 9 ++ .../money/MoneyAmountAndCurrencyType.java | 126 +++++++++++++++++ .../MySQLMoneyAmountAndCurrencyTypeTest.java | 117 ++++++++++++++++ ...tgreSQLMoneyAmountAndCurrencyTypeTest.java | 117 ++++++++++++++++ hibernate-types-43/pom.xml | 9 ++ .../money/MoneyAmountAndCurrencyType.java | 126 +++++++++++++++++ .../MySQLMoneyAmountAndCurrencyTypeTest.java | 117 ++++++++++++++++ ...tgreSQLMoneyAmountAndCurrencyTypeTest.java | 117 ++++++++++++++++ hibernate-types-5/pom.xml | 9 ++ .../money/MoneyAmountAndCurrencyType.java | 126 +++++++++++++++++ .../MySQLMoneyAmountAndCurrencyTypeTest.java | 117 ++++++++++++++++ ...tgreSQLMoneyAmountAndCurrencyTypeTest.java | 117 ++++++++++++++++ hibernate-types-52/pom.xml | 9 ++ .../money/MoneyAmountAndCurrencyType.java | 127 ++++++++++++++++++ .../MySQLMoneyAmountAndCurrencyTypeTest.java | 98 ++++++++++++++ ...tgreSQLMoneyAmountAndCurrencyTypeTest.java | 98 ++++++++++++++ hibernate-types-55/pom.xml | 10 +- .../money/MoneyAmountAndCurrencyType.java | 127 ++++++++++++++++++ .../MySQLMoneyAmountAndCurrencyTypeTest.java | 98 ++++++++++++++ ...tgreSQLMoneyAmountAndCurrencyTypeTest.java | 98 ++++++++++++++ hibernate-types-60/pom.xml | 9 ++ .../money/MoneyAmountAndCurrencyType.java | 92 +++++++++++++ .../MySQLMoneyAmountAndCurrencyTypeTest.java | 93 +++++++++++++ ...tgreSQLMoneyAmountAndCurrencyTypeTest.java | 93 +++++++++++++ 24 files changed, 2058 insertions(+), 1 deletion(-) create mode 100644 hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java create mode 100644 hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java create mode 100644 hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java create mode 100644 hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java create mode 100644 hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java create mode 100644 hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java create mode 100644 hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java create mode 100644 hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java create mode 100644 hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java create mode 100644 hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java create mode 100644 hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java create mode 100644 hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java create mode 100644 hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java create mode 100644 hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java create mode 100644 hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java create mode 100644 hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java diff --git a/hibernate-types-4/pom.xml b/hibernate-types-4/pom.xml index e9e706a2a..7bd17406d 100644 --- a/hibernate-types-4/pom.xml +++ b/hibernate-types-4/pom.xml @@ -47,6 +47,14 @@ true + + org.javamoney + moneta + ${moneta.version} + provided + true + + org.hibernate hibernate-ehcache @@ -70,6 +78,7 @@ 2.7.9.6 12.0 + 1.4.2 diff --git a/hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java b/hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java new file mode 100644 index 000000000..17e2712b0 --- /dev/null +++ b/hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java @@ -0,0 +1,126 @@ +package com.vladmihalcea.hibernate.type.money; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; +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 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 MoneyAmountAndCurrencyType 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 (x == y) || (x != null && x.equals(y)); + } + + @Override + public int hashCode(Object x) throws HibernateException { + return x.hashCode(); + } + + @Override + public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor 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, SessionImplementor 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, SessionImplementor session) throws HibernateException { + return (Serializable) value; + } + + @Override + public Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException { + return cached; + } + + @Override + public Object replace(Object original, Object target, SessionImplementor session, Object owner) throws HibernateException { + return original; + } +} diff --git a/hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java b/hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java new file mode 100644 index 000000000..1f2964cc8 --- /dev/null +++ b/hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java @@ -0,0 +1,117 @@ +package com.vladmihalcea.hibernate.type.money; + +import com.vladmihalcea.hibernate.util.AbstractMySQLIntegrationTest; +import com.vladmihalcea.hibernate.util.transaction.JPATransactionFunction; +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 MySQLMoneyAmountAndCurrencyTypeTest extends AbstractMySQLIntegrationTest { + @Override + protected Class[] entities() { + return new Class[]{ + Salary.class + }; + } + + @Test + public void testPersistAndReadMoney() { + final Salary _salary = doInJPA(new JPATransactionFunction() { + @Override + public Salary apply(EntityManager entityManager) { + Salary salary = new Salary(); + salary.setSalary(Money.of(new BigDecimal("10.23"), "USD")); + + entityManager.persist(salary); + + return salary; + } + }); + + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager entityManager) { + Salary salary = entityManager.find(Salary.class, _salary.getId()); + + assertEquals(salary.getSalary(), Money.of(new BigDecimal("10.23"), "USD")); + + return null; + } + }); + } + + @Test + public void testSearchByMoney() { + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager 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); + + return null; + } + }); + + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager 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()); + + return null; + } + }); + } + + @Entity(name = "Salary") + @Table(name = "salary") + @TypeDef(name = "monetary-amount-currency", typeClass = MoneyAmountAndCurrencyType.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; + } + } +} diff --git a/hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java b/hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java new file mode 100644 index 000000000..916ecc86d --- /dev/null +++ b/hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java @@ -0,0 +1,117 @@ +package com.vladmihalcea.hibernate.type.money; + +import com.vladmihalcea.hibernate.util.AbstractPostgreSQLIntegrationTest; +import com.vladmihalcea.hibernate.util.transaction.JPATransactionFunction; +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 PostgreSQLMoneyAmountAndCurrencyTypeTest extends AbstractPostgreSQLIntegrationTest { + @Override + protected Class[] entities() { + return new Class[]{ + Salary.class + }; + } + + @Test + public void testPersistAndReadMoney() { + final Salary _salary = doInJPA(new JPATransactionFunction() { + @Override + public Salary apply(EntityManager entityManager) { + Salary salary = new Salary(); + salary.setSalary(Money.of(new BigDecimal("10.23"), "USD")); + + entityManager.persist(salary); + + return salary; + } + }); + + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager entityManager) { + Salary salary = entityManager.find(Salary.class, _salary.getId()); + + assertEquals(salary.getSalary(), Money.of(new BigDecimal("10.23"), "USD")); + + return null; + } + }); + } + + @Test + public void testSearchByMoney() { + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager 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); + + return null; + } + }); + + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager 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()); + + return null; + } + }); + } + + @Entity(name = "Salary") + @Table(name = "salary") + @TypeDef(name = "monetary-amount-currency", typeClass = MoneyAmountAndCurrencyType.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; + } + } +} diff --git a/hibernate-types-43/pom.xml b/hibernate-types-43/pom.xml index 0eb0e2236..644ed3902 100644 --- a/hibernate-types-43/pom.xml +++ b/hibernate-types-43/pom.xml @@ -47,6 +47,14 @@ true + + org.javamoney + moneta + ${moneta.version} + provided + true + + org.hibernate hibernate-ehcache @@ -70,6 +78,7 @@ 2.7.9.6 12.0 + 1.4.2 diff --git a/hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java b/hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java new file mode 100644 index 000000000..17e2712b0 --- /dev/null +++ b/hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java @@ -0,0 +1,126 @@ +package com.vladmihalcea.hibernate.type.money; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; +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 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 MoneyAmountAndCurrencyType 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 (x == y) || (x != null && x.equals(y)); + } + + @Override + public int hashCode(Object x) throws HibernateException { + return x.hashCode(); + } + + @Override + public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor 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, SessionImplementor 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, SessionImplementor session) throws HibernateException { + return (Serializable) value; + } + + @Override + public Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException { + return cached; + } + + @Override + public Object replace(Object original, Object target, SessionImplementor session, Object owner) throws HibernateException { + return original; + } +} diff --git a/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java b/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java new file mode 100644 index 000000000..1f2964cc8 --- /dev/null +++ b/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java @@ -0,0 +1,117 @@ +package com.vladmihalcea.hibernate.type.money; + +import com.vladmihalcea.hibernate.util.AbstractMySQLIntegrationTest; +import com.vladmihalcea.hibernate.util.transaction.JPATransactionFunction; +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 MySQLMoneyAmountAndCurrencyTypeTest extends AbstractMySQLIntegrationTest { + @Override + protected Class[] entities() { + return new Class[]{ + Salary.class + }; + } + + @Test + public void testPersistAndReadMoney() { + final Salary _salary = doInJPA(new JPATransactionFunction() { + @Override + public Salary apply(EntityManager entityManager) { + Salary salary = new Salary(); + salary.setSalary(Money.of(new BigDecimal("10.23"), "USD")); + + entityManager.persist(salary); + + return salary; + } + }); + + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager entityManager) { + Salary salary = entityManager.find(Salary.class, _salary.getId()); + + assertEquals(salary.getSalary(), Money.of(new BigDecimal("10.23"), "USD")); + + return null; + } + }); + } + + @Test + public void testSearchByMoney() { + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager 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); + + return null; + } + }); + + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager 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()); + + return null; + } + }); + } + + @Entity(name = "Salary") + @Table(name = "salary") + @TypeDef(name = "monetary-amount-currency", typeClass = MoneyAmountAndCurrencyType.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; + } + } +} diff --git a/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java b/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java new file mode 100644 index 000000000..916ecc86d --- /dev/null +++ b/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java @@ -0,0 +1,117 @@ +package com.vladmihalcea.hibernate.type.money; + +import com.vladmihalcea.hibernate.util.AbstractPostgreSQLIntegrationTest; +import com.vladmihalcea.hibernate.util.transaction.JPATransactionFunction; +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 PostgreSQLMoneyAmountAndCurrencyTypeTest extends AbstractPostgreSQLIntegrationTest { + @Override + protected Class[] entities() { + return new Class[]{ + Salary.class + }; + } + + @Test + public void testPersistAndReadMoney() { + final Salary _salary = doInJPA(new JPATransactionFunction() { + @Override + public Salary apply(EntityManager entityManager) { + Salary salary = new Salary(); + salary.setSalary(Money.of(new BigDecimal("10.23"), "USD")); + + entityManager.persist(salary); + + return salary; + } + }); + + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager entityManager) { + Salary salary = entityManager.find(Salary.class, _salary.getId()); + + assertEquals(salary.getSalary(), Money.of(new BigDecimal("10.23"), "USD")); + + return null; + } + }); + } + + @Test + public void testSearchByMoney() { + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager 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); + + return null; + } + }); + + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager 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()); + + return null; + } + }); + } + + @Entity(name = "Salary") + @Table(name = "salary") + @TypeDef(name = "monetary-amount-currency", typeClass = MoneyAmountAndCurrencyType.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; + } + } +} diff --git a/hibernate-types-5/pom.xml b/hibernate-types-5/pom.xml index 6843db8a2..6d81f6fdf 100644 --- a/hibernate-types-5/pom.xml +++ b/hibernate-types-5/pom.xml @@ -47,6 +47,14 @@ true + + org.javamoney + moneta + ${moneta.version} + provided + true + + org.hibernate hibernate-ehcache @@ -70,6 +78,7 @@ 2.7.9.6 12.0 + 1.4.2 diff --git a/hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java b/hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java new file mode 100644 index 000000000..17e2712b0 --- /dev/null +++ b/hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java @@ -0,0 +1,126 @@ +package com.vladmihalcea.hibernate.type.money; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; +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 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 MoneyAmountAndCurrencyType 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 (x == y) || (x != null && x.equals(y)); + } + + @Override + public int hashCode(Object x) throws HibernateException { + return x.hashCode(); + } + + @Override + public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor 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, SessionImplementor 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, SessionImplementor session) throws HibernateException { + return (Serializable) value; + } + + @Override + public Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException { + return cached; + } + + @Override + public Object replace(Object original, Object target, SessionImplementor session, Object owner) throws HibernateException { + return original; + } +} diff --git a/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java b/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java new file mode 100644 index 000000000..1f2964cc8 --- /dev/null +++ b/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java @@ -0,0 +1,117 @@ +package com.vladmihalcea.hibernate.type.money; + +import com.vladmihalcea.hibernate.util.AbstractMySQLIntegrationTest; +import com.vladmihalcea.hibernate.util.transaction.JPATransactionFunction; +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 MySQLMoneyAmountAndCurrencyTypeTest extends AbstractMySQLIntegrationTest { + @Override + protected Class[] entities() { + return new Class[]{ + Salary.class + }; + } + + @Test + public void testPersistAndReadMoney() { + final Salary _salary = doInJPA(new JPATransactionFunction() { + @Override + public Salary apply(EntityManager entityManager) { + Salary salary = new Salary(); + salary.setSalary(Money.of(new BigDecimal("10.23"), "USD")); + + entityManager.persist(salary); + + return salary; + } + }); + + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager entityManager) { + Salary salary = entityManager.find(Salary.class, _salary.getId()); + + assertEquals(salary.getSalary(), Money.of(new BigDecimal("10.23"), "USD")); + + return null; + } + }); + } + + @Test + public void testSearchByMoney() { + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager 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); + + return null; + } + }); + + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager 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()); + + return null; + } + }); + } + + @Entity(name = "Salary") + @Table(name = "salary") + @TypeDef(name = "monetary-amount-currency", typeClass = MoneyAmountAndCurrencyType.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; + } + } +} diff --git a/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java b/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java new file mode 100644 index 000000000..916ecc86d --- /dev/null +++ b/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java @@ -0,0 +1,117 @@ +package com.vladmihalcea.hibernate.type.money; + +import com.vladmihalcea.hibernate.util.AbstractPostgreSQLIntegrationTest; +import com.vladmihalcea.hibernate.util.transaction.JPATransactionFunction; +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 PostgreSQLMoneyAmountAndCurrencyTypeTest extends AbstractPostgreSQLIntegrationTest { + @Override + protected Class[] entities() { + return new Class[]{ + Salary.class + }; + } + + @Test + public void testPersistAndReadMoney() { + final Salary _salary = doInJPA(new JPATransactionFunction() { + @Override + public Salary apply(EntityManager entityManager) { + Salary salary = new Salary(); + salary.setSalary(Money.of(new BigDecimal("10.23"), "USD")); + + entityManager.persist(salary); + + return salary; + } + }); + + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager entityManager) { + Salary salary = entityManager.find(Salary.class, _salary.getId()); + + assertEquals(salary.getSalary(), Money.of(new BigDecimal("10.23"), "USD")); + + return null; + } + }); + } + + @Test + public void testSearchByMoney() { + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager 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); + + return null; + } + }); + + doInJPA(new JPATransactionFunction() { + @Override + public Void apply(EntityManager 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()); + + return null; + } + }); + } + + @Entity(name = "Salary") + @Table(name = "salary") + @TypeDef(name = "monetary-amount-currency", typeClass = MoneyAmountAndCurrencyType.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; + } + } +} diff --git a/hibernate-types-52/pom.xml b/hibernate-types-52/pom.xml index ae6b6aafc..ab2aafea8 100644 --- a/hibernate-types-52/pom.xml +++ b/hibernate-types-52/pom.xml @@ -46,6 +46,14 @@ true + + org.javamoney + moneta + ${moneta.version} + provided + true + + org.hibernate hibernate-ehcache @@ -74,6 +82,7 @@ 2.12.6.1 2.12.6 29.0-jre + 1.4.2 diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java new file mode 100644 index 000000000..0f84c20c3 --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java @@ -0,0 +1,127 @@ +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 MoneyAmountAndCurrencyType 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; + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java new file mode 100644 index 000000000..b0b00e03b --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java @@ -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 MySQLMoneyAmountAndCurrencyTypeTest 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 = MoneyAmountAndCurrencyType.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; + } + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java new file mode 100644 index 000000000..c23389635 --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java @@ -0,0 +1,98 @@ +package com.vladmihalcea.hibernate.type.money; + +import com.vladmihalcea.hibernate.util.AbstractPostgreSQLIntegrationTest; +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 PostgreSQLMoneyAmountAndCurrencyTypeTest extends AbstractPostgreSQLIntegrationTest { + @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 = MoneyAmountAndCurrencyType.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; + } + } +} diff --git a/hibernate-types-55/pom.xml b/hibernate-types-55/pom.xml index debb4d028..3ddcc6df0 100644 --- a/hibernate-types-55/pom.xml +++ b/hibernate-types-55/pom.xml @@ -46,6 +46,14 @@ true + + org.javamoney + moneta + ${moneta.version} + provided + true + + org.hibernate hibernate-ehcache @@ -73,7 +81,7 @@ 2.12.6.1 2.12.6 29.0-jre - + 1.4.2 diff --git a/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java b/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java new file mode 100644 index 000000000..0f84c20c3 --- /dev/null +++ b/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java @@ -0,0 +1,127 @@ +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 MoneyAmountAndCurrencyType 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; + } +} diff --git a/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java b/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java new file mode 100644 index 000000000..b0b00e03b --- /dev/null +++ b/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java @@ -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 MySQLMoneyAmountAndCurrencyTypeTest 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 = MoneyAmountAndCurrencyType.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; + } + } +} diff --git a/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java b/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java new file mode 100644 index 000000000..c23389635 --- /dev/null +++ b/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java @@ -0,0 +1,98 @@ +package com.vladmihalcea.hibernate.type.money; + +import com.vladmihalcea.hibernate.util.AbstractPostgreSQLIntegrationTest; +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 PostgreSQLMoneyAmountAndCurrencyTypeTest extends AbstractPostgreSQLIntegrationTest { + @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 = MoneyAmountAndCurrencyType.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; + } + } +} diff --git a/hibernate-types-60/pom.xml b/hibernate-types-60/pom.xml index f5e29b30c..53614ad23 100644 --- a/hibernate-types-60/pom.xml +++ b/hibernate-types-60/pom.xml @@ -46,6 +46,14 @@ true + + org.javamoney + moneta + ${moneta.version} + provided + true + + org.hibernate hibernate-jcache @@ -88,6 +96,7 @@ 2.12.6.1 2.12.6 29.0-jre + 1.4.2 3.10.0 diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java new file mode 100644 index 000000000..73ddc97d6 --- /dev/null +++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/money/MoneyAmountAndCurrencyType.java @@ -0,0 +1,92 @@ +package com.vladmihalcea.hibernate.type.money; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.spi.ValueAccess; +import org.hibernate.usertype.CompositeUserType; +import org.javamoney.moneta.Money; + +import javax.money.MonetaryAmount; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Objects; + +/** + * Maps a {@link MonetaryAmount} object type to composite columns (with amount and with currency). + * + * @author Piotr Olaszewski + */ +public class MoneyAmountAndCurrencyType implements CompositeUserType { + public static class MoneyAmountAndCurrencyMapper { + BigDecimal amount; + String currency; + } + + public MoneyAmountAndCurrencyType() { + } + + @Override + public Object getPropertyValue(MonetaryAmount component, int property) throws HibernateException { + // alphabetical (amount, currency) + switch (property) { + case 0: + return component.getNumber().numberValue(BigDecimal.class); + case 1: + return component.getCurrency().getCurrencyCode(); + } + return null; + } + + @Override + public MonetaryAmount instantiate(ValueAccess values, SessionFactoryImplementor sessionFactory) { + // alphabetical (amount, currency) + BigDecimal amount = values.getValue(0, BigDecimal.class); + String currency = values.getValue(1, String.class); + return Money.of(amount, currency); + } + + @Override + public Class embeddable() { + return MoneyAmountAndCurrencyMapper.class; + } + + @Override + public Class returnedClass() { + return MonetaryAmount.class; + } + + @Override + public boolean equals(MonetaryAmount x, MonetaryAmount y) { + return Objects.equals(x, y); + } + + @Override + public int hashCode(MonetaryAmount x) { + return x.hashCode(); + } + + @Override + public MonetaryAmount deepCopy(MonetaryAmount value) { + return value; + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public Serializable disassemble(MonetaryAmount value) { + return (Serializable) value; + } + + @Override + public MonetaryAmount assemble(Serializable cached, Object owner) { + return (MonetaryAmount) cached; + } + + @Override + public MonetaryAmount replace(MonetaryAmount detached, MonetaryAmount managed, Object owner) { + return detached; + } +} diff --git a/hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java b/hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java new file mode 100644 index 000000000..438ce1955 --- /dev/null +++ b/hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMoneyAmountAndCurrencyTypeTest.java @@ -0,0 +1,93 @@ +package com.vladmihalcea.hibernate.type.money; + +import com.vladmihalcea.hibernate.util.AbstractMySQLIntegrationTest; +import jakarta.persistence.*; +import org.hibernate.annotations.CompositeType; +import org.javamoney.moneta.Money; +import org.junit.Test; + +import javax.money.MonetaryAmount; +import java.math.BigDecimal; + +import static org.junit.Assert.assertEquals; + +/** + * @author Piotr Olaszewski + */ +public class MySQLMoneyAmountAndCurrencyTypeTest 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") + public static class Salary { + @Id + @GeneratedValue + private Long id; + + @AttributeOverride(name = "amount", column = @Column(name = "salary_amount")) + @AttributeOverride(name = "currency", column = @Column(name = "salary_currency")) + @CompositeType(MoneyAmountAndCurrencyType.class) + 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; + } + } +} diff --git a/hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java b/hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java new file mode 100644 index 000000000..a8d14afd6 --- /dev/null +++ b/hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMoneyAmountAndCurrencyTypeTest.java @@ -0,0 +1,93 @@ +package com.vladmihalcea.hibernate.type.money; + +import com.vladmihalcea.hibernate.util.AbstractPostgreSQLIntegrationTest; +import jakarta.persistence.*; +import org.hibernate.annotations.CompositeType; +import org.javamoney.moneta.Money; +import org.junit.Test; + +import javax.money.MonetaryAmount; +import java.math.BigDecimal; + +import static org.junit.Assert.assertEquals; + +/** + * @author Piotr Olaszewski + */ +public class PostgreSQLMoneyAmountAndCurrencyTypeTest extends AbstractPostgreSQLIntegrationTest { + @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") + public static class Salary { + @Id + @GeneratedValue + private Long id; + + @AttributeOverride(name = "amount", column = @Column(name = "salary_amount")) + @AttributeOverride(name = "currency", column = @Column(name = "salary_currency")) + @CompositeType(MoneyAmountAndCurrencyType.class) + 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; + } + } +}