diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/ImmutableCompositeType.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/ImmutableCompositeType.java
new file mode 100644
index 000000000..8d3ddab99
--- /dev/null
+++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/ImmutableCompositeType.java
@@ -0,0 +1,328 @@
+package com.vladmihalcea.hibernate.type;
+
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.HibernateException;
+import org.hibernate.MappingException;
+import org.hibernate.engine.jdbc.Size;
+import org.hibernate.engine.spi.Mapping;
+import org.hibernate.engine.spi.SessionFactoryImplementor;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.hibernate.internal.util.collections.ArrayHelper;
+import org.hibernate.type.ForeignKeyDirection;
+import org.hibernate.type.Type;
+import org.hibernate.type.descriptor.java.IncomparableComparator;
+import org.hibernate.usertype.CompositeUserType;
+
+import java.io.Serializable;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Very convenient base class for implementing immutable object types using Hibernate {@link CompositeUserType}.
+ *
+ * The {@link ImmutableCompositeType} implements the {@link Type} interface too, so you can pass all
+ * types extending the {@link ImmutableCompositeType} to the {@link org.hibernate.query.NativeQuery#addScalar(String, Type)}
+ * method to fix the No Dialect mapping for JDBC type issues.
+ *
+ * @author Vlad Mihalcea
+ */
+public abstract class ImmutableCompositeType implements CompositeUserType, Type {
+
+ private final Configuration configuration;
+
+ private final Class clazz;
+
+ /**
+ * Initialization constructor taking the {@link Class}
+ * and using the default {@link Configuration} object.
+ *
+ * @param clazz the entity attribute {@link Class} type to be handled
+ */
+ protected ImmutableCompositeType(Class clazz) {
+ this.clazz = clazz;
+ this.configuration = Configuration.INSTANCE;
+ }
+
+ /**
+ * Initialization constructor taking the {@link Class} and {@link Configuration} objects.
+ *
+ * @param clazz the entity attribute {@link Class} type to be handled
+ * @param configuration custom {@link Configuration} object.
+ */
+ protected ImmutableCompositeType(Class clazz, Configuration configuration) {
+ this.clazz = clazz;
+ this.configuration = configuration;
+ }
+
+ /**
+ * Get the current {@link Configuration} object.
+ *
+ * @return the current {@link Configuration} object.
+ */
+ protected Configuration getConfiguration() {
+ return configuration;
+ }
+
+ /**
+ * Get the column value from the JDBC {@link ResultSet}.
+ *
+ * @param rs JDBC {@link ResultSet}
+ * @param names database column name
+ * @param session current Hibernate {@link org.hibernate.Session}
+ * @param owner current Hibernate {@link SessionFactoryImplementor}
+ * @return column value
+ * @throws SQLException in case of failure
+ */
+ protected abstract T get(ResultSet rs, String[] names,
+ SharedSessionContractImplementor session, Object owner) throws SQLException;
+
+ /**
+ * Set the column value on the provided JDBC {@link PreparedStatement}.
+ *
+ * @param st JDBC {@link PreparedStatement}
+ * @param value database column value
+ * @param index database column index
+ * @param session current Hibernate {@link org.hibernate.Session}
+ * @throws SQLException in case of failure
+ */
+ protected abstract void set(PreparedStatement st, T value, int index,
+ SharedSessionContractImplementor session) throws SQLException;
+
+ /* Methods inherited from the {@link UserType} interface */
+
+ @Override
+ public Object nullSafeGet(ResultSet rs, String[] names,
+ SharedSessionContractImplementor session, Object owner) throws SQLException {
+ return get(rs, names, session, owner);
+ }
+
+ @Override
+ public void nullSafeSet(PreparedStatement st, Object value, int index,
+ SharedSessionContractImplementor session) throws SQLException {
+ set(st, clazz.cast(value), index, session);
+ }
+
+ @Override
+ public Class returnedClass() {
+ return clazz;
+ }
+
+ @Override
+ public boolean equals(Object x, Object y) {
+ return (x == y) || (x != null && x.equals(y));
+ }
+
+ @Override
+ public int hashCode(Object x) {
+ return x.hashCode();
+ }
+
+ @Override
+ public Object deepCopy(Object value) {
+ return value;
+ }
+
+ @Override
+ public boolean isMutable() {
+ return false;
+ }
+
+ @Override
+ public Serializable disassemble(Object o, SharedSessionContractImplementor session) {
+ return (Serializable) o;
+ }
+
+ @Override
+ public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException {
+ return cached;
+ }
+
+ @Override
+ public Object replace(Object o, Object target, SharedSessionContractImplementor session, Object owner) {
+ return o;
+ }
+
+ /* Methods inherited from the {@link Type} interface */
+
+ @Override
+ public boolean isAssociationType() {
+ return false;
+ }
+
+ @Override
+ public boolean isCollectionType() {
+ return false;
+ }
+
+ @Override
+ public boolean isEntityType() {
+ return false;
+ }
+
+ @Override
+ public boolean isAnyType() {
+ return false;
+ }
+
+ @Override
+ public boolean isComponentType() {
+ return false;
+ }
+
+ @Override
+ public int getColumnSpan(Mapping mapping) throws MappingException {
+ return getPropertyTypes().length;
+ }
+
+ @Override
+ public int[] sqlTypes(Mapping mapping) throws MappingException {
+ List sqlTypes= new ArrayList<>();
+ Type[] types = getPropertyTypes();
+ for (int i = 0; i < types.length; i++) {
+ sqlTypes.addAll(
+ Arrays.stream(types[i].sqlTypes(mapping)).boxed().collect(Collectors.toList())
+ );
+ }
+ return sqlTypes.stream().mapToInt(i->i).toArray();
+ }
+
+ @Override
+ public Size[] dictatedSizes(Mapping mapping) throws MappingException {
+ return new Size[]{new Size()};
+ }
+
+ @Override
+ public Size[] defaultSizes(Mapping mapping) throws MappingException {
+ return dictatedSizes(mapping);
+ }
+
+ @Override
+ public Class getReturnedClass() {
+ return returnedClass();
+ }
+
+ @Override
+ public boolean isSame(Object x, Object y) throws HibernateException {
+ return equals(x, y);
+ }
+
+ @Override
+ public boolean isEqual(Object x, Object y) throws HibernateException {
+ return equals(x, y);
+ }
+
+ @Override
+ public boolean isEqual(Object x, Object y, SessionFactoryImplementor factory) throws HibernateException {
+ return equals(x, y);
+ }
+
+ @Override
+ public int getHashCode(Object x) throws HibernateException {
+ return hashCode(x);
+ }
+
+ @Override
+ public int getHashCode(Object x, SessionFactoryImplementor factory) throws HibernateException {
+ return hashCode(x);
+ }
+
+ @Override
+ public int compare(Object x, Object y) {
+ return IncomparableComparator.INSTANCE.compare(x, y);
+ }
+
+ @Override
+ public final boolean isDirty(Object old, Object current, SharedSessionContractImplementor session) {
+ return isDirty(old, current);
+ }
+
+ @Override
+ public final boolean isDirty(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session) {
+ return checkable[0] && isDirty(old, current);
+ }
+
+ protected final boolean isDirty(Object old, Object current) {
+ return !isSame(old, current);
+ }
+
+ @Override
+ public boolean isModified(Object dbState, Object currentState, boolean[] checkable, SharedSessionContractImplementor session) throws HibernateException {
+ return isDirty(dbState, currentState);
+ }
+
+ @Override
+ public Object nullSafeGet(ResultSet rs, String name, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
+ return get(rs, new String[]{name}, session, owner);
+ }
+
+ @Override
+ public void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] settable, SharedSessionContractImplementor session) throws HibernateException, SQLException {
+ set(st, returnedClass().cast(value), index, session);
+ }
+
+ @Override
+ public String toLoggableString(Object value, SessionFactoryImplementor factory) throws HibernateException {
+ return String.valueOf(value);
+ }
+
+ @Override
+ public String getName() {
+ return getClass().getSimpleName();
+ }
+
+ @Override
+ public Object deepCopy(Object value, SessionFactoryImplementor factory) throws HibernateException {
+ return deepCopy(value);
+ }
+
+ @Override
+ public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException {
+ return disassemble(value, session);
+ }
+
+ @Override
+ public void beforeAssemble(Serializable cached, SharedSessionContractImplementor session) {
+
+ }
+
+ @Override
+ public Object hydrate(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
+ return nullSafeGet(rs, names, session, owner);
+ }
+
+ @Override
+ public Object resolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException {
+ return value;
+ }
+
+ @Override
+ public Object semiResolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException {
+ return value;
+ }
+
+ @Override
+ public Type getSemiResolvedType(SessionFactoryImplementor factory) {
+ return this;
+ }
+
+ @Override
+ public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache) throws HibernateException {
+ return replace(original, target, session, owner);
+ }
+
+ @Override
+ public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache, ForeignKeyDirection foreignKeyDirection) throws HibernateException {
+ return replace(original, target, session, owner);
+ }
+
+ @Override
+ public boolean[] toColumnNullness(Object value, Mapping mapping) {
+ return value == null ? ArrayHelper.FALSE : ArrayHelper.TRUE;
+ }
+}
\ No newline at end of file
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
index ac0afb7b7..86b72525b 100644
--- 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
@@ -1,20 +1,19 @@
package com.vladmihalcea.hibernate.type.money;
+import com.vladmihalcea.hibernate.type.ImmutableCompositeType;
+import com.vladmihalcea.hibernate.type.util.Configuration;
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;
@@ -29,7 +28,15 @@
*
* @author Piotr Olaszewski
*/
-public class MonetaryAmountType implements CompositeUserType {
+public class MonetaryAmountType extends ImmutableCompositeType {
+
+ public MonetaryAmountType() {
+ super(MonetaryAmount.class);
+ }
+
+ public MonetaryAmountType(Configuration configuration) {
+ super(MonetaryAmount.class, configuration);
+ }
@Override
public String[] getPropertyNames() {
@@ -59,75 +66,35 @@ public void setPropertyValue(Object component, int property, Object value) throw
}
@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;
- }
-
+ protected MonetaryAmount get(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws SQLException {
String amountColumnName = names[0];
String currencyColumnName = names[1];
BigDecimal amount = rs.getBigDecimal(amountColumnName);
+ if(amount == null) {
+ return null;
+ }
String currency = rs.getString(currencyColumnName);
+ if(currency == null) {
+ return null;
+ }
return Money.of(amount, currency);
}
@Override
- public void nullSafeSet(PreparedStatement st, Object value, int amountColumnIndex, SharedSessionContractImplementor session) throws HibernateException, SQLException {
+ protected void set(PreparedStatement st, MonetaryAmount value, int amountColumnIndex, SharedSessionContractImplementor session) throws 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();
+ BigDecimal amount = value.getNumber().numberValue(BigDecimal.class);
+ String currency = value.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
index 34ce9c1ce..c6ea0cd2b 100644
--- 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
@@ -12,6 +12,7 @@
import java.math.BigDecimal;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
/**
* @author Piotr Olaszewski
@@ -64,6 +65,31 @@ public void testSearchByMoney() {
});
}
+ @Test
+ public void testReturnNullMoney() {
+ Long _id = doInJPA(entityManager -> {
+ Salary salary = new Salary();
+ entityManager.persist(salary);
+ return salary.getId();
+ });
+
+ doInJPA(entityManager -> {
+ Salary salary = entityManager.createQuery("select s from Salary s where s.id = :id", Salary.class)
+ .setParameter("id", _id)
+ .getSingleResult();
+
+ assertNull(salary.getSalary());
+ });
+
+ doInJPA(entityManager -> {
+ MonetaryAmount money = entityManager.createQuery("select s.salary from Salary s where s.id = :id", MonetaryAmount.class)
+ .setParameter("id", _id)
+ .getSingleResult();
+
+ assertNull(money);
+ });
+ }
+
@Entity(name = "Salary")
@Table(name = "salary")
@TypeDef(name = "monetary-amount-currency", typeClass = MonetaryAmountType.class, defaultForType = MonetaryAmount.class)
@@ -72,6 +98,8 @@ public static class Salary {
@GeneratedValue
private Long id;
+ private String other;
+
@Columns(columns = {
@Column(name = "salary_amount"),
@Column(name = "salary_currency")
@@ -94,5 +122,13 @@ public MonetaryAmount getSalary() {
public void setSalary(MonetaryAmount salary) {
this.salary = salary;
}
+
+ public String getOther() {
+ return other;
+ }
+
+ public void setOther(String other) {
+ this.other = other;
+ }
}
}
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
index c537e68b5..784823073 100644
--- 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
@@ -12,6 +12,7 @@
import java.math.BigDecimal;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
/**
* @author Piotr Olaszewski
@@ -64,6 +65,31 @@ public void testSearchByMoney() {
});
}
+ @Test
+ public void testReturnNullMoney() {
+ Long _id = doInJPA(entityManager -> {
+ Salary salary = new Salary();
+ entityManager.persist(salary);
+ return salary.getId();
+ });
+
+ doInJPA(entityManager -> {
+ Salary salary = entityManager.createQuery("select s from Salary s where s.id = :id", Salary.class)
+ .setParameter("id", _id)
+ .getSingleResult();
+
+ assertNull(salary.getSalary());
+ });
+
+ doInJPA(entityManager -> {
+ MonetaryAmount money = entityManager.createQuery("select s.salary from Salary s where s.id = :id", MonetaryAmount.class)
+ .setParameter("id", _id)
+ .getSingleResult();
+
+ assertNull(money);
+ });
+ }
+
@Entity(name = "Salary")
@Table(name = "salary")
@TypeDef(name = "monetary-amount-currency", typeClass = MonetaryAmountType.class, defaultForType = MonetaryAmount.class)
@@ -72,6 +98,8 @@ public static class Salary {
@GeneratedValue
private Long id;
+ private String other;
+
@Columns(columns = {
@Column(name = "salary_amount"),
@Column(name = "salary_currency")
@@ -94,5 +122,13 @@ public MonetaryAmount getSalary() {
public void setSalary(MonetaryAmount salary) {
this.salary = salary;
}
+
+ public String getOther() {
+ return other;
+ }
+
+ public void setOther(String other) {
+ this.other = other;
+ }
}
}
diff --git a/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/ImmutableCompositeType.java b/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/ImmutableCompositeType.java
new file mode 100644
index 000000000..a2f20c1d6
--- /dev/null
+++ b/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/ImmutableCompositeType.java
@@ -0,0 +1,329 @@
+package com.vladmihalcea.hibernate.type;
+
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.HibernateException;
+import org.hibernate.MappingException;
+import org.hibernate.engine.jdbc.Size;
+import org.hibernate.engine.spi.Mapping;
+import org.hibernate.engine.spi.SessionFactoryImplementor;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.hibernate.internal.util.collections.ArrayHelper;
+import org.hibernate.type.ForeignKeyDirection;
+import org.hibernate.type.Type;
+import org.hibernate.type.descriptor.java.IncomparableComparator;
+import org.hibernate.usertype.CompositeUserType;
+import org.hibernate.usertype.UserType;
+
+import java.io.Serializable;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Very convenient base class for implementing immutable object types using Hibernate {@link CompositeUserType}.
+ *
+ * The {@link ImmutableCompositeType} implements the {@link Type} interface too, so you can pass all
+ * types extending the {@link ImmutableCompositeType} to the {@link org.hibernate.query.NativeQuery#addScalar(String, Type)}
+ * method to fix the No Dialect mapping for JDBC type issues.
+ *
+ * @author Vlad Mihalcea
+ */
+public abstract class ImmutableCompositeType implements CompositeUserType, Type {
+
+ private final Configuration configuration;
+
+ private final Class clazz;
+
+ /**
+ * Initialization constructor taking the {@link Class}
+ * and using the default {@link Configuration} object.
+ *
+ * @param clazz the entity attribute {@link Class} type to be handled
+ */
+ protected ImmutableCompositeType(Class clazz) {
+ this.clazz = clazz;
+ this.configuration = Configuration.INSTANCE;
+ }
+
+ /**
+ * Initialization constructor taking the {@link Class} and {@link Configuration} objects.
+ *
+ * @param clazz the entity attribute {@link Class} type to be handled
+ * @param configuration custom {@link Configuration} object.
+ */
+ protected ImmutableCompositeType(Class clazz, Configuration configuration) {
+ this.clazz = clazz;
+ this.configuration = configuration;
+ }
+
+ /**
+ * Get the current {@link Configuration} object.
+ *
+ * @return the current {@link Configuration} object.
+ */
+ protected Configuration getConfiguration() {
+ return configuration;
+ }
+
+ /**
+ * Get the column value from the JDBC {@link ResultSet}.
+ *
+ * @param rs JDBC {@link ResultSet}
+ * @param names database column name
+ * @param session current Hibernate {@link org.hibernate.Session}
+ * @param owner current Hibernate {@link SessionFactoryImplementor}
+ * @return column value
+ * @throws SQLException in case of failure
+ */
+ protected abstract T get(ResultSet rs, String[] names,
+ SharedSessionContractImplementor session, Object owner) throws SQLException;
+
+ /**
+ * Set the column value on the provided JDBC {@link PreparedStatement}.
+ *
+ * @param st JDBC {@link PreparedStatement}
+ * @param value database column value
+ * @param index database column index
+ * @param session current Hibernate {@link org.hibernate.Session}
+ * @throws SQLException in case of failure
+ */
+ protected abstract void set(PreparedStatement st, T value, int index,
+ SharedSessionContractImplementor session) throws SQLException;
+
+ /* Methods inherited from the {@link UserType} interface */
+
+ @Override
+ public Object nullSafeGet(ResultSet rs, String[] names,
+ SharedSessionContractImplementor session, Object owner) throws SQLException {
+ return get(rs, names, session, owner);
+ }
+
+ @Override
+ public void nullSafeSet(PreparedStatement st, Object value, int index,
+ SharedSessionContractImplementor session) throws SQLException {
+ set(st, clazz.cast(value), index, session);
+ }
+
+ @Override
+ public Class returnedClass() {
+ return clazz;
+ }
+
+ @Override
+ public boolean equals(Object x, Object y) {
+ return (x == y) || (x != null && x.equals(y));
+ }
+
+ @Override
+ public int hashCode(Object x) {
+ return x.hashCode();
+ }
+
+ @Override
+ public Object deepCopy(Object value) {
+ return value;
+ }
+
+ @Override
+ public boolean isMutable() {
+ return false;
+ }
+
+ @Override
+ public Serializable disassemble(Object o, SharedSessionContractImplementor session) {
+ return (Serializable) o;
+ }
+
+ @Override
+ public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException {
+ return cached;
+ }
+
+ @Override
+ public Object replace(Object o, Object target, SharedSessionContractImplementor session, Object owner) {
+ return o;
+ }
+
+ /* Methods inherited from the {@link Type} interface */
+
+ @Override
+ public boolean isAssociationType() {
+ return false;
+ }
+
+ @Override
+ public boolean isCollectionType() {
+ return false;
+ }
+
+ @Override
+ public boolean isEntityType() {
+ return false;
+ }
+
+ @Override
+ public boolean isAnyType() {
+ return false;
+ }
+
+ @Override
+ public boolean isComponentType() {
+ return false;
+ }
+
+ @Override
+ public int getColumnSpan(Mapping mapping) throws MappingException {
+ return getPropertyTypes().length;
+ }
+
+ @Override
+ public int[] sqlTypes(Mapping mapping) throws MappingException {
+ List sqlTypes= new ArrayList<>();
+ Type[] types = getPropertyTypes();
+ for (int i = 0; i < types.length; i++) {
+ sqlTypes.addAll(
+ Arrays.stream(types[i].sqlTypes(mapping)).boxed().collect(Collectors.toList())
+ );
+ }
+ return sqlTypes.stream().mapToInt(i->i).toArray();
+ }
+
+ @Override
+ public Size[] dictatedSizes(Mapping mapping) throws MappingException {
+ return new Size[]{new Size()};
+ }
+
+ @Override
+ public Size[] defaultSizes(Mapping mapping) throws MappingException {
+ return dictatedSizes(mapping);
+ }
+
+ @Override
+ public Class getReturnedClass() {
+ return returnedClass();
+ }
+
+ @Override
+ public boolean isSame(Object x, Object y) throws HibernateException {
+ return equals(x, y);
+ }
+
+ @Override
+ public boolean isEqual(Object x, Object y) throws HibernateException {
+ return equals(x, y);
+ }
+
+ @Override
+ public boolean isEqual(Object x, Object y, SessionFactoryImplementor factory) throws HibernateException {
+ return equals(x, y);
+ }
+
+ @Override
+ public int getHashCode(Object x) throws HibernateException {
+ return hashCode(x);
+ }
+
+ @Override
+ public int getHashCode(Object x, SessionFactoryImplementor factory) throws HibernateException {
+ return hashCode(x);
+ }
+
+ @Override
+ public int compare(Object x, Object y) {
+ return IncomparableComparator.INSTANCE.compare(x, y);
+ }
+
+ @Override
+ public final boolean isDirty(Object old, Object current, SharedSessionContractImplementor session) {
+ return isDirty(old, current);
+ }
+
+ @Override
+ public final boolean isDirty(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session) {
+ return checkable[0] && isDirty(old, current);
+ }
+
+ protected final boolean isDirty(Object old, Object current) {
+ return !isSame(old, current);
+ }
+
+ @Override
+ public boolean isModified(Object dbState, Object currentState, boolean[] checkable, SharedSessionContractImplementor session) throws HibernateException {
+ return isDirty(dbState, currentState);
+ }
+
+ @Override
+ public Object nullSafeGet(ResultSet rs, String name, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
+ return get(rs, new String[]{name}, session, owner);
+ }
+
+ @Override
+ public void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] settable, SharedSessionContractImplementor session) throws HibernateException, SQLException {
+ set(st, returnedClass().cast(value), index, session);
+ }
+
+ @Override
+ public String toLoggableString(Object value, SessionFactoryImplementor factory) throws HibernateException {
+ return String.valueOf(value);
+ }
+
+ @Override
+ public String getName() {
+ return getClass().getSimpleName();
+ }
+
+ @Override
+ public Object deepCopy(Object value, SessionFactoryImplementor factory) throws HibernateException {
+ return deepCopy(value);
+ }
+
+ @Override
+ public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException {
+ return disassemble(value, session);
+ }
+
+ @Override
+ public void beforeAssemble(Serializable cached, SharedSessionContractImplementor session) {
+
+ }
+
+ @Override
+ public Object hydrate(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
+ return nullSafeGet(rs, names, session, owner);
+ }
+
+ @Override
+ public Object resolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException {
+ return value;
+ }
+
+ @Override
+ public Object semiResolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException {
+ return value;
+ }
+
+ @Override
+ public Type getSemiResolvedType(SessionFactoryImplementor factory) {
+ return this;
+ }
+
+ @Override
+ public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache) throws HibernateException {
+ return replace(original, target, session, owner);
+ }
+
+ @Override
+ public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache, ForeignKeyDirection foreignKeyDirection) throws HibernateException {
+ return replace(original, target, session, owner);
+ }
+
+ @Override
+ public boolean[] toColumnNullness(Object value, Mapping mapping) {
+ return value == null ? ArrayHelper.FALSE : ArrayHelper.TRUE;
+ }
+}
\ No newline at end of file
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
index ac0afb7b7..86b72525b 100644
--- 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
@@ -1,20 +1,19 @@
package com.vladmihalcea.hibernate.type.money;
+import com.vladmihalcea.hibernate.type.ImmutableCompositeType;
+import com.vladmihalcea.hibernate.type.util.Configuration;
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;
@@ -29,7 +28,15 @@
*
* @author Piotr Olaszewski
*/
-public class MonetaryAmountType implements CompositeUserType {
+public class MonetaryAmountType extends ImmutableCompositeType {
+
+ public MonetaryAmountType() {
+ super(MonetaryAmount.class);
+ }
+
+ public MonetaryAmountType(Configuration configuration) {
+ super(MonetaryAmount.class, configuration);
+ }
@Override
public String[] getPropertyNames() {
@@ -59,75 +66,35 @@ public void setPropertyValue(Object component, int property, Object value) throw
}
@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;
- }
-
+ protected MonetaryAmount get(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws SQLException {
String amountColumnName = names[0];
String currencyColumnName = names[1];
BigDecimal amount = rs.getBigDecimal(amountColumnName);
+ if(amount == null) {
+ return null;
+ }
String currency = rs.getString(currencyColumnName);
+ if(currency == null) {
+ return null;
+ }
return Money.of(amount, currency);
}
@Override
- public void nullSafeSet(PreparedStatement st, Object value, int amountColumnIndex, SharedSessionContractImplementor session) throws HibernateException, SQLException {
+ protected void set(PreparedStatement st, MonetaryAmount value, int amountColumnIndex, SharedSessionContractImplementor session) throws 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();
+ BigDecimal amount = value.getNumber().numberValue(BigDecimal.class);
+ String currency = value.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
index 34ce9c1ce..c6ea0cd2b 100644
--- 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
@@ -12,6 +12,7 @@
import java.math.BigDecimal;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
/**
* @author Piotr Olaszewski
@@ -64,6 +65,31 @@ public void testSearchByMoney() {
});
}
+ @Test
+ public void testReturnNullMoney() {
+ Long _id = doInJPA(entityManager -> {
+ Salary salary = new Salary();
+ entityManager.persist(salary);
+ return salary.getId();
+ });
+
+ doInJPA(entityManager -> {
+ Salary salary = entityManager.createQuery("select s from Salary s where s.id = :id", Salary.class)
+ .setParameter("id", _id)
+ .getSingleResult();
+
+ assertNull(salary.getSalary());
+ });
+
+ doInJPA(entityManager -> {
+ MonetaryAmount money = entityManager.createQuery("select s.salary from Salary s where s.id = :id", MonetaryAmount.class)
+ .setParameter("id", _id)
+ .getSingleResult();
+
+ assertNull(money);
+ });
+ }
+
@Entity(name = "Salary")
@Table(name = "salary")
@TypeDef(name = "monetary-amount-currency", typeClass = MonetaryAmountType.class, defaultForType = MonetaryAmount.class)
@@ -72,6 +98,8 @@ public static class Salary {
@GeneratedValue
private Long id;
+ private String other;
+
@Columns(columns = {
@Column(name = "salary_amount"),
@Column(name = "salary_currency")
@@ -94,5 +122,13 @@ public MonetaryAmount getSalary() {
public void setSalary(MonetaryAmount salary) {
this.salary = salary;
}
+
+ public String getOther() {
+ return other;
+ }
+
+ public void setOther(String other) {
+ this.other = other;
+ }
}
}
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
index c537e68b5..784823073 100644
--- 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
@@ -12,6 +12,7 @@
import java.math.BigDecimal;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
/**
* @author Piotr Olaszewski
@@ -64,6 +65,31 @@ public void testSearchByMoney() {
});
}
+ @Test
+ public void testReturnNullMoney() {
+ Long _id = doInJPA(entityManager -> {
+ Salary salary = new Salary();
+ entityManager.persist(salary);
+ return salary.getId();
+ });
+
+ doInJPA(entityManager -> {
+ Salary salary = entityManager.createQuery("select s from Salary s where s.id = :id", Salary.class)
+ .setParameter("id", _id)
+ .getSingleResult();
+
+ assertNull(salary.getSalary());
+ });
+
+ doInJPA(entityManager -> {
+ MonetaryAmount money = entityManager.createQuery("select s.salary from Salary s where s.id = :id", MonetaryAmount.class)
+ .setParameter("id", _id)
+ .getSingleResult();
+
+ assertNull(money);
+ });
+ }
+
@Entity(name = "Salary")
@Table(name = "salary")
@TypeDef(name = "monetary-amount-currency", typeClass = MonetaryAmountType.class, defaultForType = MonetaryAmount.class)
@@ -72,6 +98,8 @@ public static class Salary {
@GeneratedValue
private Long id;
+ private String other;
+
@Columns(columns = {
@Column(name = "salary_amount"),
@Column(name = "salary_currency")
@@ -94,5 +122,13 @@ public MonetaryAmount getSalary() {
public void setSalary(MonetaryAmount salary) {
this.salary = salary;
}
+
+ public String getOther() {
+ return other;
+ }
+
+ public void setOther(String other) {
+ this.other = other;
+ }
}
}
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
index 7da4bd3a0..49aa32a0c 100644
--- 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
@@ -10,6 +10,7 @@
import java.math.BigDecimal;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
/**
* @author Piotr Olaszewski
@@ -62,6 +63,31 @@ public void testSearchByMoney() {
});
}
+ @Test
+ public void testReturnNullMoney() {
+ Long _id = doInJPA(entityManager -> {
+ Salary salary = new Salary();
+ entityManager.persist(salary);
+ return salary.getId();
+ });
+
+ doInJPA(entityManager -> {
+ Salary salary = entityManager.createQuery("select s from Salary s where s.id = :id", Salary.class)
+ .setParameter("id", _id)
+ .getSingleResult();
+
+ assertNull(salary.getSalary());
+ });
+
+ doInJPA(entityManager -> {
+ MonetaryAmount money = entityManager.createQuery("select s.salary from Salary s where s.id = :id", MonetaryAmount.class)
+ .setParameter("id", _id)
+ .getSingleResult();
+
+ assertNull(money);
+ });
+ }
+
@Entity(name = "Salary")
@Table(name = "salary")
public static class Salary {
@@ -69,6 +95,8 @@ public static class Salary {
@GeneratedValue
private Long id;
+ private String other;
+
@AttributeOverride(name = "amount", column = @Column(name = "salary_amount"))
@AttributeOverride(name = "currency", column = @Column(name = "salary_currency"))
@CompositeType(MonetaryAmountType.class)
@@ -89,5 +117,13 @@ public MonetaryAmount getSalary() {
public void setSalary(MonetaryAmount salary) {
this.salary = salary;
}
+
+ public String getOther() {
+ return other;
+ }
+
+ public void setOther(String other) {
+ this.other = other;
+ }
}
}
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
index 7778c7794..558f8b0d8 100644
--- 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
@@ -10,6 +10,7 @@
import java.math.BigDecimal;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
/**
* @author Piotr Olaszewski
@@ -62,6 +63,31 @@ public void testSearchByMoney() {
});
}
+ @Test
+ public void testReturnNullMoney() {
+ Long _id = doInJPA(entityManager -> {
+ Salary salary = new Salary();
+ entityManager.persist(salary);
+ return salary.getId();
+ });
+
+ doInJPA(entityManager -> {
+ Salary salary = entityManager.createQuery("select s from Salary s where s.id = :id", Salary.class)
+ .setParameter("id", _id)
+ .getSingleResult();
+
+ assertNull(salary.getSalary());
+ });
+
+ doInJPA(entityManager -> {
+ MonetaryAmount money = entityManager.createQuery("select s.salary from Salary s where s.id = :id", MonetaryAmount.class)
+ .setParameter("id", _id)
+ .getSingleResult();
+
+ assertNull(money);
+ });
+ }
+
@Entity(name = "Salary")
@Table(name = "salary")
public static class Salary {
@@ -69,6 +95,8 @@ public static class Salary {
@GeneratedValue
private Long id;
+ private String other;
+
@AttributeOverride(name = "amount", column = @Column(name = "salary_amount"))
@AttributeOverride(name = "currency", column = @Column(name = "salary_currency"))
@CompositeType(MonetaryAmountType.class)
@@ -89,5 +117,13 @@ public MonetaryAmount getSalary() {
public void setSalary(MonetaryAmount salary) {
this.salary = salary;
}
+
+ public String getOther() {
+ return other;
+ }
+
+ public void setOther(String other) {
+ this.other = other;
+ }
}
}