diff --git a/persistence-modules/hibernate-mapping/pom.xml b/persistence-modules/hibernate-mapping/pom.xml index 1c0c3eb159..0248f0cb04 100644 --- a/persistence-modules/hibernate-mapping/pom.xml +++ b/persistence-modules/hibernate-mapping/pom.xml @@ -13,11 +13,22 @@ + + org.postgresql + postgresql + ${postgresql.version} + test + org.hibernate hibernate-core ${hibernate.version} + + com.vladmihalcea + hibernate-types-52 + ${hibernate-types.version} + org.assertj assertj-core @@ -64,7 +75,9 @@ + 42.2.20 5.4.12.Final + 2.10.4 3.8.0 6.0.16.Final 3.0.1-b11 diff --git a/persistence-modules/hibernate-mapping/src/main/java/com/baeldung/hibernate/arraymapping/CustomIntegerArrayType.java b/persistence-modules/hibernate-mapping/src/main/java/com/baeldung/hibernate/arraymapping/CustomIntegerArrayType.java new file mode 100644 index 0000000000..233bb95dc1 --- /dev/null +++ b/persistence-modules/hibernate-mapping/src/main/java/com/baeldung/hibernate/arraymapping/CustomIntegerArrayType.java @@ -0,0 +1,85 @@ +package com.baeldung.hibernate.arraymapping; + +import java.io.Serializable; +import java.sql.Array; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Arrays; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.usertype.UserType; + +public class CustomIntegerArrayType implements UserType { + + @Override + public int[] sqlTypes() { + return new int[]{Types.ARRAY}; + } + + @Override + public Class returnedClass() { + return Integer[].class; + } + + @Override + public boolean equals(Object x, Object y) throws HibernateException { + if (x instanceof Integer[] && y instanceof Integer[]) { + return Arrays.deepEquals((Integer[])x, (Integer[])y); + } else { + return false; + } + } + + @Override + public int hashCode(Object x) throws HibernateException { + return Arrays.hashCode((Integer[])x); + } + + @Override + public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) + throws HibernateException, SQLException { + Array array = rs.getArray(names[0]); + return array != null ? array.getArray() : null; + } + + @Override + public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) + throws HibernateException, SQLException { + if (value != null && st != null) { + Array array = session.connection().createArrayOf("int", (Integer[])value); + st.setArray(index, array); + } else { + st.setNull(index, sqlTypes()[0]); + } + } + + @Override + public Object deepCopy(Object value) throws HibernateException { + Integer[] a = (Integer[])value; + return Arrays.copyOf(a, a.length); + } + + @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/persistence-modules/hibernate-mapping/src/main/java/com/baeldung/hibernate/arraymapping/CustomStringArrayType.java b/persistence-modules/hibernate-mapping/src/main/java/com/baeldung/hibernate/arraymapping/CustomStringArrayType.java new file mode 100644 index 0000000000..7bd284def7 --- /dev/null +++ b/persistence-modules/hibernate-mapping/src/main/java/com/baeldung/hibernate/arraymapping/CustomStringArrayType.java @@ -0,0 +1,85 @@ +package com.baeldung.hibernate.arraymapping; + +import java.io.Serializable; +import java.sql.Array; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Arrays; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.usertype.UserType; + +public class CustomStringArrayType implements UserType { + + @Override + public int[] sqlTypes() { + return new int[]{Types.ARRAY}; + } + + @Override + public Class returnedClass() { + return String[].class; + } + + @Override + public boolean equals(Object x, Object y) throws HibernateException { + if (x instanceof String[] && y instanceof String[]) { + return Arrays.deepEquals((String[])x, (String[])y); + } else { + return false; + } + } + + @Override + public int hashCode(Object x) throws HibernateException { + return Arrays.hashCode((String[])x); + } + + @Override + public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) + throws HibernateException, SQLException { + Array array = rs.getArray(names[0]); + return array != null ? array.getArray() : null; + } + + @Override + public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) + throws HibernateException, SQLException { + if (value != null && st != null) { + Array array = session.connection().createArrayOf("text", (String[])value); + st.setArray(index, array); + } else { + st.setNull(index, sqlTypes()[0]); + } + } + + @Override + public Object deepCopy(Object value) throws HibernateException { + String[] a = (String[])value; + return Arrays.copyOf(a, a.length); + } + + @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/persistence-modules/hibernate-mapping/src/main/java/com/baeldung/hibernate/arraymapping/HibernateSessionUtil.java b/persistence-modules/hibernate-mapping/src/main/java/com/baeldung/hibernate/arraymapping/HibernateSessionUtil.java new file mode 100644 index 0000000000..dc3ec93a9d --- /dev/null +++ b/persistence-modules/hibernate-mapping/src/main/java/com/baeldung/hibernate/arraymapping/HibernateSessionUtil.java @@ -0,0 +1,61 @@ +package com.baeldung.hibernate.arraymapping; + +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; +import java.util.Properties; + +import org.apache.commons.lang3.StringUtils; +import org.hibernate.SessionFactory; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.service.ServiceRegistry; + +import com.baeldung.hibernate.arraymapping.User; + +public class HibernateSessionUtil { + + private static SessionFactory sessionFactory; + private static String PROPERTY_FILE_NAME; + + public static SessionFactory getSessionFactory() throws IOException { + return getSessionFactory(null); + } + + public static SessionFactory getSessionFactory(String propertyFileName) throws IOException { + PROPERTY_FILE_NAME = propertyFileName; + if (sessionFactory == null) { + ServiceRegistry serviceRegistry = configureServiceRegistry(); + sessionFactory = makeSessionFactory(serviceRegistry); + } + return sessionFactory; + } + + private static SessionFactory makeSessionFactory(ServiceRegistry serviceRegistry) { + MetadataSources metadataSources = new MetadataSources(serviceRegistry); + metadataSources.addAnnotatedClass(User.class); + + Metadata metadata = metadataSources.buildMetadata(); + return metadata.getSessionFactoryBuilder() + .build(); + + } + + private static ServiceRegistry configureServiceRegistry() throws IOException { + Properties properties = getProperties(); + return new StandardServiceRegistryBuilder().applySettings(properties) + .build(); + } + + private static Properties getProperties() throws IOException { + Properties properties = new Properties(); + URL propertiesURL = Thread.currentThread() + .getContextClassLoader() + .getResource(StringUtils.defaultString(PROPERTY_FILE_NAME, "hibernate_postgres.properties")); + try (FileInputStream inputStream = new FileInputStream(propertiesURL.getFile())) { + properties.load(inputStream); + } + return properties; + } +} diff --git a/persistence-modules/hibernate-mapping/src/main/java/com/baeldung/hibernate/arraymapping/User.java b/persistence-modules/hibernate-mapping/src/main/java/com/baeldung/hibernate/arraymapping/User.java new file mode 100644 index 0000000000..018bedc349 --- /dev/null +++ b/persistence-modules/hibernate-mapping/src/main/java/com/baeldung/hibernate/arraymapping/User.java @@ -0,0 +1,82 @@ +package com.baeldung.hibernate.arraymapping; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.annotations.Type; + +import com.vladmihalcea.hibernate.type.array.StringArrayType; + +import org.hibernate.annotations.*; + +@TypeDefs({ + @TypeDef( + name = "string-array", + typeClass = StringArrayType.class + ) +}) +@Entity +public class User { + + @Id + private Long id; + + private String name; + + @Column(columnDefinition = "text[]") + @Type(type = "com.baeldung.hibernate.arraymapping.CustomStringArrayType") + private String[] roles; + + @Column(columnDefinition = "int[]") + @Type(type = "com.baeldung.hibernate.arraymapping.CustomIntegerArrayType") + private Integer[] locations; + + @Type(type = "string-array") + @Column( + name = "phone_numbers", + columnDefinition = "text[]" + ) + private String[] phoneNumbers; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String[] getRoles() { + return roles; + } + + public void setRoles(String[] roles) { + this.roles = roles; + } + + public Integer[] getLocations() { + return locations; + } + + public void setLocations(Integer[] locations) { + this.locations = locations; + } + + public String[] getPhoneNumbers() { + return phoneNumbers; + } + + public void setPhoneNumbers(String[] phoneNumbers) { + this.phoneNumbers = phoneNumbers; + } + +} diff --git a/persistence-modules/hibernate-mapping/src/test/java/com/baeldung/hibernate/arraymapping/ArrayMappingIntegrationTest.java b/persistence-modules/hibernate-mapping/src/test/java/com/baeldung/hibernate/arraymapping/ArrayMappingIntegrationTest.java new file mode 100644 index 0000000000..ba0ccc2aa2 --- /dev/null +++ b/persistence-modules/hibernate-mapping/src/test/java/com/baeldung/hibernate/arraymapping/ArrayMappingIntegrationTest.java @@ -0,0 +1,133 @@ +package com.baeldung.hibernate.arraymapping; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; + +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + + +public class ArrayMappingIntegrationTest { + + private Session session; + private Transaction transaction; + + @BeforeEach + public void setup() throws IOException { + try { + session = HibernateSessionUtil.getSessionFactory().openSession(); + transaction = session.beginTransaction(); + + bootstrapData(); + + } catch (HibernateException | IOException e) { + System.out.println("Can't connect to a PostgreSQL DB"); + } + } + + @AfterEach + public void cleanup() { + if (null != session) { + transaction.rollback(); + session.close(); + } + } + + @Test + public void givenArrayMapping_whenQueried_thenReturnArraysFromDB() throws HibernateException, IOException { + if (null != session) { + User user = session.find(User.class, 1L); + + assertEquals("john", user.getName()); + assertEquals("superuser", user.getRoles()[0]); + assertEquals("admin", user.getRoles()[1]); + assertEquals(100, user.getLocations()[0]); + assertEquals(389, user.getLocations()[1]); + assertEquals("7000000000", user.getPhoneNumbers()[0]); + assertEquals("8000000000", user.getPhoneNumbers()[1]); + } + } + + @Test + public void givenArrayMapping_whenArraysAreInserted_thenPersistInDB() throws HibernateException, IOException { + if (null != session) { + transaction = session.beginTransaction(); + + User user = new User(); + user.setId(2L); + user.setName("smith"); + + String[] roles = {"admin", "employee"}; + user.setRoles(roles); + + Integer[] locations = {190, 578}; + user.setLocations(locations); + + session.persist(user); + session.flush(); + session.clear(); + + transaction.commit(); + + User userDBObj = session.find(User.class, 2L); + + assertEquals("smith", userDBObj.getName()); + assertEquals("admin", userDBObj.getRoles()[0]); + assertEquals(578, userDBObj.getLocations()[1]); + } + } + + @Test + public void givenArrayMapping_whenArrayIsUpdated_thenPersistInDB() throws HibernateException, IOException { + if (null != session) { + transaction = session.beginTransaction(); + + User user = session.find(User.class, 1L); + + String[] updatedRoles = {"superuser", "superadmin"}; + String[] updatedPhoneNumbers = {"9000000000"}; + + user.setRoles(updatedRoles); + user.setPhoneNumbers(updatedPhoneNumbers); + + session.persist(user); + session.flush(); + session.clear(); + + User userDBObj = session.find(User.class, 1L); + + assertEquals("john", userDBObj.getName()); + assertEquals("superadmin", userDBObj.getRoles()[1]); + assertEquals("9000000000", userDBObj.getPhoneNumbers()[0]); + } + } + + public void bootstrapData() { + session.createQuery("delete from User").executeUpdate(); + + User user = new User(); + user.setId(1L); + user.setName("john"); + + String[] roles = {"superuser", "admin"}; + user.setRoles(roles); + + Integer[] locations = {100, 389}; + user.setLocations(locations); + + String[] phoneNumbers = {"7000000000", "8000000000"}; + user.setPhoneNumbers(phoneNumbers); + + session.persist(user); + session.flush(); + session.clear(); + + transaction.commit(); + } + +} diff --git a/persistence-modules/hibernate-mapping/src/test/resources/hibernate_postgres.properties b/persistence-modules/hibernate-mapping/src/test/resources/hibernate_postgres.properties new file mode 100644 index 0000000000..72e3dcd781 --- /dev/null +++ b/persistence-modules/hibernate-mapping/src/test/resources/hibernate_postgres.properties @@ -0,0 +1,14 @@ +hibernate.connection.driver_class=org.postgresql.Driver +hibernate.connection.url=jdbc:postgresql://localhost:5432/postgres +hibernate.connection.username=web +hibernate.connection.autocommit=true +jdbc.password= + +hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +hibernate.show_sql=true +hibernate.hbm2ddl.auto=none + +hibernate.c3p0.min_size=5 +hibernate.c3p0.max_size=20 +hibernate.c3p0.acquire_increment=5 +hibernate.c3p0.timeout=1800