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;
|
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.IdentifierValue;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
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, SharedSessionContractImplementor session);
|
||||||
|
|
||||||
Object getIdentifier(Object entity);
|
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);
|
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 the mapping type has an identifier type, that identifier is the key
|
||||||
if ( modelPart instanceof SingleAttributeIdentifierMapping ) {
|
if ( modelPart instanceof SingleAttributeIdentifierMapping ) {
|
||||||
return ( (SingleAttributeIdentifierMapping) modelPart ).getIdentifier( targetObject );
|
return ( (SingleAttributeIdentifierMapping) modelPart ).getIdentifierIfNotUnsaved( targetObject, session );
|
||||||
}
|
}
|
||||||
else if ( modelPart instanceof CompositeIdentifierMapping ) {
|
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
|
// Otherwise, this is a key based on the target object i.e. without id-class
|
||||||
return targetObject;
|
return targetObject;
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.mapping.IndexedConsumer;
|
import org.hibernate.mapping.IndexedConsumer;
|
||||||
import org.hibernate.metamodel.mapping.AssociationKey;
|
import org.hibernate.metamodel.mapping.AssociationKey;
|
||||||
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
|
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
|
||||||
|
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
||||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||||
|
@ -411,6 +412,9 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa
|
||||||
else {
|
else {
|
||||||
modelPart = targetSide.getModelPart();
|
modelPart = targetSide.getModelPart();
|
||||||
}
|
}
|
||||||
|
if ( modelPart instanceof EntityIdentifierMapping ) {
|
||||||
|
return ( (EntityIdentifierMapping) modelPart ).getIdentifierIfNotUnsaved( targetObject, session );
|
||||||
|
}
|
||||||
return ( (PropertyBasedMapping) modelPart ).getPropertyAccess().getGetter().get( targetObject );
|
return ( (PropertyBasedMapping) modelPart ).getPropertyAccess().getGetter().get( targetObject );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -355,7 +355,7 @@ public class SqmUtil {
|
||||||
final EntityIdentifierMapping identifierMapping = (EntityIdentifierMapping) parameterType;
|
final EntityIdentifierMapping identifierMapping = (EntityIdentifierMapping) parameterType;
|
||||||
final EntityMappingType entityMapping = identifierMapping.findContainingEntityMapping();
|
final EntityMappingType entityMapping = identifierMapping.findContainingEntityMapping();
|
||||||
if ( entityMapping.getRepresentationStrategy().getInstantiator().isInstance( bindValue, session.getFactory() ) ) {
|
if ( entityMapping.getRepresentationStrategy().getInstantiator().isInstance( bindValue, session.getFactory() ) ) {
|
||||||
bindValue = identifierMapping.getIdentifier( bindValue );
|
bindValue = identifierMapping.getIdentifierIfNotUnsaved( bindValue, session );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ( parameterType instanceof EntityMappingType ) {
|
else if ( parameterType instanceof EntityMappingType ) {
|
||||||
|
@ -363,7 +363,7 @@ public class SqmUtil {
|
||||||
final EntityMappingType entityMapping = identifierMapping.findContainingEntityMapping();
|
final EntityMappingType entityMapping = identifierMapping.findContainingEntityMapping();
|
||||||
parameterType = identifierMapping;
|
parameterType = identifierMapping;
|
||||||
if ( entityMapping.getRepresentationStrategy().getInstantiator().isInstance( bindValue, session.getFactory() ) ) {
|
if ( entityMapping.getRepresentationStrategy().getInstantiator().isInstance( bindValue, session.getFactory() ) ) {
|
||||||
bindValue = identifierMapping.getIdentifier( bindValue );
|
bindValue = identifierMapping.getIdentifierIfNotUnsaved( bindValue, session );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ( parameterType instanceof EntityAssociationMapping ) {
|
else if ( parameterType instanceof EntityAssociationMapping ) {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import jakarta.persistence.Temporal;
|
||||||
import jakarta.persistence.TemporalType;
|
import jakarta.persistence.TemporalType;
|
||||||
|
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.TransientObjectException;
|
||||||
import org.hibernate.orm.test.query.sqm.BaseSqmUnitTest;
|
import org.hibernate.orm.test.query.sqm.BaseSqmUnitTest;
|
||||||
import org.hibernate.query.Query;
|
import org.hibernate.query.Query;
|
||||||
import org.hibernate.query.SemanticException;
|
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.expression.SqmParameter;
|
||||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
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.ExpectedException;
|
||||||
import org.hibernate.testing.orm.junit.ExpectedExceptionExtension;
|
import org.hibernate.testing.orm.junit.ExpectedExceptionExtension;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hibernate.testing.hamcrest.CollectionMatchers.hasSize;
|
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
|
@Override
|
||||||
protected boolean exportSchema() {
|
protected boolean exportSchema() {
|
||||||
return true;
|
return true;
|
||||||
|
|
Loading…
Reference in New Issue