Java tutorial
/* * Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved. * * This file is part of the Jspresso framework. * * Jspresso is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Jspresso is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Jspresso. If not, see <http://www.gnu.org/licenses/>. */ package org.jspresso.hrsample.backend; import static org.junit.Assert.*; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import org.hibernate.Hibernate; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.Restrictions; import org.junit.Test; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.jdbc.core.ConnectionCallback; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import org.jspresso.framework.application.backend.BackendControllerHolder; import org.jspresso.framework.application.backend.BackendException; import org.jspresso.framework.application.backend.ControllerAwareTransactionTemplate; import org.jspresso.framework.application.backend.persistence.hibernate.HibernateBackendController; import org.jspresso.framework.application.backend.persistence.hibernate.HibernateHelper; import org.jspresso.framework.application.backend.session.EMergeMode; import org.jspresso.framework.model.component.IPropertyTranslation; import org.jspresso.framework.model.entity.IEntity; import org.jspresso.framework.model.persistence.hibernate.criterion.EnhancedDetachedCriteria; import org.jspresso.hrsample.model.City; import org.jspresso.hrsample.model.Company; import org.jspresso.hrsample.model.ContactInfo; import org.jspresso.hrsample.model.Department; import org.jspresso.hrsample.model.Employee; import org.jspresso.hrsample.model.Event; import org.jspresso.hrsample.model.Nameable; /** * Unit Of Work management integration tests. * * @author Vincent Vandenschrick */ public class JspressoUnitOfWorkTest extends BackTestStartup { /** * Test retrieve UOW cloned instance from session. See bug 746. */ @Test public void testClonedInstanceFromHibernateSession() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); EnhancedDetachedCriteria departmentCriteria = EnhancedDetachedCriteria.forClass(Department.class); final List<Department> departments = hbc.findByCriteria(departmentCriteria, EMergeMode.MERGE_KEEP, Department.class); EnhancedDetachedCriteria companyCriteria = EnhancedDetachedCriteria.forClass(Company.class); final Company company = hbc.findFirstByCriteria(companyCriteria, EMergeMode.MERGE_KEEP, Company.class); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { // Clone the company Company companyClone = hbc.cloneInUnitOfWork(company); // Check that the departments property is not initialized assertFalse(Hibernate.isInitialized(companyClone.straightGetProperty(Company.DEPARTMENTS))); // Retrieve the company departments (load them in the Hibernate // session) Set<Department> hibernateSessionDepartments = companyClone.getDepartments(); Map<Serializable, Department> hibernateSessionDepartmentsById = new HashMap<Serializable, Department>(); for (Department d : hibernateSessionDepartments) { hibernateSessionDepartmentsById.put(d.getId(), d); } // Now clone each of the Jspresso session department and verify that // there is no reference duplication, ie.e we are able to retrieve // the Hibernate session instances. List<Department> departmentClones = hbc.cloneInUnitOfWork(departments); for (Department d : departmentClones) { Department hibernateSessionDepartment = hibernateSessionDepartmentsById.get(d.getId()); if (hibernateSessionDepartment != null) { assertSame(hibernateSessionDepartment, d); } } } }); } /** * Clone/merge of entity lists with holes. See bug 757. */ @Test public void testCloneEntityListWithHoles() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); EnhancedDetachedCriteria employeeCriteria = EnhancedDetachedCriteria.forClass(Employee.class); final Employee emp = hbc.findFirstByCriteria(employeeCriteria, EMergeMode.MERGE_KEEP, Employee.class); List<Event> events = new ArrayList<Event>(); events.add(hbc.getEntityFactory().createEntityInstance(Event.class)); events.add(null); events.add(hbc.getEntityFactory().createEntityInstance(Event.class)); emp.setAlternativeEvents(events); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { hbc.cloneInUnitOfWork(emp); } }); emp.addToAlternativeEvents(hbc.getEntityFactory().createEntityInstance(Event.class)); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { hbc.cloneInUnitOfWork(emp); } }); } /** * Clone/merge of component lists with holes. See bug 757. */ @Test public void testCloneComponentListWithHoles() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); EnhancedDetachedCriteria employeeCriteria = EnhancedDetachedCriteria.forClass(Employee.class); final Employee emp = hbc.findFirstByCriteria(employeeCriteria, EMergeMode.MERGE_KEEP, Employee.class); List<ContactInfo> alternativeContacts = new ArrayList<ContactInfo>(); alternativeContacts.add(hbc.getEntityFactory().createComponentInstance(ContactInfo.class)); alternativeContacts.add(null); alternativeContacts.add(hbc.getEntityFactory().createComponentInstance(ContactInfo.class)); emp.setAlternativeContacts(alternativeContacts); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { hbc.cloneInUnitOfWork(emp); } }); emp.addToAlternativeContacts(hbc.getEntityFactory().createComponentInstance(ContactInfo.class)); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { hbc.cloneInUnitOfWork(emp); } }); } /** * Tests the use of nested transactions. */ @Test public void testNestedTransactions() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); final TransactionTemplate tt = hbc.getTransactionTemplate(); Serializable empId = tt.execute(new TransactionCallback<Serializable>() { @Override public Serializable doInTransaction(TransactionStatus status) { TransactionTemplate nestedTT = new ControllerAwareTransactionTemplate(tt.getTransactionManager()); nestedTT.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); Serializable id = nestedTT.execute(new TransactionCallback<Serializable>() { @Override public Serializable doInTransaction(TransactionStatus nestedStatus) { DetachedCriteria empCrit = DetachedCriteria.forClass(Employee.class); Employee emp = hbc.findFirstByCriteria(empCrit, null, Employee.class); emp.setFirstName("Committed"); return emp.getId(); } }); // asserts that UOW is still active after the end of the nested transaction. assertTrue("UOW should still be active since outer TX is ongoing.", hbc.isUnitOfWorkActive()); // forces rollback of outer TX. status.setRollbackOnly(); return id; } }); DetachedCriteria empById = DetachedCriteria.forClass(Employee.class); empById.add(Restrictions.eq(IEntity.ID, empId)); Employee emp = hbc.findFirstByCriteria(empById, EMergeMode.MERGE_CLEAN_EAGER, Employee.class); assertTrue("Inner transaction should have been committed", "Committed".equals(emp.getFirstName())); Serializable emp2Id = tt.execute(new TransactionCallback<Serializable>() { @Override public Serializable doInTransaction(TransactionStatus status) { TransactionTemplate nestedTT = new ControllerAwareTransactionTemplate(tt.getTransactionManager()); nestedTT.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); Serializable id = nestedTT.execute(new TransactionCallback<Serializable>() { @Override public Serializable doInTransaction(TransactionStatus nestedStatus) { DetachedCriteria empCrit = DetachedCriteria.forClass(Employee.class); Employee emp2 = hbc.findFirstByCriteria(empCrit, null, Employee.class); emp2.setFirstName("Rollbacked"); return emp2.getId(); } }); // forces rollback of outer TX. status.setRollbackOnly(); return id; } }); DetachedCriteria emp2ById = DetachedCriteria.forClass(Employee.class); emp2ById.add(Restrictions.eq(IEntity.ID, emp2Id)); Employee emp2 = hbc.findFirstByCriteria(empById, EMergeMode.MERGE_CLEAN_EAGER, Employee.class); assertFalse("Inner transaction should have been rollbacked", "Rollbacked".equals(emp2.getFirstName())); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { City newCity = hbc.getEntityFactory().createEntityInstance(City.class); newCity.setName("Test City"); TransactionTemplate nestedTT = new ControllerAwareTransactionTemplate(tt.getTransactionManager()); nestedTT.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); String testZip = nestedTT.execute(new TransactionCallback<String>() { @Override public String doInTransaction(TransactionStatus nestedStatus) { return "12345"; } }); newCity.setZip(testZip); hbc.registerForUpdate(newCity); } }); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { final City randomCity = hbc.findFirstByCriteria(DetachedCriteria.forClass(City.class), EMergeMode.MERGE_KEEP, City.class); TransactionTemplate nestedTT = new ControllerAwareTransactionTemplate(tt.getTransactionManager()); nestedTT.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); nestedTT.execute(new TransactionCallbackWithoutResult() { @Override public void doInTransactionWithoutResult(TransactionStatus nestedStatus) { DetachedCriteria cityById = DetachedCriteria.forClass(City.class); cityById.add(Restrictions.eq(IEntity.ID, randomCity.getId())); City innerRandomCity = (City) cityById.getExecutableCriteria(hbc.getHibernateSession()) .uniqueResult(); // If we reach this point without exception, there is no mix between the inner TX and the outer UOW. // See bug #1118 } }); } }); } /** * Tests sanity check on linked components. See bug #846. */ @Test(expected = BackendException.class) public void testSanityChecksOnComponents() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); final TransactionTemplate tt = hbc.getTransactionTemplate(); Employee emp = tt.execute(new TransactionCallback<Employee>() { /** * {@inheritDoc} */ @Override public Employee doInTransaction(TransactionStatus status) { DetachedCriteria empCrit = DetachedCriteria.forClass(Employee.class); return (Employee) empCrit.getExecutableCriteria(hbc.getHibernateSession()).list().iterator().next(); } }); // From here, any modification on employee should result in an exception // since this instance of employee is not merged in session. // The exception should also occur on component (contact) properties // modification. emp.getContact().setAddress("test"); } /** * Tests merge modes. */ @Test public void testMergeModes() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); EnhancedDetachedCriteria crit = EnhancedDetachedCriteria.forClass(City.class); final City c1 = hbc.findFirstByCriteria(crit, EMergeMode.MERGE_CLEAN_EAGER, City.class); String name = c1.getName(); JdbcTemplate jdbcTemplate = getApplicationContext().getBean("jdbcTemplate", JdbcTemplate.class); jdbcTemplate.execute(new ConnectionCallback<Object>() { @Override public Object doInConnection(Connection con) throws SQLException { PreparedStatement ps = con.prepareStatement("UPDATE CITY SET NAME = ? WHERE ID = ?"); ps.setString(1, "test"); ps.setObject(2, c1.getId()); assertEquals(1, ps.executeUpdate()); return null; } }); final City c2 = hbc.findById(c1.getId(), EMergeMode.MERGE_CLEAN_LAZY, City.class); assertSame(c1, c2); assertEquals(name, c2.getName()); final City c3 = hbc.findById(c1.getId(), EMergeMode.MERGE_CLEAN_EAGER, City.class); assertSame(c1, c3); assertEquals("test", c3.getNameRaw()); jdbcTemplate.execute(new ConnectionCallback<Object>() { @Override public Object doInConnection(Connection con) throws SQLException { PreparedStatement ps = con .prepareStatement("UPDATE CITY SET NAME = ?, VERSION = VERSION+1 WHERE ID = ?"); ps.setString(1, "test2"); ps.setObject(2, c1.getId()); assertEquals(1, ps.executeUpdate()); return null; } }); final City c4 = hbc.findById(c1.getId(), EMergeMode.MERGE_KEEP, City.class); assertSame(c1, c4); assertEquals("test", c4.getNameRaw()); final City c5 = hbc.findById(c1.getId(), EMergeMode.MERGE_CLEAN_LAZY, City.class); assertSame(c1, c5); assertEquals("test2", c5.getNameRaw()); } /** * Tests in TX collection element update with // optimistic locking. */ @Test public void testInTXCollectionElementUpdate() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); final AtomicInteger countDown = new AtomicInteger(10); ExecutorService es = Executors.newFixedThreadPool(countDown.get()); List<Future<Set<String>>> futures = new ArrayList<Future<Set<String>>>(); for (int t = countDown.intValue(); t > 0; t--) { futures.add(es.submit(new Callable<Set<String>>() { @Override public Set<String> call() throws Exception { final HibernateBackendController threadHbc = getApplicationContext() .getBean("applicationBackController", HibernateBackendController.class); final TransactionTemplate threadTT = threadHbc.getTransactionTemplate(); threadHbc.start(hbc.getLocale(), hbc.getClientTimeZone()); threadHbc.setApplicationSession(hbc.getApplicationSession()); BackendControllerHolder.setThreadBackendController(threadHbc); return threadTT.execute(new TransactionCallback<Set<String>>() { /** * {@inheritDoc} */ @Override public Set<String> doInTransaction(TransactionStatus status) { DetachedCriteria compCrit = DetachedCriteria.forClass(Company.class); Set<String> names = new HashSet<String>(); Company c = (Company) compCrit.getExecutableCriteria(threadHbc.getHibernateSession()) .list().iterator().next(); synchronized (countDown) { countDown.decrementAndGet(); // wait for all threads to arrive here so that we are sure they // have all read the same data. try { countDown.wait(); } catch (InterruptedException ex) { throw new BackendException("Test has been interrupted"); } } if (c.getName().startsWith("TX_")) { throw new BackendException("Wrong data read from DB"); } c.setName("TX_" + Long.toHexString(System.currentTimeMillis())); names.add(c.getName()); for (Department d : c.getDepartments()) { d.setName(Long.toHexString(System.currentTimeMillis())); names.add(d.getName()); } return names; } }); } })); } while (countDown.get() > 0) { try { Thread.sleep(200); } catch (InterruptedException ex) { throw new BackendException("Test has been interrupted"); } } synchronized (countDown) { countDown.notifyAll(); } int successfullTxCount = 0; Set<String> names = new HashSet<String>(); for (Future<Set<String>> f : futures) { try { names = f.get(); successfullTxCount++; } catch (Exception ex) { if (ex.getCause() instanceof OptimisticLockingFailureException) { // safely ignore since this is what we are testing. } else { throw new BackendException(ex); } } } es.shutdown(); assertTrue("Only 1 TX succeeded", successfullTxCount == 1); DetachedCriteria compCrit = DetachedCriteria.forClass(Company.class); Company c = hbc.findFirstByCriteria(compCrit, EMergeMode.MERGE_LAZY, Company.class); assertTrue("the company name is the one of the successfull TX", names.contains(c.getName())); for (Department d : c.getDepartments()) { assertTrue("the department name is the one of the successfull TX", names.contains(d.getName())); } } /** * Test dirty properties in UOW. See bug #1018. */ @Test public void testDirtyPropertiesInUOW() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); EnhancedDetachedCriteria companyCriteria = EnhancedDetachedCriteria.forClass(Company.class); final Company comp = hbc.findFirstByCriteria(companyCriteria, EMergeMode.MERGE_KEEP, Company.class); assertTrue("Dirty properties are not initialized", hbc.getDirtyProperties(comp) != null); assertTrue("Dirty properties are not empty", hbc.getDirtyProperties(comp).isEmpty()); comp.setName("Updated"); assertTrue("Company name is not dirty", hbc.getDirtyProperties(comp).containsKey(Nameable.NAME)); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { EnhancedDetachedCriteria departmentCriteria = EnhancedDetachedCriteria.forClass(Department.class); Department dep = hbc.findFirstByCriteria(departmentCriteria, EMergeMode.MERGE_KEEP, Department.class); assertFalse("Company property is already initialized", Hibernate.isInitialized(dep.straightGetProperty(Department.COMPANY))); // Should be initialized now Company uowComp = dep.getCompany(); assertTrue("Dirty properties are not initialized", hbc.getDirtyProperties(uowComp) != null); assertTrue("Dirty properties are not empty", hbc.getDirtyProperties(uowComp).isEmpty()); assertFalse("Company is not fresh from DB", comp.getName().equals(uowComp.getName())); uowComp.setNameRaw("UpdatedUow"); assertTrue("Company name is not dirty", hbc.getDirtyProperties(uowComp).containsKey(Nameable.NAME_RAW)); } }); assertEquals("Company name has not been correctly committed", "UpdatedUow", comp.getNameRaw()); } /** * Test uninitialized reference properties merge on commit. See bug #1023. */ @Test public void testUnititializedPropertiesMerge() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); EnhancedDetachedCriteria departmentCriteria = EnhancedDetachedCriteria.forClass(Department.class); List<Department> departments = hbc.findByCriteria(departmentCriteria, EMergeMode.MERGE_KEEP, Department.class); final Department department = departments.get(0); final Company existingCompany = (Company) department.straightGetProperty(Department.COMPANY); assertFalse("Company property is already initialized", Hibernate.isInitialized(existingCompany)); Serializable newCompanyId = hbc.getTransactionTemplate().execute(new TransactionCallback<Serializable>() { @Override public Serializable doInTransaction(TransactionStatus status) { Department departmentClone = hbc.cloneInUnitOfWork(department); Company newCompany = hbc.getEntityFactory().createEntityInstance(Company.class); newCompany.setName("NewCompany"); departmentClone.setCompany(newCompany); return newCompany.getId(); } }); assertEquals("New company reference is not correctly merged", newCompanyId, department.getCompany().getId()); assertEquals("New company name is not correctly merged", "NewCompany", department.getCompany().getName()); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { List<IEntity> clonedEntities = hbc .cloneInUnitOfWork(Arrays.asList((IEntity) existingCompany, department)); Company existingCompanyClone = (Company) clonedEntities.get(0); assertFalse("Company clone is already initialized", Hibernate.isInitialized(existingCompanyClone)); Department departmentClone = (Department) clonedEntities.get(1); departmentClone.setCompany(existingCompanyClone); } }); assertEquals("New company reference is not correctly merged", existingCompany.getId(), department.getCompany().getId()); final Department otherDepartment = departments.get(1); Department deptFromUow = hbc.getTransactionTemplate().execute(new TransactionCallback<Department>() { @Override public Department doInTransaction(TransactionStatus status) { Department d = hbc.findById(otherDepartment.getId(), null, Department.class); return d; } }); assertFalse("Department Company property from UOW is initialized", Hibernate.isInitialized(deptFromUow.straightGetProperty(Department.COMPANY))); hbc.merge(deptFromUow, EMergeMode.MERGE_EAGER); } /** * Test update / delete in TX. See bug #1009. */ @Test public void testUpdateDeleteInTx() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); final City c1 = hbc.getEntityFactory().createEntityInstance(City.class); c1.setName("ToUpdate"); c1.setZip("12345"); hbc.registerForUpdate(c1); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { hbc.performPendingOperations(); } }); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { City c1Clone = hbc.cloneInUnitOfWork(c1); c1Clone.setName("ToDelete"); hbc.registerForUpdate(c1Clone); hbc.registerForDeletion(c1Clone); } }); City c2 = hbc.findById(c1.getId(), EMergeMode.MERGE_KEEP, City.class); assertNull("City has not been deleted correctly.", c2); } /** * Test clone in UOW of just initialized collection properties (recorded * original value might be UNKNOWN). */ @Test public void testJustInitializedCollectionPropertyClone() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); final EnhancedDetachedCriteria departmentCriteria = EnhancedDetachedCriteria.forClass(Department.class); final List<Department> departments = hbc.findByCriteria(departmentCriteria, EMergeMode.MERGE_KEEP, Department.class); final Department department = departments.get(0); assertFalse("Department teams property from is initialized", Hibernate.isInitialized(department.straightGetProperty(Department.TEAMS))); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { Department departmentInTx = hbc.findById(department.getId(), EMergeMode.MERGE_KEEP, Department.class); assertFalse("TX department teams property is initialized", Hibernate.isInitialized(departmentInTx.straightGetProperty(Department.TEAMS))); departmentInTx.getTeams(); Map<String, Object> inTxDirtyProperties = hbc.getDirtyProperties(departmentInTx); assertFalse("teams property is dirty whereas is shouldn't", inTxDirtyProperties.containsKey(Department.TEAMS)); } }); department.getTeams(); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { Department departmentInTx = hbc.cloneInUnitOfWork(department); departmentInTx.getTeams(); Map<String, Object> inTxDirtyProperties = hbc.getDirtyProperties(departmentInTx); assertFalse("teams property is dirty whereas is shouldn't", inTxDirtyProperties.containsKey(Department.TEAMS)); } }); Map<String, Object> dirtyProperties = hbc.getDirtyProperties(department); assertFalse("teams property is dirty whereas is shouldn't", dirtyProperties.containsKey(Department.TEAMS)); final Department anotherDepartment = departments.get(1); assertFalse("Other department teams property is initialized", Hibernate.isInitialized(anotherDepartment.straightGetProperty(Department.TEAMS))); final Department anotherDepartmentClone = hbc.getTransactionTemplate() .execute(new TransactionCallback<Department>() { @Override public Department doInTransaction(TransactionStatus status) { Department anotherDeptClone = hbc.cloneInUnitOfWork(anotherDepartment); assertFalse("Other department clone teams property is initialized", Hibernate.isInitialized(anotherDeptClone.straightGetProperty(Department.TEAMS))); anotherDeptClone.getTeams(); return anotherDeptClone; } }); hbc.merge(anotherDepartmentClone, EMergeMode.MERGE_EAGER); Map<String, Object> anotherDirtyProperties = hbc.getDirtyProperties(anotherDepartment); assertFalse("Other department teams property is dirty whereas is shouldn't", anotherDirtyProperties.containsKey(Department.TEAMS)); hbc.getTransactionTemplate().execute(new TransactionCallback<Department>() { @Override public Department doInTransaction(TransactionStatus status) { Department yetAnotherDeptClone = hbc.cloneInUnitOfWork(anotherDepartmentClone); return yetAnotherDeptClone; } }); } /** * Tests that an in-memory TX isolates its UOW from any inner transaction (see bug #63). */ @Test public void testInMemoryTxIsolation() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); hbc.beginUnitOfWork(); EnhancedDetachedCriteria compCrit = EnhancedDetachedCriteria.forClass(Company.class); hbc.findFirstByCriteria(compCrit, EMergeMode.MERGE_KEEP, Company.class); // Try to clone the company in the UOW. assertTrue("The in-memory TX has been terminated by the previous find", hbc.isUnitOfWorkActive()); hbc.rollbackUnitOfWork(); final Company company = hbc.findFirstByCriteria(compCrit, EMergeMode.MERGE_KEEP, Company.class); String originalCompanyName = company.getName(); hbc.beginUnitOfWork(); final Company companyCloneRollBack = hbc.cloneInUnitOfWork(company); final Department newDepartmentRollBack = hbc.getEntityFactory().createEntityInstance(Department.class); newDepartmentRollBack.setName("TestDepartment"); newDepartmentRollBack.setOuId("TE-001"); companyCloneRollBack.addToDepartments(newDepartmentRollBack); String companyNameUpdated = "Updated by 63"; companyCloneRollBack.setName(companyNameUpdated); hbc.rollbackUnitOfWork(); Company reloadedCompany = hbc.findById(company.getId(), EMergeMode.MERGE_CLEAN_EAGER, Company.class); assertEquals("Company has incorrectly been saved", originalCompanyName, reloadedCompany.getName()); boolean containsNewDepartment = false; for (Department department : reloadedCompany.getDepartments()) { if (department.getId().equals(newDepartmentRollBack.getId())) { containsNewDepartment = true; } } assertFalse("Company contains the department created in the in-memory TX although rolled-back", containsNewDepartment); hbc.beginUnitOfWork(); final Company companyClone = hbc.cloneInUnitOfWork(company); final Department newDepartment = hbc.getEntityFactory().createEntityInstance(Department.class); newDepartment.setName("TestDepartment"); newDepartment.setOuId("TE-001"); companyClone.addToDepartments(newDepartment); companyClone.setName(companyNameUpdated); hbc.merge(companyClone, EMergeMode.MERGE_EAGER); hbc.commitUnitOfWork(); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { Company saveTxCompanyClone = hbc.cloneInUnitOfWork(company); hbc.registerForUpdate(saveTxCompanyClone); } }); reloadedCompany = hbc.findById(company.getId(), EMergeMode.MERGE_CLEAN_EAGER, Company.class); assertEquals("Company has not correctly be saved by inner transaction", companyNameUpdated, reloadedCompany.getName()); containsNewDepartment = false; for (Department department : reloadedCompany.getDepartments()) { if (department.getId().equals(newDepartment.getId())) { containsNewDepartment = true; } } assertTrue("Company does not contain the department created in the in-memory TX", containsNewDepartment); hbc.beginUnitOfWork(); Company companyClone2 = hbc.cloneInUnitOfWork(company); for (Department department : companyClone2.getDepartments()) { if (department.getId().equals(newDepartment.getId())) { try { hbc.cleanRelationshipsOnDeletion(department, false); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } break; } } hbc.merge(companyClone2, EMergeMode.MERGE_EAGER); hbc.commitUnitOfWork(); containsNewDepartment = false; for (Department department : reloadedCompany.getDepartments()) { if (department.getId().equals(newDepartment.getId())) { containsNewDepartment = true; } } assertFalse("Company still contains the department removed in the in-memory TX", containsNewDepartment); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { hbc.performPendingOperations(); } }); reloadedCompany = hbc.findById(company.getId(), EMergeMode.MERGE_CLEAN_EAGER, Company.class); containsNewDepartment = false; for (Department department : reloadedCompany.getDepartments()) { if (department.getId().equals(newDepartment.getId())) { containsNewDepartment = true; } } assertFalse("Company still contains the department removed in the in-memory TX", containsNewDepartment); } /** * Tests that an in-memory TX does not dirty an entity when merging eagerly a persistent collection (see bug #66). */ @Test public void testInMemoryTxEagerMerge() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); hbc.beginUnitOfWork(); EnhancedDetachedCriteria compCrit = EnhancedDetachedCriteria.forClass(Company.class); Company company = hbc.findFirstByCriteria(compCrit, EMergeMode.MERGE_KEEP, Company.class); String companyNameUpdated = "test"; company.setName(companyNameUpdated); company = hbc.merge(company, EMergeMode.MERGE_EAGER); hbc.commitUnitOfWork(); hbc.registerForUpdate(company); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { hbc.performPendingOperations(); } }); Company reloadedCompany = hbc.findById(company.getId(), EMergeMode.MERGE_CLEAN_EAGER, Company.class); assertEquals("Company has not correctly be saved by inner transaction", companyNameUpdated, reloadedCompany.getName()); } /** * Tests that an in-memory TX preserves entities unicity in the UOW even if * multiple requests are spanned (see bug #1043). */ @Test public void testInMemoryTxEntityUnicity() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); EnhancedDetachedCriteria compCrit = EnhancedDetachedCriteria.forClass(Company.class); Company company = hbc.findFirstByCriteria(compCrit, EMergeMode.MERGE_KEEP, Company.class); // Simulates a new request hbc.cleanupRequestResources(); hbc.beginUnitOfWork(); Company companyClone = hbc.cloneInUnitOfWork(company); assertNotSame("The company clone is the same instance as the original", companyClone, company); hbc.cleanupRequestResources(); Company c1 = (Company) hbc.getHibernateSession().byId(Company.class).load(company.getId()); assertSame("Both company instances should have been the same in UOW", c1, companyClone); hbc.rollbackUnitOfWork(); hbc.beginUnitOfWork(); Company c2 = (Company) hbc.getHibernateSession().byId(Company.class).load(company.getId()); hbc.cleanupRequestResources(); Company c3 = (Company) hbc.getHibernateSession().byId(Company.class).load(company.getId()); hbc.cleanupRequestResources(); companyClone = hbc.cloneInUnitOfWork(company); assertSame("Both company instances should have been the same in UOW", c2, c3); assertSame("Both company instances should have been the same in UOW", c2, companyClone); assertNotSame("The company clone is the same instance as the original", companyClone, company); assertNotSame("The company clone is the same instance as the first UOW clone", c1, c2); hbc.rollbackUnitOfWork(); } /** * Test in memory tx entity update. */ @Test public void testInMemoryTxEntityUpdate() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); EnhancedDetachedCriteria compCrit = EnhancedDetachedCriteria.forClass(Company.class); Company company = hbc.findFirstByCriteria(compCrit, EMergeMode.MERGE_KEEP, Company.class); // Simulates a new request hbc.cleanupRequestResources(); hbc.beginUnitOfWork(); final Company companyClone = hbc.cloneInUnitOfWork(company); String modifiedInUOW = "modifiedInUOW"; companyClone.setName(modifiedInUOW); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { Company companyCloneClone = hbc.cloneInUnitOfWork(companyClone); assertNotSame("Cloning in a nested TX results in another clone", companyClone, companyCloneClone); } }); assertTrue("The outer in-memory TX has been closed", hbc.isUnitOfWorkActive()); hbc.commitUnitOfWork(); assertFalse("The outer in-memory TX is still open", hbc.isUnitOfWorkActive()); company = hbc.findById(company.getId(), EMergeMode.MERGE_CLEAN_EAGER, Company.class); assertEquals("Company has not been correctly merged", modifiedInUOW, company.getName()); company = hbc.findById(company.getId(), EMergeMode.MERGE_CLEAN_EAGER, Company.class); assertEquals("Company has not been correctly persisted", modifiedInUOW, company.getName()); } /** * Tests that an exception is thrown if we detect a flush of an entity that was not previously cloned. */ @Test(expected = BackendException.class) public void testExceptionIfNotCloned() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); final City newCity = hbc.getEntityFactory().createEntityInstance(City.class); newCity.setName("New"); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { hbc.registerForUpdate(newCity); } }); } /** * Tests fix for bug #1127. */ @Test public void testInMemoryTxDirtyCollectionProperty() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); EnhancedDetachedCriteria compCrit = EnhancedDetachedCriteria.forClass(Company.class); Company company = hbc.findFirstByCriteria(compCrit, EMergeMode.MERGE_KEEP, Company.class); hbc.beginUnitOfWork(); Company companyClone = hbc.cloneInUnitOfWork(company); companyClone.addToDepartments(hbc.getEntityFactory().createEntityInstance(Department.class)); hbc.cleanupRequestResources(); compCrit.getExecutableCriteria(hbc.getHibernateSession()).list(); } /** * Tests fix for bug #1130. */ @Test public void testDetachedEntitiesManagement() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); final Company company = hbc.getEntityFactory().createEntityInstance(Company.class); company.setName("TestCompany"); final Department department = hbc.getEntityFactory().createEntityInstance(Department.class); department.setName("TestDepartment"); department.setOuId("TE-001"); company.addToDepartments(department); company.removeFromDepartments(department); Set<?> detachedEntities = (Set<?>) company.straightGetProperty("detachedEntities"); assertTrue("DetachedEntities is empty and should not be.", detachedEntities != null && !detachedEntities.isEmpty()); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { Company companyClone = hbc.cloneInUnitOfWork(company); Department departmentClone = hbc.cloneInUnitOfWork(department); Set<?> detachedEntities = (Set<?>) companyClone.straightGetProperty("detachedEntities"); assertTrue("DetachedEntities is empty and should not be.", detachedEntities != null && !detachedEntities.isEmpty()); assertSame("DetachedEntities have not been cloned correctly.", departmentClone, detachedEntities.iterator().next()); hbc.registerForUpdate(companyClone); } }); detachedEntities = (Set<?>) company.straightGetProperty("detachedEntities"); assertNull("DetachedEntities should have been reset.", detachedEntities); } /** * Tests fix for bug #1153. */ @Test public void testAfterTxInitializationKeepsIsolation() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); EnhancedDetachedCriteria compCrit = EnhancedDetachedCriteria.forClass(Company.class); Company company = hbc.findFirstByCriteria(compCrit, EMergeMode.MERGE_KEEP, Company.class); Department uowDepartment = hbc.getTransactionTemplate().execute(new TransactionCallback<Department>() { @Override public Department doInTransaction(TransactionStatus status) { EnhancedDetachedCriteria deptCrit = EnhancedDetachedCriteria.forClass(Department.class); return hbc.findFirstByCriteria(deptCrit, null, Department.class); } }); assertTrue("UOW dept company is initialized", !Hibernate.isInitialized(uowDepartment.straightGetProperty(Department.COMPANY))); Company uowCompany = uowDepartment.getCompany(); assertFalse("Session company has been assigned to UOW department", HibernateHelper.objectEquals(company, uowCompany)); } @Test public void testTranslationComponentUpdate() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); EnhancedDetachedCriteria compCrit = EnhancedDetachedCriteria.forClass(Company.class); final Company company = hbc.findFirstByCriteria(compCrit, EMergeMode.MERGE_KEEP, Company.class); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { Company companyClone = hbc.cloneInUnitOfWork(company); companyClone.setName("Translation"); } }); for (Company.Translation translation : company.getPropertyTranslations()) { if ("Translation".equals(translation.getTranslatedValue())) { translation.setTranslatedValue("UpdatedTranslation"); } } IPropertyTranslation translationBefore = null; for (Company.Translation translation : company.getPropertyTranslations()) { if ("UpdatedTranslation".equals(translation.getTranslatedValue())) { translationBefore = translation; } } hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { hbc.cloneInUnitOfWork(company); } }); IPropertyTranslation translationAfter = null; for (Company.Translation translation : company.getPropertyTranslations()) { if ("UpdatedTranslation".equals(translation.getTranslatedValue())) { translationAfter = translation; } } assertSame("translationBefore/After should be the same reference", translationBefore, translationAfter); Company reloadedCompany = hbc.findById(company.getId(), EMergeMode.MERGE_CLEAN_EAGER, Company.class); assertEquals("Translation component not correctly updated", "UpdatedTranslation", reloadedCompany.getName()); IPropertyTranslation translationReloaded = null; for (Company.Translation translation : reloadedCompany.getPropertyTranslations()) { if ("UpdatedTranslation".equals(translation.getTranslatedValue())) { translationReloaded = translation; } } assertNotNull("Updated translation not found", translationReloaded); assertSame("translationBefore/After/Reloaded should be the same reference", translationAfter, translationReloaded); } /** * Test multiple uow cloning. */ @Test(timeout = 300) public void testMultipleUOWCloning() { final HibernateBackendController hbc = (HibernateBackendController) getBackendController(); EnhancedDetachedCriteria empCrit = EnhancedDetachedCriteria.forClass(Employee.class); final List<Employee> employees = hbc.findByCriteria(empCrit, EMergeMode.MERGE_KEEP, Employee.class); hbc.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { List<Employee> employeeClones = hbc.cloneInUnitOfWork(employees); List<Employee> newEmployeeClones = null; for (int i = 0; i < 100; i++) { newEmployeeClones = hbc.cloneInUnitOfWork(employees); } for (int i = 0; i < employeeClones.size(); i++) { assertSame("Multiple cloning did not clone to the same instance. Cloning is not idempotent.", employeeClones.get(i), newEmployeeClones.get(i)); } for (int i = 0; i < 100; i++) { newEmployeeClones = hbc.cloneInUnitOfWork(employeeClones); } for (int i = 0; i < employeeClones.size(); i++) { assertSame("Multiple cloning did not clone to the same instance. Cloning is not idempotent.", employeeClones.get(i), newEmployeeClones.get(i)); } } }); } }