HHH-17049 Add more test for issue

(cherry picked from commit de3008e712)
This commit is contained in:
Andrea Boriero 2023-09-06 17:08:24 +02:00 committed by Christian Beikov
parent 1fef8c3587
commit ad55143eb5
14 changed files with 2762 additions and 1 deletions

View File

@ -0,0 +1,702 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.bytecode.enhancement.cascade.circle;
import java.util.Iterator;
import org.hibernate.JDBCException;
import org.hibernate.PropertyValueException;
import org.hibernate.Session;
import org.hibernate.TransientPropertyValueException;
import org.hibernate.orm.test.cascade.circle.Node;
import org.hibernate.orm.test.cascade.circle.Route;
import org.hibernate.orm.test.cascade.circle.Tour;
import org.hibernate.orm.test.cascade.circle.Transport;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.After;
import org.junit.Test;
import jakarta.persistence.PersistenceException;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
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 Andrea Boriero
*/
public abstract class AbstractMultiPathCircleCascadeTest extends BaseCoreFunctionalTestCase {
private interface EntityOperation {
boolean isLegacy();
Object doEntityOperation(Object entity, Session s);
}
private static EntityOperation MERGE_OPERATION =
new EntityOperation() {
@Override
public boolean isLegacy() {
return false;
}
@Override
public Object doEntityOperation(Object entity, Session s) {
return s.merge( entity );
}
};
private static EntityOperation SAVE_OPERATION =
new EntityOperation() {
@Override
public boolean isLegacy() {
return true;
}
@Override
public Object doEntityOperation(Object entity, Session s) {
s.save( entity );
return entity;
}
};
private static EntityOperation SAVE_UPDATE_OPERATION =
new EntityOperation() {
@Override
public boolean isLegacy() {
return true;
}
@Override
public Object doEntityOperation(Object entity, Session s) {
s.saveOrUpdate( entity );
return entity;
}
};
@Test
public void testMergeEntityWithNonNullableTransientEntity() {
testEntityWithNonNullableTransientEntity( MERGE_OPERATION );
}
@Test
public void testSaveEntityWithNonNullableTransientEntity() {
testEntityWithNonNullableTransientEntity( SAVE_OPERATION );
}
@Test
public void testSaveUpdateEntityWithNonNullableTransientEntity() {
testEntityWithNonNullableTransientEntity( SAVE_UPDATE_OPERATION );
}
private void testEntityWithNonNullableTransientEntity( EntityOperation operation) {
Route route = getUpdatedDetachedEntity();
Node node = (Node) route.getNodes().iterator().next();
route.getNodes().remove( node );
Route routeNew = new Route();
routeNew.setName( "new route" );
routeNew.getNodes().add( node );
node.setRoute( routeNew );
inSession(
session -> {
session.beginTransaction();
try {
operation.doEntityOperation( node, session );
session.getTransaction().commit();
fail( "should have thrown an exception" );
}
catch (Exception ex) {
checkExceptionFromNullValueForNonNullable(
ex,
session.getFactory().getSessionFactoryOptions().isCheckNullability(),
false,
operation.isLegacy()
);
}
finally {
session.getTransaction().rollback();
}
}
);
}
@Test
public void testMergeEntityWithNonNullableEntityNull() {
testEntityWithNonNullableEntityNull( MERGE_OPERATION );
}
@Test
public void testSaveEntityWithNonNullableEntityNull() {
testEntityWithNonNullableEntityNull( SAVE_OPERATION );
}
@Test
public void testSaveUpdateEntityWithNonNullableEntityNull() {
testEntityWithNonNullableEntityNull( SAVE_UPDATE_OPERATION );
}
private void testEntityWithNonNullableEntityNull( EntityOperation operation) {
Route route = getUpdatedDetachedEntity();
Node node = (Node) route.getNodes().iterator().next();
route.getNodes().remove( node );
node.setRoute( null );
inSession(
session -> {
session.beginTransaction();
try {
operation.doEntityOperation( node, session );
session.getTransaction().commit();
fail( "should have thrown an exception" );
}
catch (Exception ex) {
checkExceptionFromNullValueForNonNullable(
ex,
session.getFactory().getSessionFactoryOptions().isCheckNullability(),
true,
operation.isLegacy()
);
}
finally {
session.getTransaction().rollback();
}
}
);
}
@Test
public void testMergeEntityWithNonNullablePropSetToNull() {
testEntityWithNonNullablePropSetToNull( MERGE_OPERATION );
}
@Test
public void testSaveEntityWithNonNullablePropSetToNull() {
testEntityWithNonNullablePropSetToNull( SAVE_OPERATION );
}
@Test
public void testSaveUpdateEntityWithNonNullablePropSetToNull() {
testEntityWithNonNullablePropSetToNull( SAVE_UPDATE_OPERATION );
}
private void testEntityWithNonNullablePropSetToNull( EntityOperation operation) {
Route route = getUpdatedDetachedEntity();
Node node = (Node) route.getNodes().iterator().next();
node.setName( null );
inSession(
session -> {
session.beginTransaction();
try {
operation.doEntityOperation( route, session );
session.getTransaction().commit();
fail( "should have thrown an exception" );
}
catch (Exception ex) {
checkExceptionFromNullValueForNonNullable(
ex,
session.getFactory().getSessionFactoryOptions().isCheckNullability(),
true,
operation.isLegacy()
);
}
finally {
session.getTransaction().rollback();
}
}
);
}
@Test
public void testMergeRoute() {
testRoute( MERGE_OPERATION );
}
// skip SAVE_OPERATION since Route is not transient
@Test
public void testSaveUpdateRoute() {
testRoute( SAVE_UPDATE_OPERATION );
}
private void testRoute( EntityOperation operation) {
Route r = getUpdatedDetachedEntity();
clearCounts();
inTransaction(
session ->
operation.doEntityOperation( r, session )
);
assertInsertCount( 4 );
assertUpdateCount( 1 );
inTransaction(
session -> {
Route route = session.get( Route.class, r.getRouteID() );
checkResults( route, true );
}
);
}
@Test
public void testMergePickupNode() {
testPickupNode( MERGE_OPERATION );
}
@Test
public void testSavePickupNode() {
testPickupNode( SAVE_OPERATION );
}
@Test
public void testSaveUpdatePickupNode() {
testPickupNode( SAVE_UPDATE_OPERATION );
}
private void testPickupNode( EntityOperation operation) {
Route r = getUpdatedDetachedEntity();
clearCounts();
inTransaction(
session -> {
Iterator it = r.getNodes().iterator();
Node node = (Node) it.next();
Node pickupNode;
if ( node.getName().equals( "pickupNodeB" ) ) {
pickupNode = node;
}
else {
node = (Node) it.next();
assertEquals( "pickupNodeB", node.getName() );
pickupNode = node;
}
operation.doEntityOperation( pickupNode, session );
}
);
assertInsertCount( 4 );
assertUpdateCount( 0 );
inTransaction(
session -> {
Route route = session.get( Route.class, r.getRouteID() );
checkResults( route, false );
}
);
}
@Test
public void testMergeDeliveryNode() {
testDeliveryNode( MERGE_OPERATION );
}
@Test
public void testSaveDeliveryNode() {
testDeliveryNode( SAVE_OPERATION );
}
@Test
public void testSaveUpdateDeliveryNode() {
testDeliveryNode( SAVE_UPDATE_OPERATION );
}
private void testDeliveryNode( EntityOperation operation) {
Route r = getUpdatedDetachedEntity();
clearCounts();
inTransaction(
session -> {
Iterator it = r.getNodes().iterator();
Node node = (Node) it.next();
Node deliveryNode;
if ( node.getName().equals( "deliveryNodeB" ) ) {
deliveryNode = node;
}
else {
node = (Node) it.next();
assertEquals( "deliveryNodeB", node.getName() );
deliveryNode = node;
}
operation.doEntityOperation( deliveryNode, session );
}
);
assertInsertCount( 4 );
assertUpdateCount( 0 );
inTransaction(
session -> {
Route route = session.get( Route.class, r.getRouteID() );
checkResults( route, false );
}
);
}
@Test
public void testMergeTour() {
testTour( MERGE_OPERATION );
}
@Test
public void testSaveTour() {
testTour( SAVE_OPERATION );
}
@Test
public void testSaveUpdateTour() {
testTour( SAVE_UPDATE_OPERATION );
}
private void testTour( EntityOperation operation) {
Route r = getUpdatedDetachedEntity();
clearCounts();
inTransaction(
session ->
operation.doEntityOperation( ( (Node) r.getNodes().toArray()[0] ).getTour(), session )
);
assertInsertCount( 4 );
assertUpdateCount( 0 );
inTransaction(
session -> {
Route route = session.get( Route.class, r.getRouteID() );
checkResults( route, false );
}
);
}
@Test
public void testMergeTransport() {
testTransport( MERGE_OPERATION );
}
@Test
public void testSaveTransport() {
testTransport( SAVE_OPERATION );
}
@Test
public void testSaveUpdateTransport() {
testTransport( SAVE_UPDATE_OPERATION );
}
private void testTransport( EntityOperation operation) {
Route r = getUpdatedDetachedEntity();
clearCounts();
inTransaction(
session -> {
Transport transport;
Node node = ( (Node) r.getNodes().toArray()[0] );
if ( node.getPickupTransports().size() == 1 ) {
transport = (Transport) node.getPickupTransports().toArray()[0];
}
else {
transport = (Transport) node.getDeliveryTransports().toArray()[0];
}
operation.doEntityOperation( transport, session );
}
);
assertInsertCount( 4 );
assertUpdateCount( 0 );
inTransaction(
session -> {
Route route = session.get( Route.class, r.getRouteID() );
checkResults( route, false );
}
);
}
private Node getSimpleUpdatedDetachedEntity() {
Node deliveryNode = new Node();
deliveryNode.setName( "deliveryNodeB" );
return deliveryNode;
}
private Route getUpdatedDetachedEntity() {
Route route = new Route();
inTransaction(
session -> {
route.setName( "routeA" );
session.save( route );
}
);
route.setName( "new routeA" );
route.setTransientField( "sfnaouisrbn" );
Tour tour = new Tour();
tour.setName( "tourB" );
Transport transport = new Transport();
transport.setName( "transportB" );
Node pickupNode = new Node();
pickupNode.setName( "pickupNodeB" );
Node deliveryNode = new Node();
deliveryNode.setName( "deliveryNodeB" );
pickupNode.setRoute( route );
pickupNode.setTour( tour );
pickupNode.getPickupTransports().add( transport );
pickupNode.setTransientField( "pickup node aaaaaaaaaaa" );
deliveryNode.setRoute( route );
deliveryNode.setTour( tour );
deliveryNode.getDeliveryTransports().add( transport );
deliveryNode.setTransientField( "delivery node aaaaaaaaa" );
tour.getNodes().add( pickupNode );
tour.getNodes().add( deliveryNode );
route.getNodes().add( pickupNode );
route.getNodes().add( deliveryNode );
transport.setPickupNode( pickupNode );
transport.setDeliveryNode( deliveryNode );
transport.setTransientField( "aaaaaaaaaaaaaa" );
return route;
}
@After
public void cleanup() {
inTransaction(
session -> {
session.createQuery( "delete from Transport" );
session.createQuery( "delete from Tour" );
session.createQuery( "delete from Node" );
session.createQuery( "delete from Route" );
}
);
}
private void checkResults(Route route, boolean isRouteUpdated) {
// since no cascaded to route, this method needs to
// know whether route is expected to be updated
if ( isRouteUpdated ) {
assertEquals( "new routeA", route.getName() );
}
assertEquals( 2, route.getNodes().size() );
Node deliveryNode = null;
Node pickupNode = null;
for ( Iterator it = route.getNodes().iterator(); it.hasNext(); ) {
Node node = (Node) it.next();
if ( "deliveryNodeB".equals( node.getName() ) ) {
deliveryNode = node;
}
else if ( "pickupNodeB".equals( node.getName() ) ) {
pickupNode = node;
}
else {
fail( "unknown node" );
}
}
assertNotNull( deliveryNode );
assertSame( route, deliveryNode.getRoute() );
assertEquals( 1, deliveryNode.getDeliveryTransports().size() );
assertEquals( 0, deliveryNode.getPickupTransports().size() );
assertNotNull( deliveryNode.getTour() );
assertEquals( "node original value", deliveryNode.getTransientField() );
assertNotNull( pickupNode );
assertSame( route, pickupNode.getRoute() );
assertEquals( 0, pickupNode.getDeliveryTransports().size() );
assertEquals( 1, pickupNode.getPickupTransports().size() );
assertNotNull( pickupNode.getTour() );
assertEquals( "node original value", pickupNode.getTransientField() );
assertTrue( !deliveryNode.getNodeID().equals( pickupNode.getNodeID() ) );
assertSame( deliveryNode.getTour(), pickupNode.getTour() );
assertSame(
deliveryNode.getDeliveryTransports().iterator().next(),
pickupNode.getPickupTransports().iterator().next()
);
Tour tour = deliveryNode.getTour();
Transport transport = (Transport) deliveryNode.getDeliveryTransports().iterator().next();
assertEquals( "tourB", tour.getName() );
assertEquals( 2, tour.getNodes().size() );
assertTrue( tour.getNodes().contains( deliveryNode ) );
assertTrue( tour.getNodes().contains( pickupNode ) );
assertEquals( "transportB", transport.getName() );
assertSame( deliveryNode, transport.getDeliveryNode() );
assertSame( pickupNode, transport.getPickupNode() );
assertEquals( "transport original value", transport.getTransientField() );
}
@Test
public void testMergeData3Nodes() {
testData3Nodes( MERGE_OPERATION );
}
@Test
public void testSaveData3Nodes() {
testData3Nodes( SAVE_OPERATION );
}
@Test
public void testSaveUpdateData3Nodes() {
testData3Nodes( SAVE_UPDATE_OPERATION );
}
private void testData3Nodes( EntityOperation operation) {
Route r = new Route();
inTransaction(
session -> {
r.setName( "routeA" );
session.save( r );
}
);
clearCounts();
inTransaction(
session -> {
Route route = session.get( Route.class, r.getRouteID() );
route.setName( "new routA" );
route.setTransientField( "sfnaouisrbn" );
Tour tour = new Tour();
tour.setName( "tourB" );
Transport transport1 = new Transport();
transport1.setName( "TRANSPORT1" );
Transport transport2 = new Transport();
transport2.setName( "TRANSPORT2" );
Node node1 = new Node();
node1.setName( "NODE1" );
Node node2 = new Node();
node2.setName( "NODE2" );
Node node3 = new Node();
node3.setName( "NODE3" );
node1.setRoute( route );
node1.setTour( tour );
node1.getPickupTransports().add( transport1 );
node1.setTransientField( "node 1" );
node2.setRoute( route );
node2.setTour( tour );
node2.getDeliveryTransports().add( transport1 );
node2.getPickupTransports().add( transport2 );
node2.setTransientField( "node 2" );
node3.setRoute( route );
node3.setTour( tour );
node3.getDeliveryTransports().add( transport2 );
node3.setTransientField( "node 3" );
tour.getNodes().add( node1 );
tour.getNodes().add( node2 );
tour.getNodes().add( node3 );
route.getNodes().add( node1 );
route.getNodes().add( node2 );
route.getNodes().add( node3 );
transport1.setPickupNode( node1 );
transport1.setDeliveryNode( node2 );
transport1.setTransientField( "aaaaaaaaaaaaaa" );
transport2.setPickupNode( node2 );
transport2.setDeliveryNode( node3 );
transport2.setTransientField( "bbbbbbbbbbbbb" );
operation.doEntityOperation( route, session );
}
);
assertInsertCount( 6 );
assertUpdateCount( 1 );
}
protected void checkExceptionFromNullValueForNonNullable(
Exception ex, boolean checkNullability, boolean isNullValue, boolean isLegacy
) {
if ( isNullValue ) {
if ( checkNullability ) {
if ( isLegacy ) {
assertTyping( PropertyValueException.class, ex );
}
else {
assertTyping( PersistenceException.class, ex );
}
}
else {
assertTrue( ( ex instanceof JDBCException ) || ( ex.getCause() instanceof JDBCException ) );
}
}
else {
if ( isLegacy ) {
assertTyping( TransientPropertyValueException.class, ex );
}
else {
assertTyping( IllegalStateException.class, ex );
}
}
}
protected void clearCounts() {
sessionFactory().getStatistics().clear();
}
protected void assertInsertCount(int expected) {
int inserts = (int) sessionFactory().getStatistics().getEntityInsertCount();
assertEquals( "unexpected insert count", expected, inserts );
}
protected void assertUpdateCount(int expected) {
int updates = (int) sessionFactory().getStatistics().getEntityUpdateCount();
assertEquals( "unexpected update counts", expected, updates );
}
protected void assertDeleteCount(int expected) {
int deletes = (int) sessionFactory().getStatistics().getEntityDeleteCount();
assertEquals( "unexpected delete counts", expected, deletes );
}
}

