HHH-15341 Disallow passing transient entity parameter values
This commit is contained in:
parent
04fd92b204
commit
3cf6f2e3ef
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue