Support for type coercion for values passed as ids and as query parameter bindings

- widening coercions
- valid (no over/under flow) narrowing coercions
- JpaCompliance setting
This commit is contained in:
Steve Ebersole 2021-04-30 13:59:20 -05:00
parent d95806b516
commit eb9bb2d82f
10 changed files with 170 additions and 19 deletions

View File

@ -2134,6 +2134,13 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings {
*/
String JPA_ID_GENERATOR_GLOBAL_SCOPE_COMPLIANCE = "hibernate.jpa.compliance.global_id_generators";
/**
* @see JpaCompliance#isLoadByIdComplianceEnabled()
*
* @since 6.0
*/
String JPA_LOAD_BY_ID_COMPLIANCE = "hibernate.jpa.compliance.load_by_id";
/**
* True/False setting indicating if the value stored in the table used by the {@link javax.persistence.TableGenerator}
* is the last value generated or the next value to be used.

View File

@ -20,8 +20,9 @@ public class JpaComplianceImpl implements JpaCompliance {
private final boolean transactionCompliance;
private final boolean closedCompliance;
private final boolean cachingCompliance;
private final boolean loadByIdCompliance;
private JpaComplianceImpl(
public JpaComplianceImpl(
boolean listCompliance,
boolean orderByMappingCompliance,
boolean proxyCompliance,
@ -29,7 +30,8 @@ public class JpaComplianceImpl implements JpaCompliance {
boolean queryCompliance,
boolean transactionCompliance,
boolean closedCompliance,
boolean cachingCompliance) {
boolean cachingCompliance,
boolean loadByIdCompliance) {
this.queryCompliance = queryCompliance;
this.transactionCompliance = transactionCompliance;
this.listCompliance = listCompliance;
@ -38,6 +40,7 @@ public class JpaComplianceImpl implements JpaCompliance {
this.cachingCompliance = cachingCompliance;
this.globalGeneratorNameScopeCompliance = globalGeneratorNameScopeCompliance;
this.orderByMappingCompliance = orderByMappingCompliance;
this.loadByIdCompliance = loadByIdCompliance;
}
@Override
@ -80,6 +83,11 @@ public class JpaComplianceImpl implements JpaCompliance {
return orderByMappingCompliance;
}
@Override
public boolean isLoadByIdComplianceEnabled() {
return loadByIdCompliance;
}
public static class JpaComplianceBuilder {
private boolean queryCompliance;
private boolean listCompliance;
@ -89,6 +97,7 @@ public class JpaComplianceImpl implements JpaCompliance {
private boolean cachingCompliance;
private boolean transactionCompliance;
private boolean closedCompliance;
private boolean loadByIdCompliance;
public JpaComplianceBuilder() {
}
@ -133,6 +142,11 @@ public class JpaComplianceImpl implements JpaCompliance {
return this;
}
public JpaComplianceBuilder setLoadByIdCompliance(boolean loadByIdCompliance) {
this.loadByIdCompliance = loadByIdCompliance;
return this;
}
JpaCompliance createJpaCompliance() {
return new JpaComplianceImpl(
listCompliance,
@ -142,7 +156,8 @@ public class JpaComplianceImpl implements JpaCompliance {
queryCompliance,
transactionCompliance,
closedCompliance,
cachingCompliance
cachingCompliance,
loadByIdCompliance
);
}
}

View File

@ -25,6 +25,7 @@ public class MutableJpaComplianceImpl implements MutableJpaCompliance {
private boolean transactionCompliance;
private boolean closedCompliance;
private boolean cachingCompliance;
private boolean loadByIdCompliance;
@SuppressWarnings("ConstantConditions")
public MutableJpaComplianceImpl(Map configurationSettings, boolean jpaByDefault) {
@ -74,6 +75,12 @@ public class MutableJpaComplianceImpl implements MutableJpaCompliance {
configurationSettings,
jpaByDefault
);
loadByIdCompliance = ConfigurationHelper.getBoolean(
AvailableSettings.JPA_LOAD_BY_ID_COMPLIANCE,
configurationSettings,
jpaByDefault
);
}
@Override
@ -158,7 +165,15 @@ public class MutableJpaComplianceImpl implements MutableJpaCompliance {
this.cachingCompliance = cachingCompliance;
}
@Override
public void setLoadByIdCompliance(boolean enabled) {
this.loadByIdCompliance = enabled;
}
@Override
public boolean isLoadByIdComplianceEnabled() {
return loadByIdCompliance;
}
@Override
public JpaCompliance immutableCopy() {
@ -170,7 +185,8 @@ public class MutableJpaComplianceImpl implements MutableJpaCompliance {
.setQueryCompliance( queryCompliance )
.setTransactionCompliance( transactionCompliance )
.setClosedCompliance( closedCompliance )
.setCachingCompliance( cachingCompliance );
.setCachingCompliance( cachingCompliance )
.setLoadByIdCompliance( loadByIdCompliance );
return builder.createJpaCompliance();
}
}

View File

@ -105,4 +105,18 @@ public interface JpaCompliance {
* this enabled, Hibernate will throw a compliance error when a non-attribute-reference is used.
*/
boolean isJpaOrderByMappingComplianceEnabled();
/**
* JPA says that the id passed to {@link javax.persistence.EntityManager#getReference} and
* {@link javax.persistence.EntityManager#find} should be the exact expected type, allowing
* no type coercion.
*
* Historically, Hibernate behaved the same way. Since 6.0 however, Hibernate has the ability to
* coerce the passed type to the expected type.
*
* This setting controls whether such a coercion should be allowed.
*
* @since 6.0
*/
boolean isLoadByIdComplianceEnabled();
}