View File

@ -0,0 +1,37 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.bytecode.enhancement.cascade.circle;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.orm.test.bytecode.enhancement.lazy.NoDirtyCheckingContext;
import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.inlinedirtychecking.DirtyCheckEnhancementContext;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
import org.junit.runner.RunWith;
/**
* @author Gail Badner
*/
@RunWith(BytecodeEnhancerRunner.class)
@CustomEnhancementContext({ NoDirtyCheckingContext.class, DirtyCheckEnhancementContext.class })
public class MultiPathCircleCascadeCheckNullFalseDelayedInsertTest extends AbstractMultiPathCircleCascadeTest {
@Override
protected String[] getOrmXmlFiles() {
return new String[] {
"org/hibernate/orm/test/cascade/circle/MultiPathCircleCascadeDelayedInsert.hbm.xml"
};
}
@Override
protected void configure(Configuration configuration) {
configuration.setProperty( Environment.GENERATE_STATISTICS, "true" );
configuration.setProperty( Environment.STATEMENT_BATCH_SIZE, "0" );
configuration.setProperty( Environment.CHECK_NULLABILITY, "false" );
}
}

View File

@ -0,0 +1,37 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.bytecode.enhancement.cascade.circle;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.orm.test.bytecode.enhancement.lazy.NoDirtyCheckingContext;
import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.inlinedirtychecking.DirtyCheckEnhancementContext;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
import org.junit.runner.RunWith;
/**
* @author Gail Badner
*/
@RunWith(BytecodeEnhancerRunner.class)
@CustomEnhancementContext({ NoDirtyCheckingContext.class, DirtyCheckEnhancementContext.class })
public class MultiPathCircleCascadeCheckNullTrueDelayedInsertTest extends AbstractMultiPathCircleCascadeTest {
@Override
protected String[] getOrmXmlFiles() {
return new String[] {
"org/hibernate/orm/test/cascade/circle/MultiPathCircleCascadeDelayedInsert.hbm.xml"
};
}
@Override
protected void configure(Configuration configuration) {
configuration.setProperty( Environment.GENERATE_STATISTICS, "true" );
configuration.setProperty( Environment.STATEMENT_BATCH_SIZE, "0" );
configuration.setProperty( Environment.CHECK_NULLABILITY, "true" );
}
}

