HHH-12007 : Executing JPA query results in persistence of new/unmanaged entity
This commit is contained in:
parent
a9f1caf998
commit
d2a19c9a77
|
@ -14,12 +14,16 @@ import org.hibernate.Session;
|
||||||
import org.hibernate.TransientObjectException;
|
import org.hibernate.TransientObjectException;
|
||||||
import org.hibernate.engine.internal.ForeignKeys;
|
import org.hibernate.engine.internal.ForeignKeys;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
import org.hibernate.loader.PropertyPath;
|
import org.hibernate.loader.PropertyPath;
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
import org.hibernate.service.ServiceRegistry;
|
import org.hibernate.service.ServiceRegistry;
|
||||||
import org.hibernate.type.EntityType;
|
import org.hibernate.type.EntityType;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
|
import static org.hibernate.internal.CoreLogging.logger;
|
||||||
|
import static org.hibernate.internal.CoreLogging.messageLogger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <b>foreign</b><br>
|
* <b>foreign</b><br>
|
||||||
* <br>
|
* <br>
|
||||||
|
@ -31,6 +35,8 @@ import org.hibernate.type.Type;
|
||||||
* @author Gavin King
|
* @author Gavin King
|
||||||
*/
|
*/
|
||||||
public class ForeignGenerator implements IdentifierGenerator, Configurable {
|
public class ForeignGenerator implements IdentifierGenerator, Configurable {
|
||||||
|
private static final CoreMessageLogger LOG = messageLogger( ForeignGenerator.class );
|
||||||
|
|
||||||
private String entityName;
|
private String entityName;
|
||||||
private String propertyName;
|
private String propertyName;
|
||||||
|
|
||||||
|
@ -105,6 +111,12 @@ public class ForeignGenerator implements IdentifierGenerator, Configurable {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
catch (TransientObjectException toe) {
|
catch (TransientObjectException toe) {
|
||||||
|
if ( LOG.isDebugEnabled() ) {
|
||||||
|
LOG.debugf(
|
||||||
|
"ForeignGenerator detected a transient entity [%s]",
|
||||||
|
foreignValueSourceType.getAssociatedEntityName()
|
||||||
|
);
|
||||||
|
}
|
||||||
id = session.save( foreignValueSourceType.getAssociatedEntityName(), associatedObject );
|
id = session.save( foreignValueSourceType.getAssociatedEntityName(), associatedObject );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
package org.hibernate.tuple.entity;
|
package org.hibernate.tuple.entity;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -18,17 +17,11 @@ import org.hibernate.HibernateException;
|
||||||
import org.hibernate.MappingException;
|
import org.hibernate.MappingException;
|
||||||
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
|
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
|
||||||
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
|
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
|
||||||
import org.hibernate.engine.internal.ForeignKeys;
|
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
import org.hibernate.engine.spi.EntityKey;
|
import org.hibernate.engine.spi.EntityKey;
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.event.service.spi.EventListenerRegistry;
|
|
||||||
import org.hibernate.event.spi.EventSource;
|
|
||||||
import org.hibernate.event.spi.EventType;
|
|
||||||
import org.hibernate.event.spi.PersistEvent;
|
|
||||||
import org.hibernate.event.spi.PersistEventListener;
|
|
||||||
import org.hibernate.id.Assigned;
|
import org.hibernate.id.Assigned;
|
||||||
import org.hibernate.internal.CoreMessageLogger;
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
import org.hibernate.loader.PropertyPath;
|
import org.hibernate.loader.PropertyPath;
|
||||||
|
@ -177,6 +170,7 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
|
||||||
else {
|
else {
|
||||||
identifierMapperType = (CompositeType) mapper.getType();
|
identifierMapperType = (CompositeType) mapper.getType();
|
||||||
mappedIdentifierValueMarshaller = buildMappedIdentifierValueMarshaller(
|
mappedIdentifierValueMarshaller = buildMappedIdentifierValueMarshaller(
|
||||||
|
getEntityName(),
|
||||||
getFactory(),
|
getFactory(),
|
||||||
(ComponentType) entityMetamodel.getIdentifierProperty().getType(),
|
(ComponentType) entityMetamodel.getIdentifierProperty().getType(),
|
||||||
(ComponentType) identifierMapperType
|
(ComponentType) identifierMapperType
|
||||||
|
@ -278,6 +272,7 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
|
||||||
private final MappedIdentifierValueMarshaller mappedIdentifierValueMarshaller;
|
private final MappedIdentifierValueMarshaller mappedIdentifierValueMarshaller;
|
||||||
|
|
||||||
private static MappedIdentifierValueMarshaller buildMappedIdentifierValueMarshaller(
|
private static MappedIdentifierValueMarshaller buildMappedIdentifierValueMarshaller(
|
||||||
|
String entityName,
|
||||||
SessionFactoryImplementor sessionFactory,
|
SessionFactoryImplementor sessionFactory,
|
||||||
ComponentType mappedIdClassComponentType,
|
ComponentType mappedIdClassComponentType,
|
||||||
ComponentType virtualIdComponent) {
|
ComponentType virtualIdComponent) {
|
||||||
|
@ -302,9 +297,10 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return wereAllEquivalent
|
return wereAllEquivalent ?
|
||||||
? new NormalMappedIdentifierValueMarshaller( virtualIdComponent, mappedIdClassComponentType )
|
new NormalMappedIdentifierValueMarshaller( virtualIdComponent, mappedIdClassComponentType ) :
|
||||||
: new IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller(
|
new IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller(
|
||||||
|
entityName,
|
||||||
sessionFactory,
|
sessionFactory,
|
||||||
virtualIdComponent,
|
virtualIdComponent,
|
||||||
mappedIdClassComponentType
|
mappedIdClassComponentType
|
||||||
|
@ -342,15 +338,18 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
|
||||||
|
|
||||||
private static class IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller
|
private static class IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller
|
||||||
implements MappedIdentifierValueMarshaller {
|
implements MappedIdentifierValueMarshaller {
|
||||||
|
private final String entityName;
|
||||||
private final SessionFactoryImplementor sessionFactory;
|
private final SessionFactoryImplementor sessionFactory;
|
||||||
private final ComponentType virtualIdComponent;
|
private final ComponentType virtualIdComponent;
|
||||||
private final ComponentType mappedIdentifierType;
|
private final ComponentType mappedIdentifierType;
|
||||||
|
|
||||||
private IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller(
|
private IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller(
|
||||||
|
String entityName,
|
||||||
SessionFactoryImplementor sessionFactory,
|
SessionFactoryImplementor sessionFactory,
|
||||||
ComponentType virtualIdComponent,
|
ComponentType virtualIdComponent,
|
||||||
ComponentType mappedIdentifierType) {
|
ComponentType mappedIdentifierType) {
|
||||||
this.sessionFactory = sessionFactory;
|
this.sessionFactory = sessionFactory;
|
||||||
|
this.entityName = entityName;
|
||||||
this.virtualIdComponent = virtualIdComponent;
|
this.virtualIdComponent = virtualIdComponent;
|
||||||
this.mappedIdentifierType = mappedIdentifierType;
|
this.mappedIdentifierType = mappedIdentifierType;
|
||||||
}
|
}
|
||||||
|
@ -368,7 +367,7 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
|
||||||
}
|
}
|
||||||
//JPA 2 @MapsId + @IdClass points to the pk of the entity
|
//JPA 2 @MapsId + @IdClass points to the pk of the entity
|
||||||
if ( subTypes[i].isAssociationType() && !copierSubTypes[i].isAssociationType() ) {
|
if ( subTypes[i].isAssociationType() && !copierSubTypes[i].isAssociationType() ) {
|
||||||
propertyValues[i] = determineEntityIdPersistIfNecessary(
|
propertyValues[i] = determineEntityId(
|
||||||
propertyValues[i],
|
propertyValues[i],
|
||||||
(AssociationType) subTypes[i],
|
(AssociationType) subTypes[i],
|
||||||
session,
|
session,
|
||||||
|
@ -404,6 +403,13 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
|
||||||
if ( association == null ) {
|
if ( association == null ) {
|
||||||
// otherwise look for an initialized version
|
// otherwise look for an initialized version
|
||||||
association = persistenceContext.getEntity( entityKey );
|
association = persistenceContext.getEntity( entityKey );
|
||||||
|
if ( association == null ) {
|
||||||
|
// get the association out of the entity itself
|
||||||
|
association = sessionFactory.getMetamodel().entityPersister( entityName ).getPropertyValue(
|
||||||
|
entity,
|
||||||
|
virtualIdComponent.getPropertyNames()[i]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
injectionValues[i] = association;
|
injectionValues[i] = association;
|
||||||
}
|
}
|
||||||
|
@ -415,19 +421,7 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Iterable<PersistEventListener> persistEventListeners(SharedSessionContractImplementor session) {
|
private static Serializable determineEntityId(
|
||||||
if ( session == null ) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
return session
|
|
||||||
.getFactory()
|
|
||||||
.getServiceRegistry()
|
|
||||||
.getService( EventListenerRegistry.class )
|
|
||||||
.getEventListenerGroup( EventType.PERSIST )
|
|
||||||
.listeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Serializable determineEntityIdPersistIfNecessary(
|
|
||||||
Object entity,
|
Object entity,
|
||||||
AssociationType associationType,
|
AssociationType associationType,
|
||||||
SharedSessionContractImplementor session,
|
SharedSessionContractImplementor session,
|
||||||
|
@ -436,9 +430,6 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE : persist if necessary for proper merge support (HHH-11328)
|
|
||||||
// but only allow persist if a Session is passed (HHH-11274)
|
|
||||||
|
|
||||||
if ( HibernateProxy.class.isInstance( entity ) ) {
|
if ( HibernateProxy.class.isInstance( entity ) ) {
|
||||||
// entity is a proxy, so we know it is not transient; just return ID from proxy
|
// entity is a proxy, so we know it is not transient; just return ID from proxy
|
||||||
return ( (HibernateProxy) entity ).getHibernateLazyInitializer().getIdentifier();
|
return ( (HibernateProxy) entity ).getHibernateLazyInitializer().getIdentifier();
|
||||||
|
@ -459,36 +450,7 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
|
||||||
sessionFactory
|
sessionFactory
|
||||||
);
|
);
|
||||||
|
|
||||||
Serializable entityId = persister.getIdentifier( entity, session );
|
return persister.getIdentifier( entity, session );
|
||||||
|
|
||||||
if ( entityId == null ) {
|
|
||||||
if ( session != null ) {
|
|
||||||
// if we have a session, then follow the HHH-11328 requirements
|
|
||||||
entityId = persistTransientEntity( entity, session );
|
|
||||||
}
|
|
||||||
// otherwise just let it be null HHH-11274
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if ( session != null ) {
|
|
||||||
// if the entity is in the process of being merged, it may be stored in the
|
|
||||||
// PC already, but doesn't have an EntityEntry yet. If this is the case,
|
|
||||||
// then don't persist even if it is transient because doing so can lead
|
|
||||||
// to having 2 entities in the PC with the same ID (HHH-11328).
|
|
||||||
final EntityKey entityKey = session.generateEntityKey( entityId, persister );
|
|
||||||
if ( session.getPersistenceContext().getEntity( entityKey ) == null &&
|
|
||||||
ForeignKeys.isTransient(
|
|
||||||
persister.getEntityName(),
|
|
||||||
entity,
|
|
||||||
null,
|
|
||||||
session
|
|
||||||
) ) {
|
|
||||||
// entity is transient and it is not in the PersistenceContext.
|
|
||||||
// entity needs to be persisted.
|
|
||||||
persistTransientEntity( entity, session );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entityId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EntityPersister resolveEntityPersister(
|
private static EntityPersister resolveEntityPersister(
|
||||||
|
@ -520,28 +482,6 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
|
||||||
return sessionFactory.getMetamodel().entityPersister( entityName );
|
return sessionFactory.getMetamodel().entityPersister( entityName );
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Serializable persistTransientEntity(
|
|
||||||
Object entity,
|
|
||||||
SharedSessionContractImplementor session) {
|
|
||||||
assert session != null;
|
|
||||||
|
|
||||||
LOG.debug( "Performing implicit derived identity cascade" );
|
|
||||||
final PersistEvent event = new PersistEvent(
|
|
||||||
null,
|
|
||||||
entity,
|
|
||||||
(EventSource) session
|
|
||||||
);
|
|
||||||
|
|
||||||
for ( PersistEventListener listener : persistEventListeners( session ) ) {
|
|
||||||
listener.onPersist( event );
|
|
||||||
}
|
|
||||||
final EntityEntry pcEntry = session.getPersistenceContext().getEntry( entity );
|
|
||||||
if ( pcEntry == null || pcEntry.getId() == null ) {
|
|
||||||
throw new HibernateException( "Unable to process implicit derived identity cascade" );
|
|
||||||
}
|
|
||||||
return pcEntry.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resetIdentifier(Object entity, Serializable currentId, Object currentVersion) {
|
public void resetIdentifier(Object entity, Serializable currentId, Object currentVersion) {
|
||||||
// 99% of the time the session is not needed. Its only needed for certain brain-dead
|
// 99% of the time the session is not needed. Its only needed for certain brain-dead
|
||||||
|
|
|
@ -29,6 +29,9 @@ import org.hibernate.testing.TestForIssue;
|
||||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class CompositeIdDerivedIdWithIdClassTest extends BaseCoreFunctionalTestCase {
|
public class CompositeIdDerivedIdWithIdClassTest extends BaseCoreFunctionalTestCase {
|
||||||
@Override
|
@Override
|
||||||
|
@ -94,7 +97,7 @@ public class CompositeIdDerivedIdWithIdClassTest extends BaseCoreFunctionalTestC
|
||||||
// merge lineItem with an ID with detached many-to-one
|
// merge lineItem with an ID with detached many-to-one
|
||||||
s = openSession();
|
s = openSession();
|
||||||
s.getTransaction().begin();
|
s.getTransaction().begin();
|
||||||
s.merge(lineItem);
|
s.merge( lineItem );
|
||||||
s.getTransaction().commit();
|
s.getTransaction().commit();
|
||||||
s.close();
|
s.close();
|
||||||
|
|
||||||
|
@ -107,8 +110,116 @@ public class CompositeIdDerivedIdWithIdClassTest extends BaseCoreFunctionalTestC
|
||||||
s.close();
|
s.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestForIssue( jiraKey = "HHH-12007")
|
||||||
|
public void testBindTransientEntityWithTransientKeyManyToOne() {
|
||||||
|
|
||||||
|
ShoppingCart cart = new ShoppingCart( "cart" );
|
||||||
|
LineItem item = new LineItem( 0, "desc", cart );
|
||||||
|
|
||||||
|
Session s = openSession();
|
||||||
|
s.getTransaction().begin();
|
||||||
|
|
||||||
|
String cartId = s.createQuery(
|
||||||
|
"select c.id from Cart c left join c.lineItems i where i = :item",
|
||||||
|
String.class
|
||||||
|
).setParameter( "item", item ).uniqueResult();
|
||||||
|
|
||||||
|
assertNull( cartId );
|
||||||
|
|
||||||
|
assertFalse( s.contains( item ) );
|
||||||
|
assertFalse( s.contains( cart ) );
|
||||||
|
|
||||||
|
s.getTransaction().commit();
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestForIssue( jiraKey = "HHH-12007")
|
||||||
|
public void testBindTransientEntityWithPersistentKeyManyToOne() {
|
||||||
|
ShoppingCart cart = new ShoppingCart( "cart" );
|
||||||
|
LineItem item = new LineItem( 0, "desc", cart );
|
||||||
|
|
||||||
|
Session s = openSession();
|
||||||
|
s.getTransaction().begin();
|
||||||
|
|
||||||
|
session.persist( cart );
|
||||||
|
String cartId = s.createQuery(
|
||||||
|
"select c.id from Cart c left join c.lineItems i where i = :item",
|
||||||
|
String.class
|
||||||
|
).setParameter( "item", item ).uniqueResult();
|
||||||
|
|
||||||
|
assertNull( cartId );
|
||||||
|
|
||||||
|
assertFalse( s.contains( item ) );
|
||||||
|
assertTrue( s.contains( cart ) );
|
||||||
|
|
||||||
|
s.getTransaction().commit();
|
||||||
|
s.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestForIssue( jiraKey = "HHH-12007")
|
||||||
|
public void testBindTransientEntityWithDetachedKeyManyToOne() {
|
||||||
|
Session s = openSession();
|
||||||
|
s.getTransaction().begin();
|
||||||
|
|
||||||
|
ShoppingCart cart = new ShoppingCart( "cart" );
|
||||||
|
|
||||||
|
s.getTransaction().commit();
|
||||||
|
s.close();
|
||||||
|
|
||||||
|
LineItem item = new LineItem( 0, "desc", cart );
|
||||||
|
|
||||||
|
s = openSession();
|
||||||
|
s.getTransaction().begin();
|
||||||
|
|
||||||
|
String cartId = s.createQuery(
|
||||||
|
"select c.id from Cart c left join c.lineItems i where i = :item",
|
||||||
|
String.class
|
||||||
|
).setParameter( "item", item ).uniqueResult();
|
||||||
|
|
||||||
|
assertNull( cartId );
|
||||||
|
|
||||||
|
assertFalse( s.contains( item ) );
|
||||||
|
assertFalse( s.contains( cart ) );
|
||||||
|
|
||||||
|
s.getTransaction().commit();
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestForIssue( jiraKey = "HHH-12007")
|
||||||
|
public void testBindTransientEntityWithCopiedKeyManyToOne() {
|
||||||
|
Session s = openSession();
|
||||||
|
s.getTransaction().begin();
|
||||||
|
|
||||||
|
ShoppingCart cart = new ShoppingCart( "cart" );
|
||||||
|
s.getTransaction().commit();
|
||||||
|
s.close();
|
||||||
|
|
||||||
|
LineItem item = new LineItem( 0, "desc", new ShoppingCart( "cart" ) );
|
||||||
|
|
||||||
|
s = openSession();
|
||||||
|
s.getTransaction().begin();
|
||||||
|
|
||||||
|
String cartId = s.createQuery(
|
||||||
|
"select c.id from Cart c left join c.lineItems i where i = :item",
|
||||||
|
String.class
|
||||||
|
).setParameter( "item", item ).uniqueResult();
|
||||||
|
|
||||||
|
assertNull( cartId );
|
||||||
|
|
||||||
|
assertFalse( s.contains( item ) );
|
||||||
|
assertFalse( s.contains( cart ) );
|
||||||
|
|
||||||
|
s.getTransaction().commit();
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
|
||||||
@Entity(name = "Cart")
|
@Entity(name = "Cart")
|
||||||
public static class ShoppingCart {
|
public static class ShoppingCart implements Serializable{
|
||||||
@Id
|
@Id
|
||||||
@Column(name = "id", nullable = false)
|
@Column(name = "id", nullable = false)
|
||||||
private String id;
|
private String id;
|
||||||
|
@ -147,7 +258,7 @@ public class CompositeIdDerivedIdWithIdClassTest extends BaseCoreFunctionalTestC
|
||||||
|
|
||||||
@Entity(name = "LineItem")
|
@Entity(name = "LineItem")
|
||||||
@IdClass(LineItem.Pk.class)
|
@IdClass(LineItem.Pk.class)
|
||||||
public static class LineItem {
|
public static class LineItem implements Serializable {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@Column(name = "item_seq_number", nullable = false)
|
@Column(name = "item_seq_number", nullable = false)
|
||||||
|
|
Loading…
Reference in New Issue