HHH-15341 Disallow passing transient entity parameter values

This commit is contained in:
Christian Beikov 2022-06-15 14:31:55 +02:00
parent 04fd92b204
commit 3cf6f2e3ef
5 changed files with 76 additions and 4 deletions

View File

@ -6,6 +6,10 @@
*/
package org.hibernate.metamodel.mapping;
import java.io.Serializable;
import org.hibernate.TransientObjectException;
import org.hibernate.engine.internal.ForeignKeys;
import org.hibernate.engine.spi.IdentifierValue;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
@ -41,6 +45,48 @@ public interface EntityIdentifierMapping extends ValueMapping, ModelPart {
Object getIdentifier(Object entity, SharedSessionContractImplementor session);
Object getIdentifier(Object entity);
/**
* Return the identifier of the persistent or transient object, or throw
* an exception if the instance is "unsaved"
* <p/>
* Used by OneToOneType and ManyToOneType to determine what id value should
* be used for an object that may or may not be associated with the session.
* This does a "best guess" using any/all info available to use (not just the
* EntityEntry).
*
* @param entity The entity instance
* @param session The session
*
* @return The identifier
*
* @throws TransientObjectException if the entity is transient (does not yet have an identifier)
* @see org.hibernate.engine.internal.ForeignKeys#getEntityIdentifierIfNotUnsaved(String, Object, SharedSessionContractImplementor)
* @since 6.1.1
*/
default Object getIdentifierIfNotUnsaved(Object entity, SharedSessionContractImplementor session) {
if ( entity == null ) {
return null;
}
else if ( session == null ) {
// If we have no session available, just return the identifier
return getIdentifier( entity );
}
Object id = session.getContextEntityIdentifier( entity );
if ( id == null ) {
// context-entity-identifier returns null explicitly if the entity
// is not associated with the persistence context; so make some
// deeper checks...
final String entityName = findContainingEntityMapping().getEntityName();
if ( ForeignKeys.isTransient( entityName, entity, Boolean.FALSE, session ) ) {
throw new TransientObjectException(
"object references an unsaved transient instance - save the transient instance before flushing: " +
(entityName == null ? session.guessEntityName( entity ) : entityName)
);
}
id = getIdentifier( entity );
}
return id;
}
void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session);

View File

@ -545,10 +545,10 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor {
}
// If the mapping type has an identifier type, that identifier is the key
if ( modelPart instanceof SingleAttributeIdentifierMapping ) {
return ( (SingleAttributeIdentifierMapping) modelPart ).getIdentifier( targetObject );
return ( (SingleAttributeIdentifierMapping) modelPart ).getIdentifierIfNotUnsaved( targetObject, session );
}
else if ( modelPart instanceof CompositeIdentifierMapping ) {
return ( (CompositeIdentifierMapping) modelPart ).getIdentifier( targetObject );
return ( (CompositeIdentifierMapping) modelPart ).getIdentifierIfNotUnsaved( targetObject, session );
}
// Otherwise, this is a key based on the target object i.e. without id-class
return targetObject;

View File

@ -18,6 +18,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.AssociationKey;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
@ -411,6 +412,9 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa
else {
modelPart = targetSide.getModelPart();
}
if ( modelPart instanceof EntityIdentifierMapping ) {
return ( (EntityIdentifierMapping) modelPart ).getIdentifierIfNotUnsaved( targetObject, session );
}
return ( (PropertyBasedMapping) modelPart ).getPropertyAccess().getGetter().get( targetObject );
}

View File

@ -355,7 +355,7 @@ public class SqmUtil {
final EntityIdentifierMapping identifierMapping = (EntityIdentifierMapping) parameterType;
final EntityMappingType entityMapping = identifierMapping.findContainingEntityMapping();
if ( entityMapping.getRepresentationStrategy().getInstantiator().isInstance( bindValue, session.getFactory() ) ) {
bindValue = identifierMapping.getIdentifier( bindValue );
bindValue = identifierMapping.getIdentifierIfNotUnsaved( bindValue, session );
}
}
else if ( parameterType instanceof EntityMappingType ) {
@ -363,7 +363,7 @@ public class SqmUtil {
final EntityMappingType entityMapping = identifierMapping.findContainingEntityMapping();
parameterType = identifierMapping;
if ( entityMapping.getRepresentationStrategy().getInstantiator().isInstance( bindValue, session.getFactory() ) ) {
bindValue = identifierMapping.getIdentifier( bindValue );
bindValue = identifierMapping.getIdentifierIfNotUnsaved( bindValue, session );
}
}
else if ( parameterType instanceof EntityAssociationMapping ) {

View File

@ -21,6 +21,7 @@ import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import org.hibernate.Session;
import org.hibernate.TransientObjectException;
import org.hibernate.orm.test.query.sqm.BaseSqmUnitTest;
import org.hibernate.query.Query;
import org.hibernate.query.SemanticException;
@ -30,12 +31,15 @@ import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.ExpectedException;
import org.hibernate.testing.orm.junit.ExpectedExceptionExtension;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hibernate.testing.hamcrest.CollectionMatchers.hasSize;
@ -177,6 +181,24 @@ public class ParameterTests extends BaseSqmUnitTest {
);
}
@Test
@TestForIssue( jiraKey = "HHH-15341")
public void testTransientParamValue() {
inTransaction(
session -> {
try {
session.createQuery( "from Person p where p.mate = :p" )
.setParameter( "p", new Person())
.list();
Assertions.fail( "Expected TransientObjectException" );
}
catch (IllegalStateException ex) {
assertThat( ex.getCause(), instanceOf( TransientObjectException.class ) );
}
}
);
}
@Override
protected boolean exportSchema() {
return true;