/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program 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 this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.cascade;
import org.hibernate.Session;
import org.hibernate.TransientObjectException;
import org.hibernate.proxy.HibernateProxy;
import org.junit.Test;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author <a href="mailto:ovidiu@feodorov.com">Ovidiu Feodorov</a>
* @author Gail Badner
*/
public class MultiPathCascadeTest extends BaseCoreFunctionalTestCase {
@Override
public String[] getMappings() {
return new String[] {
"cascade/MultiPathCascade.hbm.xml"
};
}
@Override
protected void cleanupTest() {
Session s = openSession();
s.beginTransaction();
s.createQuery( "delete from A" );
s.createQuery( "delete from G" );
s.createQuery( "delete from H" );
}
@Test
public void testMultiPathMergeModifiedDetached() throws Exception {
// persist a simple A in the database
Session s = openSession();
s.beginTransaction();
A a = new A();
a.setData( "Anna" );
s.save( a );
s.getTransaction().commit();
s.close();
// modify detached entity
modifyEntity( a );
s = openSession();
s.beginTransaction();
a = (A) s.merge( a );
s.getTransaction().commit();
s.close();
verifyModifications( a.getId() );
}
@Test
public void testMultiPathMergeModifiedDetachedIntoProxy() throws Exception {
// persist a simple A in the database
Session s = openSession();
s.beginTransaction();
A a = new A();
a.setData( "Anna" );
s.save( a );
s.getTransaction().commit();
s.close();
// modify detached entity
modifyEntity( a );
s = openSession();
s.beginTransaction();
A aLoaded = (A) s.load( A.class, new Long( a.getId() ) );
assertTrue( aLoaded instanceof HibernateProxy );
assertSame( aLoaded, s.merge( a ) );
s.getTransaction().commit();
s.close();
verifyModifications( a.getId() );
}
@Test
public void testMultiPathUpdateModifiedDetached() throws Exception {
// persist a simple A in the database
Session s = openSession();
s.beginTransaction();
A a = new A();
a.setData( "Anna" );
s.save( a );
s.getTransaction().commit();
s.close();
// modify detached entity
modifyEntity( a );
s = openSession();
s.beginTransaction();
s.update( a );
s.getTransaction().commit();
s.close();
verifyModifications( a.getId() );
}
@Test
public void testMultiPathGetAndModify() throws Exception {
// persist a simple A in the database
Session s = openSession();
s.beginTransaction();
A a = new A();
a.setData( "Anna" );
s.save( a );
s.getTransaction().commit();
s.close();
s = openSession();
s.beginTransaction();
// retrieve the previously saved instance from the database, and update it
a = (A) s.get( A.class, new Long( a.getId() ) );
modifyEntity( a );
s.getTransaction().commit();
s.close();
verifyModifications( a.getId() );
}
@Test
public void testMultiPathMergeNonCascadedTransientEntityInCollection() throws Exception {
// persist a simple A in the database
Session s = openSession();
s.beginTransaction();
A a = new A();
a.setData( "Anna" );
s.save( a );
s.getTransaction().commit();
s.close();
// modify detached entity
modifyEntity( a );
s = openSession();
s.beginTransaction();
a = (A) s.merge( a );
s.getTransaction().commit();
s.close();
verifyModifications( a.getId() );
// add a new (transient) G to collection in h
// there is no cascade from H to the collection, so this should fail when merged
assertEquals( 1, a.getHs().size() );
H h = (H) a.getHs().iterator().next();
G gNew = new G();
gNew.setData( "Gail" );
gNew.getHs().add( h );
h.getGs().add( gNew );
s = openSession();
s.beginTransaction();
try {
s.merge( a );
s.merge( h );
s.getTransaction().commit();
fail( "should have thrown TransientObjectException" );
}
catch (TransientObjectException ex) {
// expected
}
finally {
s.getTransaction().rollback();
}
s.close();
}
@Test
public void testMultiPathMergeNonCascadedTransientEntityInOneToOne() throws Exception {
// persist a simple A in the database
Session s = openSession();
s.beginTransaction();
A a = new A();
a.setData( "Anna" );
s.save( a );
s.getTransaction().commit();
s.close();
// modify detached entity
modifyEntity( a );
s = openSession();
s.beginTransaction();
a = (A) s.merge( a );
s.getTransaction().commit();
s.close();
verifyModifications( a.getId() );
// change the one-to-one association from g to be a new (transient) A
// there is no cascade from G to A, so this should fail when merged
G g = a.getG();
a.setG( null );
A aNew = new A();
aNew.setData( "Alice" );
g.setA( aNew );
aNew.setG( g );
s = openSession();
s.beginTransaction();
try {
s.merge( a );
s.merge( g );
s.getTransaction().commit();
fail( "should have thrown TransientObjectException" );
}
catch (TransientObjectException ex) {
// expected
}
finally {
s.getTransaction().rollback();
}
s.close();
}
@Test
public void testMultiPathMergeNonCascadedTransientEntityInManyToOne() throws Exception {
// persist a simple A in the database
Session s = openSession();
s.beginTransaction();
A a = new A();
a.setData( "Anna" );
s.save( a );
s.getTransaction().commit();
s.close();
// modify detached entity
modifyEntity( a );
s = openSession();
s.beginTransaction();
a = (A) s.merge( a );
s.getTransaction().commit();
s.close();
verifyModifications( a.getId() );
// change the many-to-one association from h to be a new (transient) A
// there is no cascade from H to A, so this should fail when merged
assertEquals( 1, a.getHs().size() );
H h = (H) a.getHs().iterator().next();
a.getHs().remove( h );
A aNew = new A();
aNew.setData( "Alice" );
aNew.addH( h );
s = openSession();
s.beginTransaction();
try {
s.merge( a );
s.merge( h );
s.getTransaction().commit();
fail( "should have thrown TransientObjectException" );
}
catch (TransientObjectException ex) {
// expected
}
finally {
s.getTransaction().rollback();
}
s.close();
}
private void modifyEntity(A a) {
// create a *circular* graph in detached entity
a.setData( "Anthony" );
G g = new G();
g.setData( "Giovanni" );
H h = new H();
h.setData( "Hellen" );
a.setG( g );
g.setA( a );
a.getHs().add( h );
h.setA( a );
g.getHs().add( h );
h.getGs().add( g );
}
private void verifyModifications(long aId) {
Session s = openSession();
s.beginTransaction();
// retrieve the A object and check it
A a = (A) s.get( A.class, new Long( aId ) );
assertEquals( aId, a.getId() );
assertEquals( "Anthony", a.getData() );
assertNotNull( a.getG() );
assertNotNull( a.getHs() );
assertEquals( 1, a.getHs().size() );
G gFromA = a.getG();
H hFromA = (H) a.getHs().iterator().next();
// check the G object
assertEquals( "Giovanni", gFromA.getData() );
assertSame( a, gFromA.getA() );
assertNotNull( gFromA.getHs() );
assertEquals( a.getHs(), gFromA.getHs() );
assertSame( hFromA, gFromA.getHs().iterator().next() );
// check the H object
assertEquals( "Hellen", hFromA.getData() );
assertSame( a, hFromA.getA() );
assertNotNull( hFromA.getGs() );
assertEquals( 1, hFromA.getGs().size() );
assertSame( gFromA, hFromA.getGs().iterator().next() );
s.getTransaction().commit();
s.close();
}
}