View File

@ -0,0 +1,38 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.bytecode.enhancement.cascade.circle;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.orm.test.bytecode.enhancement.lazy.NoDirtyCheckingContext;
import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.inlinedirtychecking.DirtyCheckEnhancementContext;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
import org.junit.runner.RunWith;
/**
* @author Gail Badner
*/
@RunWith(BytecodeEnhancerRunner.class)
@CustomEnhancementContext({ NoDirtyCheckingContext.class, DirtyCheckEnhancementContext.class })
public class MultiPathCircleCascadeCheckNullibilityFalseTest extends AbstractMultiPathCircleCascadeTest {
@Override
protected String[] getOrmXmlFiles() {
return new String[] {
"org/hibernate/orm/test/cascade/circle/MultiPathCircleCascade.hbm.xml"
};
}
@Override
protected void configure(Configuration configuration) {
configuration.setProperty( Environment.GENERATE_STATISTICS, "true" );
configuration.setProperty( Environment.STATEMENT_BATCH_SIZE, "0" );
configuration.setProperty( Environment.CHECK_NULLABILITY, "false" );
}
}

View File

@ -0,0 +1,37 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.bytecode.enhancement.cascade.circle;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.orm.test.bytecode.enhancement.lazy.NoDirtyCheckingContext;
import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.inlinedirtychecking.DirtyCheckEnhancementContext;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
import org.junit.runner.RunWith;
/**
* @author Gail Badner
*/
@RunWith(BytecodeEnhancerRunner.class)
@CustomEnhancementContext({ NoDirtyCheckingContext.class, DirtyCheckEnhancementContext.class })
public class MultiPathCircleCascadeCheckNullibilityTrueTest extends AbstractMultiPathCircleCascadeTest {
@Override
protected String[] getOrmXmlFiles() {
return new String[] {
"org/hibernate/orm/test/cascade/circle/MultiPathCircleCascade.hbm.xml"
};
}
@Override
protected void configure(Configuration configuration) {
configuration.setProperty( Environment.GENERATE_STATISTICS, "true" );
configuration.setProperty( Environment.STATEMENT_BATCH_SIZE, "0" );
configuration.setProperty( Environment.CHECK_NULLABILITY, "true" );
}
}

View File

@ -0,0 +1,36 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.bytecode.enhancement.cascade.circle;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.orm.test.bytecode.enhancement.lazy.NoDirtyCheckingContext;
import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.inlinedirtychecking.DirtyCheckEnhancementContext;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
import org.junit.runner.RunWith;
/**
* @author Gail Badner
*/
@RunWith(BytecodeEnhancerRunner.class)
@CustomEnhancementContext({ NoDirtyCheckingContext.class, DirtyCheckEnhancementContext.class })
public class MultiPathCircleCascadeDelayedInsertTest extends AbstractMultiPathCircleCascadeTest {
@Override
protected String[] getOrmXmlFiles() {
return new String[] {
"org/hibernate/orm/test/cascade/circle/MultiPathCircleCascadeDelayedInsert.hbm.xml"
};
}
@Override
protected void configure(Configuration configuration) {
configuration.setProperty( Environment.GENERATE_STATISTICS, "true" );
configuration.setProperty( Environment.STATEMENT_BATCH_SIZE, "0" );
}
}

View File

@ -0,0 +1,55 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.bytecode.enhancement.cascade.circle;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.orm.test.bytecode.enhancement.lazy.NoDirtyCheckingContext;
import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.inlinedirtychecking.DirtyCheckEnhancementContext;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
import org.junit.runner.RunWith;
/**
* The test case uses the following model:
* <p>
* <- ->
* -- (N : 0,1) -- Tour
* | <- ->
* | -- (1 : N) -- (pickup) ----
* -> | | |
* Route -- (1 : N) -- Node Transport
* | <- -> |
* -- (1 : N) -- (delivery) --
* <p>
* Arrows indicate the direction of cascade-merge, cascade-save, and cascade-save-or-update
* <p>
* It reproduced the following issues:
* http://opensource.atlassian.com/projects/hibernate/browse/HHH-3046
* http://opensource.atlassian.com/projects/hibernate/browse/HHH-3810
* <p>
* This tests that cascades are done properly from each entity.
*
* @author Pavol Zibrita, Gail Badner
*/
@RunWith(BytecodeEnhancerRunner.class)
@CustomEnhancementContext({ NoDirtyCheckingContext.class, DirtyCheckEnhancementContext.class })
public class MultiPathCircleCascadeTest extends AbstractMultiPathCircleCascadeTest {
@Override
protected String[] getOrmXmlFiles() {
return new String[] {
"org/hibernate/orm/test/cascade/circle/MultiPathCircleCascade.hbm.xml"
};
}
@Override
protected void configure(Configuration configuration) {
configuration.setProperty( Environment.GENERATE_STATISTICS, "true" );
configuration.setProperty( Environment.STATEMENT_BATCH_SIZE, "0" );
}
}

