diff --git a/hibernate5/pom.xml b/hibernate5/pom.xml
index 610c893bdc..748a106cca 100644
--- a/hibernate5/pom.xml
+++ b/hibernate5/pom.xml
@@ -44,6 +44,11 @@
mariaDB4j
${mariaDB4j.version}
+
+ org.hibernate
+ hibernate-testing
+ 5.2.2.Final
+
@@ -57,7 +62,7 @@
- 5.3.2.Final
+ 5.3.6.Final
6.0.6
2.2.3
1.4.196
diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/HibernateUtil.java b/hibernate5/src/main/java/com/baeldung/hibernate/HibernateUtil.java
index 2212e736ab..e0d1de591b 100644
--- a/hibernate5/src/main/java/com/baeldung/hibernate/HibernateUtil.java
+++ b/hibernate5/src/main/java/com/baeldung/hibernate/HibernateUtil.java
@@ -5,6 +5,8 @@ import java.io.IOException;
import java.net.URL;
import java.util.Properties;
+import com.baeldung.hibernate.customtypes.LocalDateStringType;
+import com.baeldung.hibernate.customtypes.OfficeEmployee;
import com.baeldung.hibernate.entities.DeptEmployee;
import com.baeldung.hibernate.optimisticlocking.OptimisticLockingCourse;
import com.baeldung.hibernate.optimisticlocking.OptimisticLockingStudent;
@@ -18,8 +20,10 @@ import com.baeldung.hibernate.pojo.inheritance.*;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
+import org.hibernate.boot.MetadataBuilder;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
+import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import com.baeldung.hibernate.pojo.Course;
@@ -66,6 +70,7 @@ public class HibernateUtil {
private static SessionFactory makeSessionFactory(ServiceRegistry serviceRegistry) {
MetadataSources metadataSources = new MetadataSources(serviceRegistry);
+
metadataSources.addPackage("com.baeldung.hibernate.pojo");
metadataSources.addAnnotatedClass(Employee.class);
metadataSources.addAnnotatedClass(Phone.class);
@@ -102,8 +107,12 @@ public class HibernateUtil {
metadataSources.addAnnotatedClass(com.baeldung.hibernate.entities.Department.class);
metadataSources.addAnnotatedClass(OptimisticLockingCourse.class);
metadataSources.addAnnotatedClass(OptimisticLockingStudent.class);
+ metadataSources.addAnnotatedClass(OfficeEmployee.class);
+
+ Metadata metadata = metadataSources.getMetadataBuilder()
+ .applyBasicType(LocalDateStringType.INSTANCE)
+ .build();
- Metadata metadata = metadataSources.buildMetadata();
return metadata.getSessionFactoryBuilder()
.build();
diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/Address.java b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/Address.java
new file mode 100644
index 0000000000..d559e5a6c2
--- /dev/null
+++ b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/Address.java
@@ -0,0 +1,69 @@
+package com.baeldung.hibernate.customtypes;
+
+import java.util.Objects;
+
+public class Address {
+
+ private String addressLine1;
+ private String addressLine2;
+ private String city;
+ private String country;
+ private int zipCode;
+
+ public String getAddressLine1() {
+ return addressLine1;
+ }
+
+ public String getAddressLine2() {
+ return addressLine2;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public int getZipCode() {
+ return zipCode;
+ }
+
+ public void setAddressLine1(String addressLine1) {
+ this.addressLine1 = addressLine1;
+ }
+
+ public void setAddressLine2(String addressLine2) {
+ this.addressLine2 = addressLine2;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ public void setZipCode(int zipCode) {
+ this.zipCode = zipCode;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Address address = (Address) o;
+ return zipCode == address.zipCode &&
+ Objects.equals(addressLine1, address.addressLine1) &&
+ Objects.equals(addressLine2, address.addressLine2) &&
+ Objects.equals(city, address.city) &&
+ Objects.equals(country, address.country);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(addressLine1, addressLine2, city, country, zipCode);
+ }
+}
diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/AddressType.java b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/AddressType.java
new file mode 100644
index 0000000000..c10c67df9a
--- /dev/null
+++ b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/AddressType.java
@@ -0,0 +1,169 @@
+package com.baeldung.hibernate.customtypes;
+
+import org.hibernate.HibernateException;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.hibernate.type.IntegerType;
+import org.hibernate.type.StringType;
+import org.hibernate.type.Type;
+import org.hibernate.usertype.CompositeUserType;
+
+import java.io.Serializable;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Objects;
+
+public class AddressType implements CompositeUserType {
+
+ @Override
+ public String[] getPropertyNames() {
+ return new String[]{"addressLine1", "addressLine2",
+ "city", "country", "zipcode"};
+ }
+
+ @Override
+ public Type[] getPropertyTypes() {
+ return new Type[]{StringType.INSTANCE, StringType.INSTANCE,
+ StringType.INSTANCE, StringType.INSTANCE, IntegerType.INSTANCE};
+ }
+
+ @Override
+ public Object getPropertyValue(Object component, int property) throws HibernateException {
+
+ Address empAdd = (Address) component;
+
+ switch (property) {
+ case 0:
+ return empAdd.getAddressLine1();
+ case 1:
+ return empAdd.getAddressLine2();
+ case 2:
+ return empAdd.getCity();
+ case 3:
+ return empAdd.getCountry();
+ case 4:
+ return Integer.valueOf(empAdd.getZipCode());
+ }
+
+ throw new IllegalArgumentException(property +
+ " is an invalid property index for class type " +
+ component.getClass().getName());
+ }
+
+ @Override
+ public void setPropertyValue(Object component, int property, Object value) throws HibernateException {
+
+ Address empAdd = (Address) component;
+
+ switch (property) {
+ case 0:
+ empAdd.setAddressLine1((String) value);
+ case 1:
+ empAdd.setAddressLine2((String) value);
+ case 2:
+ empAdd.setCity((String) value);
+ case 3:
+ empAdd.setCountry((String) value);
+ case 4:
+ empAdd.setZipCode((Integer) value);
+ }
+
+ throw new IllegalArgumentException(property +
+ " is an invalid property index for class type " +
+ component.getClass().getName());
+
+ }
+
+ @Override
+ public Class returnedClass() {
+ return Address.class;
+ }
+
+ @Override
+ public boolean equals(Object x, Object y) throws HibernateException {
+ if (x == y)
+ return true;
+
+ if (Objects.isNull(x) || Objects.isNull(y))
+ return false;
+
+ return x.equals(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 {
+
+ Address empAdd = new Address();
+ empAdd.setAddressLine1(rs.getString(names[0]));
+
+ if (rs.wasNull())
+ return null;
+
+ empAdd.setAddressLine2(rs.getString(names[1]));
+ empAdd.setCity(rs.getString(names[2]));
+ empAdd.setCountry(rs.getString(names[3]));
+ empAdd.setZipCode(rs.getInt(names[4]));
+
+ return empAdd;
+ }
+
+ @Override
+ public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
+
+ if (Objects.isNull(value))
+ st.setNull(index, Types.VARCHAR);
+ else {
+
+ Address empAdd = (Address) value;
+ st.setString(index, empAdd.getAddressLine1());
+ st.setString(index + 1, empAdd.getAddressLine2());
+ st.setString(index + 2, empAdd.getCity());
+ st.setString(index + 3, empAdd.getCountry());
+ st.setInt(index + 4, empAdd.getZipCode());
+ }
+ }
+
+ @Override
+ public Object deepCopy(Object value) throws HibernateException {
+
+ if (Objects.isNull(value))
+ return null;
+
+ Address oldEmpAdd = (Address) value;
+ Address newEmpAdd = new Address();
+
+ newEmpAdd.setAddressLine1(oldEmpAdd.getAddressLine1());
+ newEmpAdd.setAddressLine2(oldEmpAdd.getAddressLine2());
+ newEmpAdd.setCity(oldEmpAdd.getCity());
+ newEmpAdd.setCountry(oldEmpAdd.getCountry());
+ newEmpAdd.setZipCode(oldEmpAdd.getZipCode());
+
+ return newEmpAdd;
+ }
+
+ @Override
+ public boolean isMutable() {
+ return true;
+ }
+
+ @Override
+ public Serializable disassemble(Object value, SharedSessionContractImplementor session) throws HibernateException {
+ return (Serializable) deepCopy(value);
+ }
+
+ @Override
+ public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException {
+ return deepCopy(cached);
+ }
+
+ @Override
+ public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner) throws HibernateException {
+ return original;
+ }
+}
diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/LocalDateStringJavaDescriptor.java b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/LocalDateStringJavaDescriptor.java
new file mode 100644
index 0000000000..56be9e693f
--- /dev/null
+++ b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/LocalDateStringJavaDescriptor.java
@@ -0,0 +1,51 @@
+package com.baeldung.hibernate.customtypes;
+
+import org.hibernate.type.LocalDateType;
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.AbstractTypeDescriptor;
+import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan;
+import org.hibernate.type.descriptor.java.MutabilityPlan;
+
+import java.time.LocalDate;
+
+public class LocalDateStringJavaDescriptor extends AbstractTypeDescriptor {
+
+ public static final LocalDateStringJavaDescriptor INSTANCE = new LocalDateStringJavaDescriptor();
+
+ public LocalDateStringJavaDescriptor() {
+ super(LocalDate.class, ImmutableMutabilityPlan.INSTANCE);
+ }
+
+ @Override
+ public String toString(LocalDate value) {
+ return LocalDateType.FORMATTER.format(value);
+ }
+
+ @Override
+ public LocalDate fromString(String string) {
+ return LocalDate.from(LocalDateType.FORMATTER.parse(string));
+ }
+
+ @Override
+ public X unwrap(LocalDate value, Class type, WrapperOptions options) {
+
+ if (value == null)
+ return null;
+
+ if (String.class.isAssignableFrom(type))
+ return (X) LocalDateType.FORMATTER.format(value);
+
+ throw unknownUnwrap(type);
+ }
+
+ @Override
+ public LocalDate wrap(X value, WrapperOptions options) {
+ if (value == null)
+ return null;
+
+ if(String.class.isInstance(value))
+ return LocalDate.from(LocalDateType.FORMATTER.parse((CharSequence) value));
+
+ throw unknownWrap(value.getClass());
+ }
+}
diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/LocalDateStringType.java b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/LocalDateStringType.java
new file mode 100644
index 0000000000..c8d37073e8
--- /dev/null
+++ b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/LocalDateStringType.java
@@ -0,0 +1,34 @@
+package com.baeldung.hibernate.customtypes;
+
+import org.hibernate.dialect.Dialect;
+import org.hibernate.type.AbstractSingleColumnStandardBasicType;
+import org.hibernate.type.DiscriminatorType;
+import org.hibernate.type.descriptor.java.LocalDateJavaDescriptor;
+import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor;
+
+import java.time.LocalDate;
+
+public class LocalDateStringType extends AbstractSingleColumnStandardBasicType implements DiscriminatorType {
+
+ public static final LocalDateStringType INSTANCE = new LocalDateStringType();
+
+ public LocalDateStringType() {
+ super(VarcharTypeDescriptor.INSTANCE, LocalDateStringJavaDescriptor.INSTANCE);
+ }
+
+ @Override
+ public String getName() {
+ return "LocalDateString";
+ }
+
+ @Override
+ public LocalDate stringToObject(String xml) throws Exception {
+ return fromString(xml);
+ }
+
+ @Override
+ public String objectToSQLString(LocalDate value, Dialect dialect) throws Exception {
+ return '\'' + toString(value) + '\'';
+ }
+
+}
diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/OfficeEmployee.java b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/OfficeEmployee.java
new file mode 100644
index 0000000000..3ca06e4316
--- /dev/null
+++ b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/OfficeEmployee.java
@@ -0,0 +1,88 @@
+package com.baeldung.hibernate.customtypes;
+
+import com.baeldung.hibernate.pojo.Phone;
+import org.hibernate.annotations.Columns;
+import org.hibernate.annotations.Parameter;
+import org.hibernate.annotations.Type;
+import org.hibernate.annotations.TypeDef;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.time.LocalDate;
+
+@TypeDef(name = "PhoneNumber",
+ typeClass = PhoneNumberType.class,
+ defaultForType = PhoneNumber.class)
+@Entity
+@Table(name = "OfficeEmployee")
+public class OfficeEmployee {
+
+ @Id
+ @GeneratedValue
+ private long id;
+
+ @Column
+ @Type(type = "LocalDateString")
+ private LocalDate dateOfJoining;
+
+ @Columns(columns = {@Column(name = "country_code"),
+ @Column(name = "city_code"),
+ @Column(name = "number")})
+ private PhoneNumber employeeNumber;
+
+ @Columns(columns = {@Column(name = "address_line_1"),
+ @Column(name = "address_line_2"),
+ @Column(name = "city"), @Column(name = "country"),
+ @Column(name = "zip_code")})
+ @Type(type = "com.baeldung.hibernate.customtypes.AddressType")
+ private Address empAddress;
+
+ @Type(type = "com.baeldung.hibernate.customtypes.SalaryType",
+ parameters = {@Parameter(name = "currency", value = "USD")})
+ @Columns(columns = {@Column(name = "amount"),
+ @Column(name = "currency")})
+ private Salary salary;
+
+ public Salary getSalary() {
+ return salary;
+ }
+
+ public void setSalary(Salary salary) {
+ this.salary = salary;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public LocalDate getDateOfJoining() {
+ return dateOfJoining;
+ }
+
+ public void setDateOfJoining(LocalDate dateOfJoining) {
+ this.dateOfJoining = dateOfJoining;
+ }
+
+ public PhoneNumber getEmployeeNumber() {
+ return employeeNumber;
+ }
+
+ public void setEmployeeNumber(PhoneNumber employeeNumber) {
+ this.employeeNumber = employeeNumber;
+ }
+
+ public Address getEmpAddress() {
+ return empAddress;
+ }
+
+ public void setEmpAddress(Address empAddress) {
+ this.empAddress = empAddress;
+ }
+}
diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/PhoneNumber.java b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/PhoneNumber.java
new file mode 100644
index 0000000000..0be6cbc910
--- /dev/null
+++ b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/PhoneNumber.java
@@ -0,0 +1,43 @@
+package com.baeldung.hibernate.customtypes;
+
+import java.util.Objects;
+
+public final class PhoneNumber {
+
+ private final int countryCode;
+ private final int cityCode;
+ private final int number;
+
+ public PhoneNumber(int countryCode, int cityCode, int number) {
+ this.countryCode = countryCode;
+ this.cityCode = cityCode;
+ this.number = number;
+ }
+
+ public int getCountryCode() {
+ return countryCode;
+ }
+
+ public int getCityCode() {
+ return cityCode;
+ }
+
+ public int getNumber() {
+ return number;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PhoneNumber that = (PhoneNumber) o;
+ return countryCode == that.countryCode &&
+ cityCode == that.cityCode &&
+ number == that.number;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(countryCode, cityCode, number);
+ }
+}
diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/PhoneNumberType.java b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/PhoneNumberType.java
new file mode 100644
index 0000000000..9f09352bec
--- /dev/null
+++ b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/PhoneNumberType.java
@@ -0,0 +1,98 @@
+package com.baeldung.hibernate.customtypes;
+
+import org.hibernate.HibernateException;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.hibernate.usertype.UserType;
+
+import java.io.Serializable;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Objects;
+
+
+public class PhoneNumberType implements UserType {
+ @Override
+ public int[] sqlTypes() {
+ return new int[]{Types.INTEGER, Types.INTEGER, Types.INTEGER};
+ }
+
+ @Override
+ public Class returnedClass() {
+ return PhoneNumber.class;
+ }
+
+ @Override
+ public boolean equals(Object x, Object y) throws HibernateException {
+ if (x == y)
+ return true;
+ if (Objects.isNull(x) || Objects.isNull(y))
+ return false;
+
+ return x.equals(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 {
+ int countryCode = rs.getInt(names[0]);
+
+ if (rs.wasNull())
+ return null;
+
+ int cityCode = rs.getInt(names[1]);
+ int number = rs.getInt(names[2]);
+ PhoneNumber employeeNumber = new PhoneNumber(countryCode, cityCode, number);
+
+ return employeeNumber;
+ }
+
+ @Override
+ public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
+
+ if (Objects.isNull(value)) {
+ st.setNull(index, Types.INTEGER);
+ } else {
+ PhoneNumber employeeNumber = (PhoneNumber) value;
+ st.setInt(index,employeeNumber.getCountryCode());
+ st.setInt(index+1,employeeNumber.getCityCode());
+ st.setInt(index+2,employeeNumber.getNumber());
+ }
+ }
+
+ @Override
+ public Object deepCopy(Object value) throws HibernateException {
+ if (Objects.isNull(value))
+ return null;
+
+ PhoneNumber empNumber = (PhoneNumber) value;
+ PhoneNumber newEmpNumber = new PhoneNumber(empNumber.getCountryCode(),empNumber.getCityCode(),empNumber.getNumber());
+
+ return newEmpNumber;
+ }
+
+ @Override
+ public boolean isMutable() {
+ return false;
+ }
+
+ @Override
+ public Serializable disassemble(Object value) throws HibernateException {
+ return (Serializable) value;
+ }
+
+ @Override
+ public Object assemble(Serializable cached, Object owner) throws HibernateException {
+ return cached;
+ }
+
+ @Override
+ public Object replace(Object original, Object target, Object owner) throws HibernateException {
+ return original;
+ }
+}
diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/Salary.java b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/Salary.java
new file mode 100644
index 0000000000..f9a7ac5902
--- /dev/null
+++ b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/Salary.java
@@ -0,0 +1,39 @@
+package com.baeldung.hibernate.customtypes;
+
+import java.util.Objects;
+
+public class Salary {
+
+ private Long amount;
+ private String currency;
+
+ public Long getAmount() {
+ return amount;
+ }
+
+ public void setAmount(Long amount) {
+ this.amount = amount;
+ }
+
+ public String getCurrency() {
+ return currency;
+ }
+
+ public void setCurrency(String currency) {
+ this.currency = currency;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Salary salary = (Salary) o;
+ return Objects.equals(amount, salary.amount) &&
+ Objects.equals(currency, salary.currency);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(amount, currency);
+ }
+}
diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/SalaryCurrencyConvertor.java b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/SalaryCurrencyConvertor.java
new file mode 100644
index 0000000000..340c697c11
--- /dev/null
+++ b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/SalaryCurrencyConvertor.java
@@ -0,0 +1,15 @@
+package com.baeldung.hibernate.customtypes;
+
+public class SalaryCurrencyConvertor {
+
+ public static Long convert(Long amount, String oldCurr, String newCurr){
+ if (newCurr.equalsIgnoreCase(oldCurr))
+ return amount;
+
+ return convertTo();
+ }
+
+ private static Long convertTo() {
+ return 10L;
+ }
+}
diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/SalaryType.java b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/SalaryType.java
new file mode 100644
index 0000000000..266b85140b
--- /dev/null
+++ b/hibernate5/src/main/java/com/baeldung/hibernate/customtypes/SalaryType.java
@@ -0,0 +1,161 @@
+package com.baeldung.hibernate.customtypes;
+
+import org.hibernate.HibernateException;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.hibernate.type.LongType;
+import org.hibernate.type.StringType;
+import org.hibernate.type.Type;
+import org.hibernate.usertype.CompositeUserType;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.io.Serializable;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Objects;
+import java.util.Properties;
+
+public class SalaryType implements CompositeUserType, DynamicParameterizedType {
+
+ private String localCurrency;
+
+ @Override
+ public String[] getPropertyNames() {
+ return new String[]{"amount", "currency"};
+ }
+
+ @Override
+ public Type[] getPropertyTypes() {
+ return new Type[]{LongType.INSTANCE, StringType.INSTANCE};
+ }
+
+ @Override
+ public Object getPropertyValue(Object component, int property) throws HibernateException {
+
+ Salary salary = (Salary) component;
+
+ switch (property) {
+ case 0:
+ return salary.getAmount();
+ case 1:
+ return salary.getCurrency();
+ }
+
+ throw new IllegalArgumentException(property +
+ " is an invalid property index for class type " +
+ component.getClass().getName());
+
+ }
+
+
+ @Override
+ public void setPropertyValue(Object component, int property, Object value) throws HibernateException {
+
+ Salary salary = (Salary) component;
+
+ switch (property) {
+ case 0:
+ salary.setAmount((Long) value);
+ case 1:
+ salary.setCurrency((String) value);
+ }
+
+ throw new IllegalArgumentException(property +
+ " is an invalid property index for class type " +
+ component.getClass().getName());
+
+ }
+
+ @Override
+ public Class returnedClass() {
+ return Salary.class;
+ }
+
+ @Override
+ public boolean equals(Object x, Object y) throws HibernateException {
+
+ if (x == y)
+ return true;
+
+ if (Objects.isNull(x) || Objects.isNull(y))
+ return false;
+
+ return x.equals(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 {
+
+ Salary salary = new Salary();
+ salary.setAmount(rs.getLong(names[0]));
+
+ if (rs.wasNull())
+ return null;
+
+ salary.setCurrency(rs.getString(names[1]));
+
+ return salary;
+ }
+
+ @Override
+ public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
+
+
+ if (Objects.isNull(value))
+ st.setNull(index, Types.BIGINT);
+ else {
+
+ Salary salary = (Salary) value;
+ st.setLong(index, SalaryCurrencyConvertor.convert(salary.getAmount(),
+ salary.getCurrency(), localCurrency));
+ st.setString(index + 1, salary.getCurrency());
+ }
+ }
+
+ @Override
+ public Object deepCopy(Object value) throws HibernateException {
+
+ if (Objects.isNull(value))
+ return null;
+
+ Salary oldSal = (Salary) value;
+ Salary newSal = new Salary();
+
+ newSal.setAmount(oldSal.getAmount());
+ newSal.setCurrency(oldSal.getCurrency());
+
+ return newSal;
+ }
+
+ @Override
+ public boolean isMutable() {
+ return true;
+ }
+
+ @Override
+ public Serializable disassemble(Object value, SharedSessionContractImplementor session) throws HibernateException {
+ return (Serializable) deepCopy(value);
+ }
+
+ @Override
+ public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException {
+ return deepCopy(cached);
+ }
+
+ @Override
+ public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner) throws HibernateException {
+ return original;
+ }
+
+ @Override
+ public void setParameterValues(Properties parameters) {
+ this.localCurrency = parameters.getProperty("currency");
+ }
+}
diff --git a/hibernate5/src/test/java/com/baeldung/hibernate/customtypes/HibernateCustomTypesUnitTest.java b/hibernate5/src/test/java/com/baeldung/hibernate/customtypes/HibernateCustomTypesUnitTest.java
new file mode 100644
index 0000000000..f0179701df
--- /dev/null
+++ b/hibernate5/src/test/java/com/baeldung/hibernate/customtypes/HibernateCustomTypesUnitTest.java
@@ -0,0 +1,90 @@
+package com.baeldung.hibernate.customtypes;
+
+import com.baeldung.hibernate.HibernateUtil;
+import org.hibernate.SessionFactory;
+import org.hibernate.query.Query;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.time.LocalDate;
+
+import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
+
+public class HibernateCustomTypesUnitTest {
+
+ @Test
+ public void givenEmployee_whenSavedWithCustomTypes_thenEntityIsSaved() throws IOException {
+
+ final OfficeEmployee e = new OfficeEmployee();
+ e.setDateOfJoining(LocalDate.now());
+
+ PhoneNumber number = new PhoneNumber(1, 222, 8781902);
+ e.setEmployeeNumber(number);
+
+ Address empAdd = new Address();
+ empAdd.setAddressLine1("Street");
+ empAdd.setAddressLine2("Area");
+ empAdd.setCity("City");
+ empAdd.setCountry("Country");
+ empAdd.setZipCode(100);
+
+ e.setEmpAddress(empAdd);
+
+ Salary empSalary = new Salary();
+ empSalary.setAmount(1000L);
+ empSalary.setCurrency("USD");
+ e.setSalary(empSalary);
+
+ doInHibernate(this::sessionFactory, session -> {
+ session.save(e);
+ boolean contains = session.contains(e);
+ Assert.assertTrue(contains);
+ });
+
+ }
+
+ @Test
+ public void givenEmployee_whenCustomTypeInQuery_thenReturnEntity() throws IOException {
+
+ final OfficeEmployee e = new OfficeEmployee();
+ e.setDateOfJoining(LocalDate.now());
+
+ PhoneNumber number = new PhoneNumber(1, 222, 8781902);
+ e.setEmployeeNumber(number);
+
+ Address empAdd = new Address();
+ empAdd.setAddressLine1("Street");
+ empAdd.setAddressLine2("Area");
+ empAdd.setCity("City");
+ empAdd.setCountry("Country");
+ empAdd.setZipCode(100);
+ e.setEmpAddress(empAdd);
+
+ Salary empSalary = new Salary();
+ empSalary.setAmount(1000L);
+ empSalary.setCurrency("USD");
+ e.setSalary(empSalary);
+
+ doInHibernate(this::sessionFactory, session -> {
+ session.save(e);
+
+ Query query = session.createQuery("FROM OfficeEmployee OE WHERE OE.empAddress.zipcode = :pinCode");
+ query.setParameter("pinCode",100);
+ int size = query.list().size();
+
+ Assert.assertEquals(1, size);
+ });
+
+ }
+
+ private SessionFactory sessionFactory() {
+ try {
+ return HibernateUtil.getSessionFactory("hibernate-customtypes.properties");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+}
diff --git a/hibernate5/src/test/resources/hibernate-customtypes.properties b/hibernate5/src/test/resources/hibernate-customtypes.properties
new file mode 100644
index 0000000000..345f3d37b0
--- /dev/null
+++ b/hibernate5/src/test/resources/hibernate-customtypes.properties
@@ -0,0 +1,10 @@
+hibernate.connection.driver_class=org.postgresql.Driver
+hibernate.connection.url=jdbc:postgresql://localhost:5432/test
+hibernate.connection.username=postgres
+hibernate.connection.password=thule
+hibernate.connection.autocommit=true
+jdbc.password=thule
+
+hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
+hibernate.show_sql=true
+hibernate.hbm2ddl.auto=create-drop
\ No newline at end of file