diff --git a/hibernate-types-4/pom.xml b/hibernate-types-4/pom.xml
index e9e706a2a..838459696 100644
--- a/hibernate-types-4/pom.xml
+++ b/hibernate-types-4/pom.xml
@@ -47,6 +47,15 @@
true
+
+ org.javamoney
+ moneta
+ ${moneta.version}
+ pom
+ provided
+ true
+
+
org.hibernate
hibernate-ehcache
@@ -70,6 +79,7 @@
2.7.9.6
12.0
+ 1.4.2
diff --git a/hibernate-types-43/pom.xml b/hibernate-types-43/pom.xml
index 0eb0e2236..70e488784 100644
--- a/hibernate-types-43/pom.xml
+++ b/hibernate-types-43/pom.xml
@@ -47,6 +47,15 @@
true
+
+ org.javamoney
+ moneta
+ ${moneta.version}
+ pom
+ provided
+ true
+
+
org.hibernate
hibernate-ehcache
@@ -70,6 +79,7 @@
2.7.9.6
12.0
+ 1.4.2
diff --git a/hibernate-types-5/pom.xml b/hibernate-types-5/pom.xml
index 6843db8a2..5c329f4b9 100644
--- a/hibernate-types-5/pom.xml
+++ b/hibernate-types-5/pom.xml
@@ -47,6 +47,15 @@
true
+
+ org.javamoney
+ moneta
+ ${moneta.version}
+ pom
+ provided
+ true
+
+
org.hibernate
hibernate-ehcache
@@ -70,6 +79,7 @@
2.7.9.6
12.0
+ 1.4.2
diff --git a/hibernate-types-52/pom.xml b/hibernate-types-52/pom.xml
index d1757ead2..e603650db 100644
--- a/hibernate-types-52/pom.xml
+++ b/hibernate-types-52/pom.xml
@@ -46,6 +46,15 @@
true
+
+ org.javamoney
+ moneta
+ ${moneta.version}
+ pom
+ provided
+ true
+
+
org.hibernate
hibernate-ehcache
@@ -74,6 +83,7 @@
2.12.6.1
2.12.6
31.1-jre
+ 1.4.2
diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/money/MonetaryAmountType.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/money/MonetaryAmountType.java
new file mode 100644
index 000000000..096afb1c5
--- /dev/null
+++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/money/MonetaryAmountType.java
@@ -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;
+ }
+}
diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMonetaryAmountTypeTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMonetaryAmountTypeTest.java
new file mode 100644
index 000000000..34ce9c1ce
--- /dev/null
+++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMonetaryAmountTypeTest.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 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;
+ }
+ }
+}
diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMonetaryAmountTypeTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMonetaryAmountTypeTest.java
new file mode 100644
index 000000000..c537e68b5
--- /dev/null
+++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMonetaryAmountTypeTest.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 PostgreSQLMonetaryAmountTypeTest 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 = 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;
+ }
+ }
+}
diff --git a/hibernate-types-55/pom.xml b/hibernate-types-55/pom.xml
index e74fc5551..8fba02afb 100644
--- a/hibernate-types-55/pom.xml
+++ b/hibernate-types-55/pom.xml
@@ -46,6 +46,15 @@
true
+
+ org.javamoney
+ moneta
+ ${moneta.version}
+ pom
+ provided
+ true
+
+
org.hibernate
hibernate-ehcache
@@ -73,6 +82,7 @@
2.12.6.1
2.12.6
31.1-jre
+ 1.4.2
diff --git a/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/money/MonetaryAmountType.java b/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/money/MonetaryAmountType.java
new file mode 100644
index 000000000..096afb1c5
--- /dev/null
+++ b/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/money/MonetaryAmountType.java
@@ -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;
+ }
+}
diff --git a/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMonetaryAmountTypeTest.java b/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMonetaryAmountTypeTest.java
new file mode 100644
index 000000000..34ce9c1ce
--- /dev/null
+++ b/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMonetaryAmountTypeTest.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 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;
+ }
+ }
+}
diff --git a/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMonetaryAmountTypeTest.java b/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMonetaryAmountTypeTest.java
new file mode 100644
index 000000000..c537e68b5
--- /dev/null
+++ b/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMonetaryAmountTypeTest.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 PostgreSQLMonetaryAmountTypeTest 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 = 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;
+ }
+ }
+}
diff --git a/hibernate-types-60/pom.xml b/hibernate-types-60/pom.xml
index 7d649907f..9d8290880 100644
--- a/hibernate-types-60/pom.xml
+++ b/hibernate-types-60/pom.xml
@@ -46,6 +46,15 @@
true
+
+ org.javamoney
+ moneta
+ ${moneta.version}
+ pom
+ provided
+ true
+
+
org.hibernate
hibernate-jcache
@@ -88,6 +97,7 @@
2.12.6.1
2.12.6
31.1-jre
+ 1.4.2
3.10.0
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/money/MonetaryAmountType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/money/MonetaryAmountType.java
new file mode 100644
index 000000000..0e1ae6838
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/money/MonetaryAmountType.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 MonetaryAmountType implements CompositeUserType {
+ public static class MonetaryAmountMapper {
+ BigDecimal amount;
+ String currency;
+ }
+
+ public MonetaryAmountType() {
+ }
+
+ @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 MonetaryAmountMapper.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/MySQLMonetaryAmountTypeTest.java b/hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMonetaryAmountTypeTest.java
new file mode 100644
index 000000000..7da4bd3a0
--- /dev/null
+++ b/hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/money/MySQLMonetaryAmountTypeTest.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 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")
+ 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(MonetaryAmountType.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/PostgreSQLMonetaryAmountTypeTest.java b/hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMonetaryAmountTypeTest.java
new file mode 100644
index 000000000..7778c7794
--- /dev/null
+++ b/hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/money/PostgreSQLMonetaryAmountTypeTest.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 PostgreSQLMonetaryAmountTypeTest 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(MonetaryAmountType.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;
+ }
+ }
+}