View File

@ -26,5 +26,7 @@ public interface MutableJpaCompliance extends JpaCompliance {
void setGeneratorNameScopeCompliance(boolean generatorScopeCompliance);
void setLoadByIdCompliance(boolean enabled);
JpaCompliance immutableCopy();
}

View File

@ -24,6 +24,7 @@ import org.hibernate.event.spi.LoadEventListener;
import org.hibernate.graph.GraphSemantic;
import org.hibernate.graph.RootGraph;
import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.jpa.spi.JpaCompliance;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
@ -116,7 +117,10 @@ public class IdentifierLoadAccessImpl<T> implements IdentifierLoadAccess<T>, Jav
final EventSource eventSource = (EventSource) session;
final LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers();
id = entityPersister.getIdentifierMapping().getJavaTypeDescriptor().coerce( id, this );
final JpaCompliance jpaCompliance = session.getFactory().getSessionFactoryOptions().getJpaCompliance();
if ( ! jpaCompliance.isLoadByIdComplianceEnabled() ) {
id = entityPersister.getIdentifierMapping().getJavaTypeDescriptor().coerce( id, this );
}
if ( this.lockOptions != null ) {
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, eventSource, loadQueryInfluencers.getReadOnly() );
@ -158,7 +162,10 @@ public class IdentifierLoadAccessImpl<T> implements IdentifierLoadAccess<T>, Jav
final EventSource eventSource = (EventSource) session;
final LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers();
id = entityPersister.getIdentifierMapping().getJavaTypeDescriptor().coerce( id, this );
final JpaCompliance jpaCompliance = session.getFactory().getSessionFactoryOptions().getJpaCompliance();
if ( ! jpaCompliance.isLoadByIdComplianceEnabled() ) {
id = entityPersister.getIdentifierMapping().getJavaTypeDescriptor().coerce( id, this );
}
if ( this.lockOptions != null ) {
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, eventSource, loadQueryInfluencers.getReadOnly() );

View File

@ -53,4 +53,9 @@ public class JpaComplianceStub implements JpaCompliance {
public boolean isJpaOrderByMappingComplianceEnabled() {
return false;
}
@Override
public boolean isLoadByIdComplianceEnabled() {
return false;
}
}

View File

@ -8,7 +8,6 @@ package org.hibernate.orm.test.jpa.emops;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.fail;
@ -17,11 +16,14 @@ import static org.junit.jupiter.api.Assertions.fail;
* @author Emmanuel Bernard
*/
@Jpa(annotatedClasses = {
Competitor.class,
Race.class,
Mail.class
})
@Jpa(
annotatedClasses = {
Competitor.class,
Race.class,
Mail.class
},
loadByIdComplianceEnabled = true
)
public class GetReferenceTest {
@Test
public void testWrongIdType(EntityManagerFactoryScope scope) {
@ -51,4 +53,32 @@ public class GetReferenceTest {
}
);
}
@Test
public void testWrongIdTypeFind(EntityManagerFactoryScope scope) {
scope.inEntityManager(
entityManager -> {
try {
entityManager.find( Competitor.class, "30" );
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException e) {
//success
}
catch ( Exception e ) {
fail("Wrong exception: " + e );
}
try {
entityManager.find( Mail.class, 1 );
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException e) {
//success
}
catch ( Exception e ) {
fail("Wrong exception: " + e );
}
}
);
}
}

View File