View File

@ -0,0 +1,206 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.bytecode.enhancement.deletetransient;
import org.hibernate.orm.test.bytecode.enhancement.lazy.NoDirtyCheckingContext;
import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.inlinedirtychecking.DirtyCheckEnhancementContext;
import org.hibernate.orm.test.deletetransient.Address;
import org.hibernate.orm.test.deletetransient.Note;
import org.hibernate.orm.test.deletetransient.Person;
import org.hibernate.orm.test.deletetransient.Suite;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
/**
* @author Steve Ebersole
*/
@RunWith(BytecodeEnhancerRunner.class)
@CustomEnhancementContext({ NoDirtyCheckingContext.class, DirtyCheckEnhancementContext.class })
public class DeleteTransientEntityTest extends BaseCoreFunctionalTestCase {
@Override
protected String getBaseForMappings() {
return "org/hibernate/orm/test/";
}
@Override
public String[] getMappings() {
return new String[] { "deletetransient/Person.hbm.xml" };
}
@Override
protected boolean isCleanupTestDataRequired() {
return true;
}
@Test
public void testTransientEntityDeletionNoCascades() {
inTransaction(
session -> {
session.remove( new Address() );
}
);
}
@Test
public void testTransientEntityDeletionCascadingToTransientAssociation() {
inTransaction(
session -> {
Person p = new Person();
p.getAddresses().add( new Address() );
session.persist( p );
}
);
}
@Test
public void testTransientEntityDeleteCascadingToCircularity() {
inTransaction(
session -> {
Person p1 = new Person();
Person p2 = new Person();
p1.getFriends().add( p2 );
p2.getFriends().add( p1 );
session.persist( p1 );
}
);
}
@Test
public void testTransientEntityDeletionCascadingToDetachedAssociation() {
Address address = new Address();
inTransaction(
session -> {
address.setInfo( "123 Main St." );
session.persist( address );
}
);
inTransaction(
session -> {
Person p = new Person();
p.getAddresses().add( address );
session.delete( p );
}
);
inTransaction(
session -> {
Long count = (Long) session.createQuery( "select count(*) from Address" ).list().get( 0 );
assertEquals( "delete not cascaded properly across transient entity", 0, count.longValue() );
}
);
}
@Test
public void testTransientEntityDeletionCascadingToPersistentAssociation() {
Long id = fromTransaction(
session -> {
Address address = new Address();
address.setInfo( "123 Main St." );
session.persist( address );
return address.getId();
}
);
inTransaction(
session -> {
Address address = session.get( Address.class, id );
Person p = new Person();
p.getAddresses().add( address );
session.delete( p );
}
);
inTransaction(
session -> {
Long count = (Long) session.createQuery( "select count(*) from Address" ).list().get( 0 );
assertEquals( "delete not cascaded properly across transient entity", 0, count.longValue() );
}
);
}
@Test
@SuppressWarnings({ "unchecked" })
public void testCascadeAllFromClearedPersistentAssnToTransientEntity() {
final Person p = new Person();
Address address = new Address();
inTransaction(
session -> {
address.setInfo( "123 Main St." );
p.getAddresses().add( address );
session.save( p );
}
);
inTransaction(
session -> {
Suite suite = new Suite();
address.getSuites().add( suite );
p.getAddresses().clear();
session.saveOrUpdate( p );
}
);
inTransaction(
session -> {
Person person = session.get( p.getClass(), p.getId() );
assertEquals( "persistent collection not cleared", 0, person.getAddresses().size() );
Long count = (Long) session.createQuery( "select count(*) from Address" ).list().get( 0 );
assertEquals( 1, count.longValue() );
count = (Long) session.createQuery( "select count(*) from Suite" ).list().get( 0 );
assertEquals( 0, count.longValue() );
}
);
}
@Test
@SuppressWarnings({ "unchecked" })
public void testCascadeAllDeleteOrphanFromClearedPersistentAssnToTransientEntity() {
Address address = new Address();
address.setInfo( "123 Main St." );
Suite suite = new Suite();
address.getSuites().add( suite );
inTransaction(
session -> {
session.save( address );
}
);
inTransaction(
session -> {
Note note = new Note();
note.setDescription( "a description" );
suite.getNotes().add( note );
address.getSuites().clear();
session.saveOrUpdate( address );
}
);
inTransaction(
session -> {
Long count = (Long) session.createQuery( "select count(*) from Suite" ).list().get( 0 );
assertEquals(
"all-delete-orphan not cascaded properly to cleared persistent collection entities",
0,
count.longValue()
);
count = (Long) session.createQuery( "select count(*) from Note" ).list().get( 0 );
assertEquals( 0, count.longValue() );
}
);
}
}

View File

