From 568198fed9bbfb9c87a6c440f1d695ce956fc613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=9F=D0=B5=D1=82?= =?UTF-8?q?=D1=83=D0=BD=D0=B8=D0=BD?= Date: Sun, 29 Oct 2017 11:43:08 +0100 Subject: [PATCH] BAEL-1280: Mapping Dynamic Values (@Formula, @Filter, @Any, @Where) --- hibernate5/pom.xml | 2 +- .../hibernate/HibernateMultiTenantUtil.java | 2 +- .../com/baeldung/hibernate/HibernateUtil.java | 62 +++++-- .../com/baeldung/hibernate/pojo/Employee.java | 87 ++++++++++ .../hibernate/pojo/EntityDescription.java | 55 ++++++ .../com/baeldung/hibernate/pojo/Phone.java | 50 ++++++ .../baeldung/hibernate/pojo/package-info.java | 9 + .../hibernate/DynamicMappingTest.java | 164 ++++++++++++++++++ .../hibernate-multitenancy.properties | 9 + .../src/test/resources/hibernate.properties | 2 +- 10 files changed, 425 insertions(+), 17 deletions(-) create mode 100644 hibernate5/src/main/java/com/baeldung/hibernate/pojo/Employee.java create mode 100644 hibernate5/src/main/java/com/baeldung/hibernate/pojo/EntityDescription.java create mode 100644 hibernate5/src/main/java/com/baeldung/hibernate/pojo/Phone.java create mode 100644 hibernate5/src/main/java/com/baeldung/hibernate/pojo/package-info.java create mode 100644 hibernate5/src/test/java/com/baeldung/hibernate/DynamicMappingTest.java create mode 100644 hibernate5/src/test/resources/hibernate-multitenancy.properties diff --git a/hibernate5/pom.xml b/hibernate5/pom.xml index c501b7f11d..a9e3742e1c 100644 --- a/hibernate5/pom.xml +++ b/hibernate5/pom.xml @@ -22,7 +22,7 @@ org.hibernate hibernate-core - 5.2.9.Final + 5.2.12.Final junit diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/HibernateMultiTenantUtil.java b/hibernate5/src/main/java/com/baeldung/hibernate/HibernateMultiTenantUtil.java index 5a10b2ba56..30f3c3cf53 100644 --- a/hibernate5/src/main/java/com/baeldung/hibernate/HibernateMultiTenantUtil.java +++ b/hibernate5/src/main/java/com/baeldung/hibernate/HibernateMultiTenantUtil.java @@ -68,7 +68,7 @@ public class HibernateMultiTenantUtil { Properties properties = new Properties(); URL propertiesURL = Thread.currentThread() .getContextClassLoader() - .getResource("hibernate.properties"); + .getResource("hibernate-multitenancy.properties"); FileInputStream inputStream = new FileInputStream(propertiesURL.getFile()); properties.load(inputStream); System.out.println("LOADED PROPERTIES FROM hibernate.properties"); diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/HibernateUtil.java b/hibernate5/src/main/java/com/baeldung/hibernate/HibernateUtil.java index c1f7301d46..91392bd454 100644 --- a/hibernate5/src/main/java/com/baeldung/hibernate/HibernateUtil.java +++ b/hibernate5/src/main/java/com/baeldung/hibernate/HibernateUtil.java @@ -1,24 +1,58 @@ package com.baeldung.hibernate; +import com.baeldung.hibernate.pojo.Employee; +import com.baeldung.hibernate.pojo.EntityDescription; +import com.baeldung.hibernate.pojo.Phone; import org.hibernate.SessionFactory; -import org.hibernate.cfg.Configuration; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.service.ServiceRegistry; + +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; +import java.util.Properties; public class HibernateUtil { + private static SessionFactory sessionFactory; - private static final SessionFactory sessionFactory; - - static { - try { - Configuration configuration = new Configuration().configure(); - sessionFactory = configuration.buildSessionFactory(); - - } catch (Throwable ex) { - System.err.println("Initial SessionFactory creation failed." + ex); - throw new ExceptionInInitializerError(ex); + public static SessionFactory getSessionFactory() throws IOException { + if (sessionFactory == null) { + ServiceRegistry serviceRegistry = configureServiceRegistry(); + sessionFactory = makeSessionFactory(serviceRegistry); } - } - - public static SessionFactory getSessionFactory() { return sessionFactory; } + + 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); + metadataSources.addAnnotatedClass(EntityDescription.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("hibernate.properties"); + try (FileInputStream inputStream = new FileInputStream(propertiesURL.getFile())) { + properties.load(inputStream); + } + return properties; + } + } \ No newline at end of file diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/pojo/Employee.java b/hibernate5/src/main/java/com/baeldung/hibernate/pojo/Employee.java new file mode 100644 index 0000000000..e9732b2b67 --- /dev/null +++ b/hibernate5/src/main/java/com/baeldung/hibernate/pojo/Employee.java @@ -0,0 +1,87 @@ +package com.baeldung.hibernate.pojo; + +import org.hibernate.annotations.*; + +import javax.persistence.Entity; +import javax.persistence.*; +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + +@Entity +@Where(clause = "deleted = false") +@FilterDef(name = "incomeLevelFilter", parameters = @ParamDef(name = "incomeLimit", type = "int")) +@Filter(name = "incomeLevelFilter", condition = "grossIncome > :incomeLimit") +public class Employee implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + private long grossIncome; + + private int taxInPercents; + + private boolean deleted; + + public long getTaxJavaWay() { + return grossIncome * taxInPercents / 100; + } + + @Formula("grossIncome * taxInPercents / 100") + private long tax; + + @OneToMany + @JoinColumn(name = "employee_id") + @Where(clause = "deleted = false") + private Set phones = new HashSet<>(0); + + public Employee() { + } + + public Employee(long grossIncome, int taxInPercents) { + this.grossIncome = grossIncome; + this.taxInPercents = taxInPercents; + } + + public Integer getId() { + return id; + } + + public long getGrossIncome() { + return grossIncome; + } + + public int getTaxInPercents() { + return taxInPercents; + } + + public long getTax() { + return tax; + } + + public void setId(Integer id) { + this.id = id; + } + + public void setGrossIncome(long grossIncome) { + this.grossIncome = grossIncome; + } + + public void setTaxInPercents(int taxInPercents) { + this.taxInPercents = taxInPercents; + } + + public boolean getDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } + + public Set getPhones() { + return phones; + } + +} diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/pojo/EntityDescription.java b/hibernate5/src/main/java/com/baeldung/hibernate/pojo/EntityDescription.java new file mode 100644 index 0000000000..131bb73a80 --- /dev/null +++ b/hibernate5/src/main/java/com/baeldung/hibernate/pojo/EntityDescription.java @@ -0,0 +1,55 @@ +package com.baeldung.hibernate.pojo; + +import org.hibernate.annotations.Any; + +import javax.persistence.*; +import java.io.Serializable; + +@Entity +public class EntityDescription implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + private String description; + + @Any( + metaDef = "EntityDescriptionMetaDef", + metaColumn = @Column(name = "entity_type") + ) + @JoinColumn(name = "entity_id") + private Serializable entity; + + public EntityDescription() { + } + + public EntityDescription(String description, Serializable entity) { + this.description = description; + this.entity = entity; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Serializable getEntity() { + return entity; + } + + public void setEntity(Serializable entity) { + this.entity = entity; + } +} diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/pojo/Phone.java b/hibernate5/src/main/java/com/baeldung/hibernate/pojo/Phone.java new file mode 100644 index 0000000000..d923bda5de --- /dev/null +++ b/hibernate5/src/main/java/com/baeldung/hibernate/pojo/Phone.java @@ -0,0 +1,50 @@ +package com.baeldung.hibernate.pojo; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import java.io.Serializable; + +@Entity +public class Phone implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + private boolean deleted; + + private String number; + + public Phone() { + } + + public Phone(String number) { + this.number = number; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } +} diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/pojo/package-info.java b/hibernate5/src/main/java/com/baeldung/hibernate/pojo/package-info.java new file mode 100644 index 0000000000..992cda7c1d --- /dev/null +++ b/hibernate5/src/main/java/com/baeldung/hibernate/pojo/package-info.java @@ -0,0 +1,9 @@ +@AnyMetaDef(name = "EntityDescriptionMetaDef", metaType = "string", idType = "int", + metaValues = { + @MetaValue(value = "Employee", targetEntity = Employee.class), + @MetaValue(value = "Phone", targetEntity = Phone.class) + }) +package com.baeldung.hibernate.pojo; + +import org.hibernate.annotations.AnyMetaDef; +import org.hibernate.annotations.MetaValue; \ No newline at end of file diff --git a/hibernate5/src/test/java/com/baeldung/hibernate/DynamicMappingTest.java b/hibernate5/src/test/java/com/baeldung/hibernate/DynamicMappingTest.java new file mode 100644 index 0000000000..456985eeae --- /dev/null +++ b/hibernate5/src/test/java/com/baeldung/hibernate/DynamicMappingTest.java @@ -0,0 +1,164 @@ +package com.baeldung.hibernate; + +import com.baeldung.hibernate.pojo.Employee; +import com.baeldung.hibernate.pojo.EntityDescription; +import com.baeldung.hibernate.pojo.Phone; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.*; + +public class DynamicMappingTest { + + private Session session; + + private Transaction transaction; + + @Before + public void setUp() throws IOException { + session = HibernateUtil.getSessionFactory().openSession(); + transaction = session.beginTransaction(); + + session.createNativeQuery("delete from phone").executeUpdate(); + session.createNativeQuery("delete from employee").executeUpdate(); + + transaction.commit(); + transaction = session.beginTransaction(); + } + + @After + public void tearDown() { + transaction.commit(); + session.close(); + } + + @Test + public void givenEntity_whenFieldMappedWithFormula_thenFieldIsCalculated() { + Employee employee = new Employee(10_000L, 25); + assertEquals(2_500L, employee.getTaxJavaWay()); + + session.save(employee); + session.flush(); + session.clear(); + + employee = session.get(Employee.class, employee.getId()); + assertEquals(2_500L, employee.getTax()); + } + + @Test + public void givenEntityMappedWithWhere_whenDeletedIsTrue_thenEntityNotFetched() { + Employee employee = new Employee(); + + session.save(employee); + session.clear(); + + employee = session.find(Employee.class, employee.getId()); + assertNotNull(employee); + + employee.setDeleted(true); + session.flush(); + + employee = session.find(Employee.class, employee.getId()); + assertNotNull(employee); + + session.clear(); + + employee = session.find(Employee.class, employee.getId()); + assertNull(employee); + + } + + @Test + public void givenCollectionMappedWithWhere_whenDeletedIsTrue_thenEntityNotFetched() { + Employee employee = new Employee(); + Phone phone1 = new Phone("555-45-67"); + Phone phone2 = new Phone("555-89-01"); + employee.getPhones().add(phone1); + employee.getPhones().add(phone2); + + session.save(phone1); + session.save(phone2); + session.save(employee); + session.flush(); + session.clear(); + + employee = session.find(Employee.class, employee.getId()); + assertEquals(employee.getPhones().size(), 2); + + employee.getPhones().iterator().next().setDeleted(true); + session.flush(); + session.clear(); + + employee = session.find(Employee.class, employee.getId()); + assertEquals(employee.getPhones().size(), 1); + + List fullPhoneList = session.createQuery("from Phone").getResultList(); + assertEquals(2, fullPhoneList.size()); + + } + + @Test + public void givenFilterByIncome_whenIncomeLimitSet_thenFilterIsApplied() throws IOException { + session.save(new Employee(10_000, 25)); + session.save(new Employee(12_000, 25)); + session.save(new Employee(15_000, 25)); + + session.flush(); + session.clear(); + + session.enableFilter("incomeLevelFilter") + .setParameter("incomeLimit", 11_000); + + List employees = session.createQuery("from Employee").getResultList(); + + assertEquals(2, employees.size()); + + Employee employee = session.get(Employee.class, 1); + assertEquals(10_000, employee.getGrossIncome()); + + session.close(); + + session = HibernateUtil.getSessionFactory().openSession(); + transaction = session.beginTransaction(); + + employees = session.createQuery("from Employee").getResultList(); + + assertEquals(3, employees.size()); + + } + + @Test + public void givenMappingWithAny_whenDescriptionAddedToEntity_thenDescriptionCanReferAnyEntity() { + Employee employee = new Employee(); + Phone phone1 = new Phone("555-45-67"); + Phone phone2 = new Phone("555-89-01"); + employee.getPhones().add(phone1); + employee.getPhones().add(phone2); + + EntityDescription employeeDescription = new EntityDescription("Send to conference next year", employee); + EntityDescription phone1Description = new EntityDescription("Home phone (do not call after 10PM)", phone1); + EntityDescription phone2Description = new EntityDescription("Work phone", phone1); + + session.save(phone1); + session.save(phone2); + session.save(employee); + session.save(employeeDescription); + session.save(phone1Description); + session.save(phone2Description); + session.flush(); + session.clear(); + + List descriptions = session.createQuery("from EntityDescription").getResultList(); + + assertTrue(Employee.class.isAssignableFrom(descriptions.get(0).getEntity().getClass())); + assertTrue(Phone.class.isAssignableFrom(descriptions.get(1).getEntity().getClass())); + assertTrue(Phone.class.isAssignableFrom(descriptions.get(2).getEntity().getClass())); + } + +} diff --git a/hibernate5/src/test/resources/hibernate-multitenancy.properties b/hibernate5/src/test/resources/hibernate-multitenancy.properties new file mode 100644 index 0000000000..298ecd05d3 --- /dev/null +++ b/hibernate5/src/test/resources/hibernate-multitenancy.properties @@ -0,0 +1,9 @@ +hibernate.connection.driver_class=org.h2.Driver +hibernate.connection.url=jdbc:h2:mem:mydb1;DB_CLOSE_DELAY=-1 +hibernate.connection.username=sa +hibernate.connection.autocommit=true +jdbc.password= + +hibernate.dialect=org.hibernate.dialect.H2Dialect +hibernate.show_sql=true +hibernate.multiTenancy=DATABASE diff --git a/hibernate5/src/test/resources/hibernate.properties b/hibernate5/src/test/resources/hibernate.properties index 298ecd05d3..7b8764637b 100644 --- a/hibernate5/src/test/resources/hibernate.properties +++ b/hibernate5/src/test/resources/hibernate.properties @@ -6,4 +6,4 @@ jdbc.password= hibernate.dialect=org.hibernate.dialect.H2Dialect hibernate.show_sql=true -hibernate.multiTenancy=DATABASE +hibernate.hbm2ddl.auto=create-drop \ No newline at end of file