@ -48,8 +48,9 @@ import org.jboss.logging.Logger;
* including argument injection (or see {@link SessionFactoryScopeAware})
*
* @author Steve Ebersole
* @see SessionFactoryScope
*
* @see DomainModelExtension
* @see SessionFactoryExtension
*/
public class EntityManagerFactoryExtension
implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler {
@ -79,8 +80,7 @@ public class EntityManagerFactoryExtension
context.getElement().get(),
Jpa.class
);
final Jpa emfAnn = emfAnnWrapper.orElseThrow( () -> new RuntimeException(
"Could not locate @EntityManagerFactory" ) );
final Jpa emfAnn = emfAnnWrapper.orElseThrow( () -> new RuntimeException( "Could not locate @EntityManagerFactory" ) );
final PersistenceUnitInfoImpl pui = new PersistenceUnitInfoImpl( emfAnn.persistenceUnitName() );
@ -89,6 +89,17 @@ public class EntityManagerFactoryExtension
pui.setValidationMode( emfAnn.validationMode() );
pui.setExcludeUnlistedClasses( emfAnn.excludeUnlistedClasses() );
// JpaCompliance
pui.getProperties().put( AvailableSettings.JPA_QUERY_COMPLIANCE, emfAnn.queryComplianceEnabled() );
pui.getProperties().put( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, emfAnn.transactionComplianceEnabled() );
pui.getProperties().put( AvailableSettings.JPA_LIST_COMPLIANCE, emfAnn.listMappingComplianceEnabled() );
pui.getProperties().put( AvailableSettings.JPA_CLOSED_COMPLIANCE, emfAnn.closedComplianceEnabled() );
pui.getProperties().put( AvailableSettings.JPA_PROXY_COMPLIANCE, emfAnn.proxyComplianceEnabled() );
pui.getProperties().put( AvailableSettings.JPA_CACHING_COMPLIANCE, emfAnn.cacheComplianceEnabled() );
pui.getProperties().put( AvailableSettings.JPA_ID_GENERATOR_GLOBAL_SCOPE_COMPLIANCE, emfAnn.generatorScopeComplianceEnabled() );
pui.getProperties().put( AvailableSettings.JPA_ORDER_BY_MAPPING_COMPLIANCE, emfAnn.orderByMappingComplianceEnabled() );
pui.getProperties().put( AvailableSettings.JPA_LOAD_BY_ID_COMPLIANCE, emfAnn.loadByIdComplianceEnabled() );
final Setting[] properties = emfAnn.properties();
for ( int i = 0; i < properties.length; i++ ) {
final Setting property = properties[i];
@ -301,8 +312,6 @@ public class EntityManagerFactoryExtension
}
protected javax.persistence.EntityManagerFactory createEntityManagerFactory() {
final EntityManagerFactoryBuilder emfBuilder = Bootstrap.getEntityManagerFactoryBuilder(
new PersistenceUnitInfoDescriptor( persistenceUnitInfo ),
integrationSettings

View File

@ -16,6 +16,8 @@ import javax.persistence.SharedCacheMode;
import javax.persistence.ValidationMode;
import javax.persistence.spi.PersistenceUnitTransactionType;
import org.hibernate.jpa.spi.JpaCompliance;
import org.hibernate.testing.orm.domain.DomainModelDescriptor;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.jpa.NonStringValueSettingProvider;
@ -38,6 +40,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith( FailureExpectedExtension.class )
public @interface Jpa {
String persistenceUnitName() default "test-pu";
/**
* Used to mimic container integration
@ -46,8 +49,6 @@ public @interface Jpa {
Class<? extends NonStringValueSettingProvider>[] nonStringValueSettingProviders() default {};
String persistenceUnitName() default "test-pu";
// todo : multiple persistence units?
/**
@ -62,6 +63,51 @@ public @interface Jpa {
SharedCacheMode sharedCacheMode() default SharedCacheMode.UNSPECIFIED;
ValidationMode validationMode() default ValidationMode.NONE;
/**
* @see JpaCompliance#isJpaQueryComplianceEnabled()
*/
boolean queryComplianceEnabled() default false;
/**
* @see JpaCompliance#isJpaTransactionComplianceEnabled()
*/
boolean transactionComplianceEnabled() default false;
/**
* @see JpaCompliance#isJpaClosedComplianceEnabled()
*/
boolean closedComplianceEnabled() default false;
/**
* @see JpaCompliance#isJpaListComplianceEnabled()
*/
boolean listMappingComplianceEnabled() default false;
/**
* @see JpaCompliance#isJpaOrderByMappingComplianceEnabled()
*/
boolean orderByMappingComplianceEnabled() default false;
/**
* @see JpaCompliance#isJpaProxyComplianceEnabled()
*/
boolean proxyComplianceEnabled() default false;
/**
* @see JpaCompliance#isJpaCacheComplianceEnabled()
*/
boolean cacheComplianceEnabled() default false;
/**
* @see JpaCompliance#isGlobalGeneratorScopeEnabled()
*/
boolean generatorScopeComplianceEnabled() default false;
/**
* @see JpaCompliance#isLoadByIdComplianceEnabled()
*/
boolean loadByIdComplianceEnabled() default false;
boolean excludeUnlistedClasses() default false;
StandardDomainModel[] standardModels() default {};