@ -0,0 +1,287 @@
package org.hibernate.orm.test.bytecode.enhancement.lazy;
import java.util.List;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.inlinedirtychecking.DirtyCheckEnhancementContext;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
import org.hibernate.testing.orm.junit.JiraKey;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
@JiraKey("HHH-17049")
@RunWith(BytecodeEnhancerRunner.class)
@CustomEnhancementContext({ NoDirtyCheckingContext.class, DirtyCheckEnhancementContext.class })
public class JpaConstructorInitializationAndDynamicUpdateTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] {
Person.class,
LoginAccount.class,
AccountPreferences.class
};
}
@Before
public void setUp() {
doInJPA( this::entityManagerFactory, em -> {
Person person = new Person( 1l, "Henry" );
LoginAccount loginAccount = new LoginAccount();
loginAccount.setOwner( person );
person.setLoginAccount( loginAccount );
em.persist( person );
}
);
doInJPA( this::entityManagerFactory, em -> {
List<LoginAccount> accounts = em.createQuery(
"select la from LoginAccount la",
LoginAccount.class
).getResultList();
assertThat( accounts.size() ).isEqualTo( 1 );
List<AccountPreferences> preferences = em.createQuery(
"select ap from AccountPreferences ap",
AccountPreferences.class
).getResultList();
assertThat( preferences.size() ).isEqualTo( 1 );
}
);
}
@After
public void tearDown() {
doInJPA( this::entityManagerFactory, em -> {
em.createQuery( "delete from Person" ).executeUpdate();
em.createQuery( "delete from LoginAccount" ).executeUpdate();
em.createQuery( "delete from AccountPreferences" ).executeUpdate();
}
);
}
@Test
public void findTest() {
doInJPA( this::entityManagerFactory, em -> {
em.clear();
Person person = em.find( Person.class, 1L );
person.setFirstName( "Liza" );
}
);
doInJPA( this::entityManagerFactory, em -> {
List<LoginAccount> accounts = em.createQuery(
"select la from LoginAccount la",
LoginAccount.class
).getResultList();
assertThat( accounts.size() ).isEqualTo( 1 );
List<AccountPreferences> preferences = em.createQuery(
"select ap from AccountPreferences ap",
AccountPreferences.class
).getResultList();
assertThat( preferences.size() ).isEqualTo( 1 );
}
);
}
@Test
public void getReferenceTest() {
doInJPA( this::entityManagerFactory, em -> {
em.clear();
Person person = em.getReference( Person.class, 1L );
person.setFirstName( "Liza" );
}
);
doInJPA( this::entityManagerFactory, em -> {
List<LoginAccount> accounts = em.createQuery(
"select la from LoginAccount la",
LoginAccount.class
).getResultList();
assertThat( accounts.size() ).isEqualTo( 1 );
List<AccountPreferences> preferences = em.createQuery(
"select ap from AccountPreferences ap",
AccountPreferences.class
).getResultList();
assertThat( preferences.size() ).isEqualTo( 1 );
}
);
}
@Test
public void findTest2() {
doInJPA( this::entityManagerFactory, em -> {
em.clear();
Person person = em.find( Person.class, 1L );
person.setFirstName( "Liza" );
LoginAccount loginAccount = person.getLoginAccount();
loginAccount.setName( "abc" );
}
);
doInJPA( this::entityManagerFactory, em -> {
Person person = em.find( Person.class, 1L );
assertThat( person.getFirstName() ).isEqualTo( "Liza" );
LoginAccount loginAccount = person.getLoginAccount();
assertThat( loginAccount ).isNotNull();
assertThat( loginAccount.getName() ).isEqualTo( "abc" );
List<LoginAccount> accounts = em.createQuery(
"select la from LoginAccount la",
LoginAccount.class
).getResultList();
assertThat( accounts.size() ).isEqualTo( 1 );
List<AccountPreferences> preferences = em.createQuery(
"select ap from AccountPreferences ap",
AccountPreferences.class
).getResultList();
assertThat( preferences.size() ).isEqualTo( 1 );
}
);
}
@Test
public void getReferenceTest2() {
doInJPA( this::entityManagerFactory, em -> {
em.clear();
Person person = em.getReference( Person.class, 1L );
person.setFirstName( "Liza" );
LoginAccount loginAccount = person.getLoginAccount();
loginAccount.setName( "abc" );
}
);
doInJPA( this::entityManagerFactory, em -> {
Person person = em.find( Person.class, 1L );
assertThat( person.getFirstName() ).isEqualTo( "Liza" );
LoginAccount loginAccount = person.getLoginAccount();
assertThat( loginAccount ).isNotNull();
assertThat( loginAccount.getName() ).isEqualTo( "abc" );
List<LoginAccount> accounts = em.createQuery(
"select la from LoginAccount la",
LoginAccount.class
).getResultList();
assertThat( accounts.size() ).isEqualTo( 1 );
List<AccountPreferences> preferences = em.createQuery(
"select ap from AccountPreferences ap",
AccountPreferences.class
).getResultList();
assertThat( preferences.size() ).isEqualTo( 1 );
}
);
}
@Entity(name = "Person")
@DynamicUpdate
public static class Person {
@Id
private long id;
private String firstName;
@OneToOne(orphanRemoval = true, cascade = { CascadeType.PERSIST, CascadeType.REMOVE }, fetch = FetchType.LAZY)
private LoginAccount loginAccount = new LoginAccount();
public Person() {
}
public Person(long id, String firstName) {
this.id = id;
this.firstName = firstName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public LoginAccount getLoginAccount() {
return loginAccount;
}
public void setLoginAccount(LoginAccount loginAccount) {
this.loginAccount = loginAccount;
}
}
@Entity(name = "LoginAccount")
@DynamicUpdate
public static class LoginAccount {
@Id
@GeneratedValue
private long id;
private String name;
@OneToOne(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private AccountPreferences accountPreferences = new AccountPreferences();
@OneToOne(mappedBy = "loginAccount")
private Person owner;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public AccountPreferences getAccountPreferences() {
return accountPreferences;
}
public void setAccountPreferences(AccountPreferences accountPreferences) {
this.accountPreferences = accountPreferences;
}
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
this.owner = owner;
}
}
@Entity(name = "AccountPreferences")
@DynamicUpdate
public static class AccountPreferences {
@Id
@GeneratedValue
private long id;
@Column(name = "open_col")
private boolean open = false;
}
}

View File

@ -0,0 +1,282 @@
package org.hibernate.orm.test.bytecode.enhancement.lazy;
import java.util.List;
import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.inlinedirtychecking.DirtyCheckEnhancementContext;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
import org.hibernate.testing.orm.junit.JiraKey;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
@JiraKey("HHH-17049")
@RunWith(BytecodeEnhancerRunner.class)
@CustomEnhancementContext({ NoDirtyCheckingContext.class, DirtyCheckEnhancementContext.class })
public class JpaConstructorInitializationTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] {
Person.class,
LoginAccount.class,
AccountPreferences.class
};
}
@Before
public void setUp() {
doInJPA( this::entityManagerFactory, em -> {
Person person = new Person( 1l, "Henry" );
LoginAccount loginAccount = new LoginAccount();
loginAccount.setOwner( person );
person.setLoginAccount( loginAccount );
em.persist( person );
}
);
doInJPA( this::entityManagerFactory, em -> {
List<LoginAccount> accounts = em.createQuery(
"select la from LoginAccount la",
LoginAccount.class
).getResultList();
assertThat( accounts.size() ).isEqualTo( 1 );
List<AccountPreferences> preferences = em.createQuery(
"select ap from AccountPreferences ap",
AccountPreferences.class
).getResultList();
assertThat( preferences.size() ).isEqualTo( 1 );
}
);
}
@After
public void tearDown() {
doInJPA( this::entityManagerFactory, em -> {
em.createQuery( "delete from Person" ).executeUpdate();
em.createQuery( "delete from LoginAccount" ).executeUpdate();
em.createQuery( "delete from AccountPreferences" ).executeUpdate();
}
);
}
@Test
public void findTest() {
doInJPA( this::entityManagerFactory, em -> {
em.clear();
Person person = em.find( Person.class, 1L );
person.setFirstName( "Liza" );
}
);
doInJPA( this::entityManagerFactory, em -> {
List<LoginAccount> accounts = em.createQuery(
"select la from LoginAccount la",
LoginAccount.class
).getResultList();
assertThat( accounts.size() ).isEqualTo( 1 );
List<AccountPreferences> preferences = em.createQuery(
"select ap from AccountPreferences ap",
AccountPreferences.class
).getResultList();
assertThat( preferences.size() ).isEqualTo( 1 );
}
);
}
@Test
public void getReferenceTest() {
doInJPA( this::entityManagerFactory, em -> {
em.clear();
Person person = em.getReference( Person.class, 1L );
person.setFirstName( "Liza" );
}
);
doInJPA( this::entityManagerFactory, em -> {
List<LoginAccount> accounts = em.createQuery(
"select la from LoginAccount la",
LoginAccount.class
).getResultList();
assertThat( accounts.size() ).isEqualTo( 1 );
List<AccountPreferences> preferences = em.createQuery(
"select ap from AccountPreferences ap",
AccountPreferences.class
).getResultList();
assertThat( preferences.size() ).isEqualTo( 1 );
}
);
}
@Test
public void findTest2() {
doInJPA( this::entityManagerFactory, em -> {
em.clear();
Person person = em.find( Person.class, 1L );
person.setFirstName( "Liza" );
LoginAccount loginAccount = person.getLoginAccount();
loginAccount.setName( "abc" );
}
);
doInJPA( this::entityManagerFactory, em -> {
Person person = em.find( Person.class, 1L );
assertThat( person.getFirstName() ).isEqualTo( "Liza" );
LoginAccount loginAccount = person.getLoginAccount();
assertThat( loginAccount ).isNotNull();
assertThat( loginAccount.getName() ).isEqualTo( "abc" );
List<LoginAccount> accounts = em.createQuery(
"select la from LoginAccount la",
LoginAccount.class
).getResultList();
assertThat( accounts.size() ).isEqualTo( 1 );
List<AccountPreferences> preferences = em.createQuery(
"select ap from AccountPreferences ap",
AccountPreferences.class
).getResultList();
assertThat( preferences.size() ).isEqualTo( 1 );
}
);
}
@Test
public void getReferenceTest2() {
doInJPA( this::entityManagerFactory, em -> {
em.clear();
Person person = em.getReference( Person.class, 1L );
person.setFirstName( "Liza" );
LoginAccount loginAccount = person.getLoginAccount();
loginAccount.setName( "abc" );
}
);
doInJPA( this::entityManagerFactory, em -> {
Person person = em.find( Person.class, 1L );
assertThat( person.getFirstName() ).isEqualTo( "Liza" );
LoginAccount loginAccount = person.getLoginAccount();
assertThat( loginAccount ).isNotNull();
assertThat( loginAccount.getName() ).isEqualTo( "abc" );
List<LoginAccount> accounts = em.createQuery(
"select la from LoginAccount la",
LoginAccount.class
).getResultList();
assertThat( accounts.size() ).isEqualTo( 1 );
List<AccountPreferences> preferences = em.createQuery(
"select ap from AccountPreferences ap",
AccountPreferences.class
).getResultList();
assertThat( preferences.size() ).isEqualTo( 1 );
}
);
}
@Entity(name = "Person")
public static class Person {
@Id
private long id;
private String firstName;
@OneToOne(orphanRemoval = true, cascade = { CascadeType.PERSIST, CascadeType.REMOVE }, fetch = FetchType.LAZY)
private LoginAccount loginAccount = new LoginAccount();
public Person() {
}
public Person(long id, String firstName) {
this.id = id;
this.firstName = firstName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public LoginAccount getLoginAccount() {
return loginAccount;
}
public void setLoginAccount(LoginAccount loginAccount) {
this.loginAccount = loginAccount;
}
}
@Entity(name = "LoginAccount")
public static class LoginAccount {
@Id
@GeneratedValue
private long id;
private String name;
@OneToOne(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private AccountPreferences accountPreferences = new AccountPreferences();
@OneToOne(mappedBy = "loginAccount")
private Person owner;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public AccountPreferences getAccountPreferences() {
return accountPreferences;
}
public void setAccountPreferences(AccountPreferences accountPreferences) {
this.accountPreferences = accountPreferences;
}
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
this.owner = owner;
}
}
@Entity(name = "AccountPreferences")
public static class AccountPreferences {
@Id
@GeneratedValue
private long id;
@Column(name = "open_col")
private boolean open = false;
}
}

