Merge pull request #5438 from dev-chirag/master

BAEL-1708 Custom Types in Hibernate
This commit is contained in:
Eric Martin 2018-10-20 15:58:49 -05:00 committed by GitHub
commit 3137c5f805
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 883 additions and 2 deletions

View File

@ -44,6 +44,11 @@
<artifactId>mariaDB4j</artifactId>
<version>${mariaDB4j.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-testing</artifactId>
<version>5.2.2.Final</version>
</dependency>
</dependencies>
<build>
@ -57,7 +62,7 @@
</build>
<properties>
<hibernate.version>5.3.2.Final</hibernate.version>
<hibernate.version>5.3.6.Final</hibernate.version>
<mysql.version>6.0.6</mysql.version>
<mariaDB4j.version>2.2.3</mariaDB4j.version>
<h2database.version>1.4.196</h2database.version>

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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<LocalDate> {
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> X unwrap(LocalDate value, Class<X> type, WrapperOptions options) {
if (value == null)
return null;
if (String.class.isAssignableFrom(type))
return (X) LocalDateType.FORMATTER.format(value);
throw unknownUnwrap(type);
}
@Override
public <X> 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());
}
}

View File

@ -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<LocalDate> implements DiscriminatorType<LocalDate> {
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) + '\'';
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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");
}
}

View File

@ -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;
}
}

View File

@ -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