View File

@ -0,0 +1,350 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.bytecode.enhancement.lazy;
import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.inlinedirtychecking.DirtyCheckEnhancementContext;
import org.hibernate.orm.test.cascade.A;
import org.hibernate.orm.test.cascade.G;
import org.hibernate.orm.test.cascade.H;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
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 Ovidiu Feodorov
* @author Gail Badner
*/
@RunWith(BytecodeEnhancerRunner.class)
@CustomEnhancementContext({ NoDirtyCheckingContext.class, DirtyCheckEnhancementContext.class })
public class MultiPathCascadeTest extends BaseCoreFunctionalTestCase {
@Override
protected String[] getOrmXmlFiles() {
return new String[] {
"org/hibernate/orm/test/cascade/MultiPathCascade.hbm.xml"
};
}
@After
public void cleanupTest() {
inTransaction(
session -> {
session.createQuery( "delete from A" );
session.createQuery( "delete from G" );
session.createQuery( "delete from H" );
}
);
}
@Test
public void testMultiPathMergeModifiedDetached() {
// persist a simple A in the database
A a = new A();
a.setData( "Anna" );
inTransaction(
session ->
session.save( a )
);
// modify detached entity
modifyEntity( a );
inTransaction(
session -> session.merge( a )
);
verifyModifications( a.getId() );
}
@Test
public void testMultiPathMergeModifiedDetachedIntoProxy() {
// persist a simple A in the database
A a = new A();
a.setData( "Anna" );
inTransaction(
session ->
session.save( a )
);
// modify detached entity
modifyEntity( a );
inTransaction(
session -> {
A aLoaded = session.load( A.class, new Long( a.getId() ) );
assertTrue( aLoaded instanceof HibernateProxy );
assertSame( aLoaded, session.merge( a ) );
}
);
verifyModifications( a.getId() );
}
@Test
public void testMultiPathUpdateModifiedDetached() {
// persist a simple A in the database
A a = new A();
a.setData( "Anna" );
inTransaction(
session ->
session.save( a )
);
// modify detached entity
modifyEntity( a );
inTransaction(
session ->
session.update( a )
);
verifyModifications( a.getId() );
}
@Test
public void testMultiPathGetAndModify() {
// persist a simple A in the database
A a = new A();
a.setData( "Anna" );
inTransaction(
session ->
session.save( a )
);
inTransaction(
session -> {
A result = session.get( A.class, new Long( a.getId() ) );
modifyEntity( result );
}
);
// retrieve the previously saved instance from the database, and update it
verifyModifications( a.getId() );
}
@Test
public void testMultiPathMergeNonCascadedTransientEntityInCollection() {
// persist a simple A in the database
A a = new A();
a.setData( "Anna" );
inTransaction(
session ->
session.save( a )
);
// modify detached entity
modifyEntity( a );
A merged = fromTransaction(
session ->
(A) session.merge( a )
);
verifyModifications( merged.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, merged.getHs().size() );
H h = (H) merged.getHs().iterator().next();
G gNew = new G();
gNew.setData( "Gail" );
gNew.getHs().add( h );
h.getGs().add( gNew );
inSession(
session -> {
session.beginTransaction();
try {
session.merge( merged );
session.merge( h );
session.getTransaction().commit();
fail( "should have thrown IllegalStateException" );
}
catch (IllegalStateException expected) {
// expected
}
finally {
session.getTransaction().rollback();
}
}
);
}
@Test
public void testMultiPathMergeNonCascadedTransientEntityInOneToOne() {
// persist a simple A in the database
A a = new A();
a.setData( "Anna" );
inTransaction(
session ->
session.save( a )
);
// modify detached entity
modifyEntity( a );
A merged = fromTransaction(
session ->
(A) session.merge( a )
);
verifyModifications( merged.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 = merged.getG();
a.setG( null );
A aNew = new A();
aNew.setData( "Alice" );
g.setA( aNew );
aNew.setG( g );
inSession(
session -> {
session.beginTransaction();
try {
session.merge( merged );
session.merge( g );
session.getTransaction().commit();
fail( "should have thrown IllegalStateException" );
}
catch (IllegalStateException expected) {
// expected
}
finally {
session.getTransaction().rollback();
}
}
);
}
@Test
public void testMultiPathMergeNonCascadedTransientEntityInManyToOne() {
// persist a simple A in the database
A a = new A();
a.setData( "Anna" );
inTransaction(
session ->
session.save( a )
);
// modify detached entity
modifyEntity( a );
A merged = fromTransaction(
session ->
(A) session.merge( a )
);
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, merged.getHs().size() );
H h = (H) merged.getHs().iterator().next();
merged.getHs().remove( h );
A aNew = new A();
aNew.setData( "Alice" );
aNew.addH( h );
inSession(
session -> {
session.beginTransaction();
try {
session.merge( merged );
session.merge( h );
session.getTransaction().commit();
fail( "should have thrown IllegalStateException" );
}
catch (IllegalStateException expected) {
// expected
}
finally {
session.getTransaction().rollback();
}
}
);
}
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) {
inTransaction(
session -> {
// retrieve the A object and check it
A a = session.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() );
}
);
}
}

View File

@ -0,0 +1,290 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.bytecode.enhancement.lazy.backref;
import org.hibernate.Hibernate;
import org.hibernate.LockMode;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.orm.test.bytecode.enhancement.lazy.NoDirtyCheckingContext;
import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.inlinedirtychecking.DirtyCheckEnhancementContext;
import org.hibernate.orm.test.collection.backref.map.compkey.MapKey;
import org.hibernate.orm.test.collection.backref.map.compkey.Part;
import org.hibernate.orm.test.collection.backref.map.compkey.Product;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* BackrefCompositeMapKeyTest implementation. Test access to a composite map-key
* backref via a number of different access methods.
*
* @author Steve Ebersole
*/
@RunWith( BytecodeEnhancerRunner.class )
@CustomEnhancementContext({ NoDirtyCheckingContext.class, DirtyCheckEnhancementContext.class })
public class BackrefCompositeMapKeyTest extends BaseCoreFunctionalTestCase {
@Override
protected String[] getOrmXmlFiles() {
return new String[] {
"org/hibernate/orm/test/collection/backref/map/compkey/Mappings.hbm.xml"
};
}
@Test
public void testOrphanDeleteOnDelete() {
inTransaction(
session -> {
Product prod = new Product( "Widget" );
Part part = new Part( "Widge", "part if a Widget" );
MapKey mapKey = new MapKey( "Top" );
prod.getParts().put( mapKey, part );
Part part2 = new Part( "Get", "another part if a Widget" );
prod.getParts().put( new MapKey( "Bottom" ), part2 );
session.persist( prod );
session.flush();
prod.getParts().remove( mapKey );
session.delete( prod );
}
);
inTransaction(
session -> {
assertNull( "Orphan 'Widge' was not deleted", session.get( Part.class, "Widge" ) );
assertNull( "Orphan 'Get' was not deleted", session.get( Part.class, "Get" ) );
assertNull( "Orphan 'Widget' was not deleted", session.get( Product.class, "Widget" ) );
}
);
}
@Test
public void testOrphanDeleteAfterPersist() {
inTransaction(
session -> {
Product prod = new Product( "Widget" );
Part part = new Part( "Widge", "part if a Widget" );
MapKey mapKey = new MapKey( "Top" );
prod.getParts().put( mapKey, part );
Part part2 = new Part( "Get", "another part if a Widget" );
prod.getParts().put( new MapKey( "Bottom" ), part2 );
session.persist( prod );
prod.getParts().remove( mapKey );
}
);
inTransaction(
session ->
session.delete( session.get( Product.class, "Widget" ) )
);
}
@Test
public void testOrphanDeleteAfterPersistAndFlush() {
inTransaction(
session -> {
Product prod = new Product( "Widget" );
Part part = new Part( "Widge", "part if a Widget" );
MapKey mapKey = new MapKey( "Top" );
prod.getParts().put( mapKey, part );
Part part2 = new Part( "Get", "another part if a Widget" );
prod.getParts().put( new MapKey( "Bottom" ), part2 );
session.persist( prod );
session.flush();
prod.getParts().remove( mapKey );
}
);
inTransaction(
session -> {
assertNull( session.get( Part.class, "Widge" ) );
assertNotNull( session.get( Part.class, "Get" ) );
session.delete( session.get( Product.class, "Widget" ) );
}
);
}
@Test
public void testOrphanDeleteAfterLock() {
Product prod = new Product( "Widget" );
MapKey mapKey = new MapKey( "Top" );
inTransaction(
session -> {
Part part = new Part( "Widge", "part if a Widget" );
prod.getParts().put( mapKey, part );
Part part2 = new Part( "Get", "another part if a Widget" );
prod.getParts().put( new MapKey( "Bottom" ), part2 );
session.persist( prod );
}
);
inTransaction(
session -> {
session.lock( prod, LockMode.READ );
prod.getParts().remove( mapKey );
}
);
inTransaction(
session -> {
assertNull( session.get( Part.class, "Widge" ) );
assertNotNull( session.get( Part.class, "Get" ) );
session.delete( session.get( Product.class, "Widget" ) );
}
);
}
@Test
public void testOrphanDeleteOnSaveOrUpdate() {
Product prod = new Product( "Widget" );
MapKey mapKey = new MapKey( "Top" );
inTransaction(
session -> {
Part part = new Part( "Widge", "part if a Widget" );
prod.getParts().put( mapKey, part );
Part part2 = new Part( "Get", "another part if a Widget" );
prod.getParts().put( new MapKey( "Bottom" ), part2 );
session.persist( prod );
}
);
prod.getParts().remove( mapKey );
inTransaction(
session ->
session.saveOrUpdate( prod )
);
inTransaction(
session -> {
assertNull( session.get( Part.class, "Widge" ) );
assertNotNull( session.get( Part.class, "Get" ) );
session.delete( session.get( Product.class, "Widget" ) );
}
);
}
@Test
public void testOrphanDeleteOnSaveOrUpdateAfterSerialization() {
Product prod = new Product( "Widget" );
MapKey mapKey = new MapKey( "Top" );
inTransaction(
session -> {
Part part = new Part( "Widge", "part if a Widget" );
prod.getParts().put( mapKey, part );
Part part2 = new Part( "Get", "another part if a Widget" );
prod.getParts().put( new MapKey( "Bottom" ), part2 );
session.persist( prod );
}
);
prod.getParts().remove( mapKey );
Product cloned = (Product) SerializationHelper.clone( prod );
inTransaction(
session ->
session.saveOrUpdate( cloned )
);
inTransaction(
session -> {
assertNull( session.get( Part.class, "Widge" ) );
assertNotNull( session.get( Part.class, "Get" ) );
session.delete( session.get( Product.class, "Widget" ) );
}
);
}
@Test
public void testOrphanDelete() {
MapKey mapKey = new MapKey( "Top" );
inTransaction(
session -> {
Product prod = new Product( "Widget" );
Part part = new Part( "Widge", "part if a Widget" );
prod.getParts().put( mapKey, part );
Part part2 = new Part( "Get", "another part if a Widget" );
prod.getParts().put( new MapKey( "Bottom" ), part2 );
session.persist( prod );
}
);
SessionFactoryImplementor sessionFactory = sessionFactory();
sessionFactory.getCache().evictEntityData( Product.class );
sessionFactory.getCache().evictEntityData( Part.class );
inTransaction(
session -> {
Product prod = session.get( Product.class, "Widget" );
assertTrue( Hibernate.isInitialized( prod.getParts() ) );
Part part = session.get( Part.class, "Widge" );
prod.getParts().remove( mapKey );
}
);
sessionFactory.getCache().evictEntityData( Product.class );
sessionFactory.getCache().evictEntityData( Part.class );
inTransaction(
session -> {
Product prod = session.get( Product.class, "Widget" );
assertTrue( Hibernate.isInitialized( prod.getParts() ) );
assertNull( prod.getParts().get( new MapKey( "Top" ) ) );
assertNotNull( session.get( Part.class, "Get" ) );
session.delete( session.get( Product.class, "Widget" ) );
}
);
}
@Test
public void testOrphanDeleteOnMerge() {
Product prod = new Product( "Widget" );
MapKey mapKey = new MapKey( "Top" );
inTransaction(
session -> {
Part part = new Part( "Widge", "part if a Widget" );
prod.getParts().put( mapKey, part );
Part part2 = new Part( "Get", "another part if a Widget" );
prod.getParts().put( new MapKey( "Bottom" ), part2 );
session.persist( prod );
}
);
prod.getParts().remove( mapKey );
inTransaction(
session ->
session.merge( prod )
);
inTransaction(
session -> {
assertNull( session.get( Part.class, "Widge" ) );
assertNotNull( session.get( Part.class, "Get" ) );
session.delete( session.get( Product.class, "Widget" ) );
}
);
}
}

View File

@ -0,0 +1,404 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.bytecode.enhancement.orphan;
import org.hibernate.Hibernate;
import org.hibernate.LockMode;
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.orm.test.orphan.Part;
import org.hibernate.orm.test.orphan.Product;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Gavin King
*/
@RunWith(BytecodeEnhancerRunner.class)
@CustomEnhancementContext({
EnhancerTestContext.class, // supports laziness and dirty-checking
DefaultEnhancementContext.class
})
public class OrphanTest extends BaseCoreFunctionalTestCase {
@Override
protected String[] getOrmXmlFiles() {
return new String[]{
"org/hibernate/orm/test/orphan/Product.hbm.xml"
};
}
@After
public void tearDown() {
inTransaction(
session -> {
session.createQuery( "delete from Part" ).executeUpdate();
session.createQuery( "delete from Product" ).executeUpdate();
}
);
}
@Test
@SuppressWarnings("unchecked")
public void testOrphanDeleteOnDelete() {
inTransaction(
session -> {
Product prod = new Product();
prod.setName( "Widget" );
Part part = new Part();
part.setName( "Widge" );
part.setDescription( "part if a Widget" );
prod.getParts().add( part );
Part part2 = new Part();
part2.setName( "Get" );
part2.setDescription( "another part if a Widget" );
prod.getParts().add( part2 );
session.persist( prod );
session.flush();
prod.getParts().remove( part );
session.delete( prod );
}
);
inTransaction(
session -> {
assertNull( session.get( Part.class, "Widge" ) );
assertNull( session.get( Part.class, "Get" ) );
assertNull( session.get( Product.class, "Widget" ) );
}
);
}
@Test
@SuppressWarnings("unchecked")
public void testOrphanDeleteAfterPersist() {
inTransaction(
session -> {
Product prod = new Product();
prod.setName( "Widget" );
Part part = new Part();
part.setName( "Widge" );
part.setDescription( "part if a Widget" );
prod.getParts().add( part );
Part part2 = new Part();
part2.setName( "Get" );
part2.setDescription( "another part if a Widget" );
prod.getParts().add( part2 );
session.persist( prod );
prod.getParts().remove( part );
}
);
inTransaction(
session -> {
assertNull( session.get( Part.class, "Widge" ) );
assertNotNull( session.get( Part.class, "Get" ) );
session.delete( session.get( Product.class, "Widget" ) );
}
);
}
@Test
@SuppressWarnings("unchecked")
public void testOrphanDeleteAfterPersistAndFlush() {
inTransaction(
session -> {
Product prod = new Product();
prod.setName( "Widget" );
Part part = new Part();
part.setName( "Widge" );
part.setDescription( "part if a Widget" );
prod.getParts().add( part );
Part part2 = new Part();
part2.setName( "Get" );
part2.setDescription( "another part if a Widget" );
prod.getParts().add( part2 );
session.persist( prod );
session.flush();
prod.getParts().remove( part );
}
);
inTransaction(
session -> {
assertNull( session.get( Part.class, "Widge" ) );
assertNotNull( session.get( Part.class, "Get" ) );
session.delete( session.get( Product.class, "Widget" ) );
}
);
}
@Test
@SuppressWarnings("unchecked")
public void testOrphanDeleteAfterLock() {
Product prod = new Product();
Part part = new Part();
inTransaction(
session -> {
prod.setName( "Widget" );
part.setName( "Widge" );
part.setDescription( "part if a Widget" );
prod.getParts().add( part );
Part part2 = new Part();
part2.setName( "Get" );
part2.setDescription( "another part if a Widget" );
prod.getParts().add( part2 );
session.persist( prod );
}
);
inTransaction(
session -> {
session.lock( prod, LockMode.READ );
prod.getParts().remove( part );
}
);
inTransaction(
session -> {
assertNull( session.get( Part.class, "Widge" ) );
assertNotNull( session.get( Part.class, "Get" ) );
session.delete( session.get( Product.class, "Widget" ) );
}
);
}
@Test
@SuppressWarnings("unchecked")
public void testOrphanDeleteOnSaveOrUpdate() {
Product prod = new Product();
Part part = new Part();
inTransaction(
session -> {
prod.setName( "Widget" );
part.setName( "Widge" );
part.setDescription( "part if a Widget" );
prod.getParts().add( part );
Part part2 = new Part();
part2.setName( "Get" );
part2.setDescription( "another part if a Widget" );
prod.getParts().add( part2 );
session.persist( prod );
}
);
prod.getParts().remove( part );
inTransaction(
session ->
session.saveOrUpdate( prod )
);
inTransaction(
session -> {
assertNull( session.get( Part.class, "Widge" ) );
assertNotNull( session.get( Part.class, "Get" ) );
session.delete( session.get( Product.class, "Widget" ) );
}
);
}
@Test
@SuppressWarnings("unchecked")
public void testOrphanDeleteOnSaveOrUpdateAfterSerialization() {
Product prod = new Product();
Part part = new Part();
inTransaction(
session -> {
prod.setName( "Widget" );
part.setName( "Widge" );
part.setDescription( "part if a Widget" );
prod.getParts().add( part );
Part part2 = new Part();
part2.setName( "Get" );
part2.setDescription( "another part if a Widget" );
prod.getParts().add( part2 );
session.persist( prod );
}
);
prod.getParts().remove( part );
Product cloned = (Product) SerializationHelper.clone( prod );
inTransaction(
session ->
session.saveOrUpdate( cloned )
);
inTransaction(
session -> {
assertNull( session.get( Part.class, "Widge" ) );
assertNotNull( session.get( Part.class, "Get" ) );
session.delete( session.get( Product.class, "Widget" ) );
}
);
}
@Test
@SuppressWarnings("unchecked")
public void testOrphanDelete() {
inTransaction(
session -> {
Product prod = new Product();
prod.setName( "Widget" );
Part part = new Part();
part.setName( "Widge" );
part.setDescription( "part if a Widget" );
prod.getParts().add( part );
Part part2 = new Part();
part2.setName( "Get" );
part2.setDescription( "another part if a Widget" );
prod.getParts().add( part2 );
session.persist( prod );
}
);
sessionFactory().getCache().evictEntityData( Product.class );
sessionFactory().getCache().evictEntityData( Part.class );
inTransaction(
session -> {
Product prod = session.get( Product.class, "Widget" );
assertTrue( Hibernate.isInitialized( prod.getParts() ) );
Part part = session.get( Part.class, "Widge" );
prod.getParts().remove( part );
}
);
inTransaction(
session -> {
assertNull( session.get( Part.class, "Widge" ) );
assertNotNull( session.get( Part.class, "Get" ) );
session.delete( session.get( Product.class, "Widget" ) );
}
);
}
@Test
@SuppressWarnings("unchecked")
public void testOrphanDeleteOnMerge() {
Product prod = new Product();
Part part = new Part();
inTransaction(
session -> {
prod.setName( "Widget" );
part.setName( "Widge" );
part.setDescription( "part if a Widget" );
prod.getParts().add( part );
Part part2 = new Part();
part2.setName( "Get" );
part2.setDescription( "another part if a Widget" );
prod.getParts().add( part2 );
session.persist( prod );
}
);
prod.getParts().remove( part );
inTransaction(
session ->
session.merge( prod )
);
inTransaction(
session -> {
assertNull( session.get( Part.class, "Widge" ) );
assertNotNull( session.get( Part.class, "Get" ) );
session.delete( session.get( Product.class, "Widget" ) );
}
);
}
@Test
@SuppressWarnings("unchecked")
public void testOrphanDeleteOnMergeRemoveElementMerge() {
Product prod = new Product();
Part part = new Part();
inTransaction(
session -> {
prod.setName( "Widget" );
part.setName( "Widge" );
part.setDescription( "part if a Widget" );
prod.getParts().add( part );
session.persist( prod );
}
);
inTransaction(
session -> {
session.merge( prod );
prod.getParts().remove( part );
session.merge( prod );
}
);
inTransaction(
session -> {
assertNull( session.get( Part.class, "Widge" ) );
session.delete( session.get( Product.class, "Widget" ) );
}
);
}
@Test
@SuppressWarnings("unchecked")
@TestForIssue(jiraKey = "HHH-9171")
public void testOrphanDeleteOnAddElementMergeRemoveElementMerge() {
Product prod = new Product();
inTransaction(
session -> {
prod.setName( "Widget" );
session.persist( prod );
}
);
Part part = new Part();
part.setName( "Widge" );
part.setDescription( "part if a Widget" );
prod.getParts().add( part );
inTransaction(
session -> {
session.merge( prod );
// In Section 2.9, Entity Relationships, the JPA 2.1 spec says:
// "If the entity being orphaned is a detached, new, or removed entity,
// the semantics of orphanRemoval do not apply."
// In other words, since part is a new entity, it will not be deleted when removed
// from prod.parts, even though cascade for the association includes "delete-orphan".
prod.getParts().remove( part );
session.merge( prod );
}
);
inTransaction(
session -> {
assertNotNull( session.get( Part.class, "Widge" ) );
session.delete( session.get( Product.class, "Widget" ) );
}
);
}
}

View File

@ -41,7 +41,7 @@ public class Route {
this.name = name;
}
protected Set getNodes() {
public Set getNodes() {
return nodes;
}