HHH-18271 Avoid mega-morphic callsites for equals/hashCode with known types
This commit is contained in:
parent
850a2a0753
commit
55702e458b
|
@ -26,7 +26,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
|||
public final class CollectionKey implements Serializable {
|
||||
private final String role;
|
||||
private final Object key;
|
||||
private final Type keyType;
|
||||
private final @Nullable Type keyType;
|
||||
private final SessionFactoryImplementor factory;
|
||||
private final int hashCode;
|
||||
|
||||
|
@ -34,7 +34,7 @@ public final class CollectionKey implements Serializable {
|
|||
this(
|
||||
persister.getRole(),
|
||||
key,
|
||||
persister.getKeyType(),
|
||||
persister.getKeyType().getTypeForEqualsHashCode(),
|
||||
persister.getFactory()
|
||||
);
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ public final class CollectionKey implements Serializable {
|
|||
private CollectionKey(
|
||||
String role,
|
||||
@Nullable Object key,
|
||||
Type keyType,
|
||||
@Nullable Type keyType,
|
||||
SessionFactoryImplementor factory) {
|
||||
this.role = role;
|
||||
if ( key == null ) {
|
||||
|
@ -58,7 +58,7 @@ public final class CollectionKey implements Serializable {
|
|||
private int generateHashCode() {
|
||||
int result = 17;
|
||||
result = 37 * result + role.hashCode();
|
||||
result = 37 * result + keyType.getHashCode( key, factory );
|
||||
result = 37 * result + ( keyType == null ? key.hashCode() : keyType.getHashCode( key, factory ) );
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,7 @@ public final class CollectionKey implements Serializable {
|
|||
final CollectionKey that = (CollectionKey) other;
|
||||
return that.role.equals( role )
|
||||
&& ( this.key == that.key ||
|
||||
keyType.isEqual( this.key, that.key, factory ) );
|
||||
keyType == null ? this.key.equals( that.key ) : keyType.isEqual( this.key, that.key, factory ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -14,6 +14,7 @@ import java.io.Serializable;
|
|||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.pretty.MessageHelper;
|
||||
import org.hibernate.type.Type;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
|
@ -59,7 +60,8 @@ public final class EntityKey implements Serializable {
|
|||
int result = 17;
|
||||
final String rootEntityName = persister.getRootEntityName();
|
||||
result = 37 * result + rootEntityName.hashCode();
|
||||
result = 37 * result + persister.getIdentifierType().getHashCode( identifier, persister.getFactory() );
|
||||
final Type identifierType = persister.getIdentifierType().getTypeForEqualsHashCode();
|
||||
result = 37 * result + ( identifierType == null ? identifier.hashCode() : identifierType.getHashCode( identifier, persister.getFactory() ) );
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -99,8 +101,10 @@ public final class EntityKey implements Serializable {
|
|||
}
|
||||
|
||||
private boolean sameIdentifier(final EntityKey otherKey) {
|
||||
return this.identifier == otherKey.identifier ||
|
||||
persister.getIdentifierType().isEqual( otherKey.identifier, this.identifier, persister.getFactory() );
|
||||
final Type identifierType;
|
||||
return this.identifier == otherKey.identifier || (
|
||||
(identifierType = persister.getIdentifierType().getTypeForEqualsHashCode()) == null && identifier.equals( otherKey.identifier )
|
||||
|| identifierType != null && identifierType.isEqual( otherKey.identifier, this.identifier, persister.getFactory() ) );
|
||||
}
|
||||
|
||||
private boolean samePersistentType(final EntityKey otherKey) {
|
||||
|
|
|
@ -27,13 +27,14 @@ import org.hibernate.query.sqm.CastType;
|
|||
import org.hibernate.type.descriptor.ValueBinder;
|
||||
import org.hibernate.type.descriptor.ValueExtractor;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
import org.hibernate.type.descriptor.java.AbstractClassJavaType;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.java.MutabilityPlan;
|
||||
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Convenience base class for {@link BasicType} implementations.
|
||||
* Packages a {@link JavaType} with a {@link JdbcType}.
|
||||
|
@ -50,7 +51,7 @@ public abstract class AbstractStandardBasicType<T>
|
|||
private final ValueBinder<T> jdbcValueBinder;
|
||||
private final ValueExtractor<T> jdbcValueExtractor;
|
||||
private final JdbcLiteralFormatter<T> jdbcLiteralFormatter;
|
||||
private final AbstractClassJavaType<T> javaTypeAsAbstractClassJavaType;
|
||||
private final @Nullable Type typeForEqualsHashCode;
|
||||
private final Class javaTypeClass;
|
||||
private final MutabilityPlan<T> mutabilityPlan;
|
||||
private final Comparator<T> javatypeComparator;
|
||||
|
@ -68,13 +69,7 @@ public abstract class AbstractStandardBasicType<T>
|
|||
this.javaTypeClass = javaType.getJavaTypeClass();
|
||||
this.mutabilityPlan = javaType.getMutabilityPlan();
|
||||
this.javatypeComparator = javaType.getComparator();
|
||||
//This is a dispatch optimisation to avoid megamorphic invocations on the most common type:
|
||||
if ( javaType instanceof AbstractClassJavaType ) {
|
||||
this.javaTypeAsAbstractClassJavaType = (AbstractClassJavaType) javaType;
|
||||
}
|
||||
else {
|
||||
this.javaTypeAsAbstractClassJavaType = null;
|
||||
}
|
||||
this.typeForEqualsHashCode = javaType.useObjectEqualsHashCode() ? null : this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -98,14 +93,8 @@ public abstract class AbstractStandardBasicType<T>
|
|||
}
|
||||
|
||||
public T fromString(CharSequence string) {
|
||||
final AbstractClassJavaType<T> type = this.javaTypeAsAbstractClassJavaType;
|
||||
if ( type != null ) {
|
||||
return type.fromString( string );
|
||||
}
|
||||
else {
|
||||
return javaType.fromString( string );
|
||||
}
|
||||
}
|
||||
|
||||
protected MutabilityPlan<T> getMutabilityPlan() {
|
||||
return this.mutabilityPlan;
|
||||
|
@ -196,25 +185,19 @@ public abstract class AbstractStandardBasicType<T>
|
|||
else if ( one == null || another == null ) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
final AbstractClassJavaType<T> type = this.javaTypeAsAbstractClassJavaType;
|
||||
if ( type != null ) {
|
||||
//Optimize for the most common case: avoid the megamorphic call
|
||||
return type.areEqual( (T) one, (T) another );
|
||||
else if ( typeForEqualsHashCode == null ) {
|
||||
return one.equals( another );
|
||||
}
|
||||
else {
|
||||
return javaType.areEqual( (T) one, (T) another );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public int getHashCode(Object x) {
|
||||
final AbstractClassJavaType<T> type = this.javaTypeAsAbstractClassJavaType;
|
||||
if ( type != null ) {
|
||||
//Optimize for the most common case: avoid the megamorphic call
|
||||
return type.extractHashCode( (T) x );
|
||||
if ( typeForEqualsHashCode == null ) {
|
||||
return x.hashCode();
|
||||
}
|
||||
else {
|
||||
return javaType.extractHashCode( (T) x );
|
||||
|
@ -226,6 +209,11 @@ public abstract class AbstractStandardBasicType<T>
|
|||
return getHashCode( x );
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Type getTypeForEqualsHashCode() {
|
||||
return typeForEqualsHashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public final int compare(Object x, Object y) {
|
||||
|
|
|
@ -219,6 +219,15 @@ public interface Type extends Serializable {
|
|||
*/
|
||||
int getHashCode(Object x, SessionFactoryImplementor factory) throws HibernateException;
|
||||
|
||||
/**
|
||||
* The type to use for {@code equals()} and {@code hashCode()} computation.
|
||||
* When {@code null}, use {@link Object#equals(Object)} and {@link Object#hashCode()}.
|
||||
* This is useful to avoid mega-morphic callsites.
|
||||
*/
|
||||
default @Nullable Type getTypeForEqualsHashCode() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a {@link java.util.Comparator}-style comparison of the given values.
|
||||
*
|
||||
|
|
|
@ -47,6 +47,11 @@ public class BooleanJavaType extends AbstractClassJavaType<Boolean> implements
|
|||
stringValueFalse = String.valueOf( characterValueFalse );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Boolean value) {
|
||||
return value == null ? null : value.toString();
|
||||
|
|
|
@ -31,6 +31,11 @@ public class ByteJavaType extends AbstractClassJavaType<Byte>
|
|||
super( Byte.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Byte value) {
|
||||
return value == null ? null : value.toString();
|
||||
|
|
|
@ -25,6 +25,11 @@ public class CharacterJavaType extends AbstractClassJavaType<Character> implemen
|
|||
super( Character.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Character value) {
|
||||
return value.toString();
|
||||
|
|
|
@ -22,6 +22,11 @@ public class ClassJavaType extends AbstractClassJavaType<Class> {
|
|||
super( Class.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString(Class value) {
|
||||
return value.getName();
|
||||
}
|
||||
|
|
|
@ -24,6 +24,11 @@ public class CurrencyJavaType extends AbstractClassJavaType<Currency> {
|
|||
super( Currency.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Currency value) {
|
||||
return value.getCurrencyCode();
|
||||
|
|
|
@ -35,6 +35,11 @@ public class DoubleJavaType extends AbstractClassJavaType<Double> implements
|
|||
return DoubleJdbcType.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Double value) {
|
||||
return value == null ? null : value.toString();
|
||||
|
|
|
@ -52,6 +52,11 @@ public class DurationJavaType extends AbstractClassJavaType<Duration> {
|
|||
.getDescriptor( context.getPreferredSqlTypeCodeForDuration() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Duration value) {
|
||||
if ( value == null ) {
|
||||
|
|
|
@ -81,6 +81,11 @@ public class EnumJavaType<T extends Enum<T>> extends AbstractClassJavaType<T> {
|
|||
return getJavaTypeClass().getEnumConstants().length > 128;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(T value) {
|
||||
return value == null ? "<null>" : value.name();
|
||||
|
|
|
@ -34,6 +34,11 @@ public class FloatJavaType extends AbstractClassJavaType<Float> implements Primi
|
|||
return indicators.getJdbcType( SqlTypes.FLOAT );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Float value) {
|
||||
return value == null ? null : value.toString();
|
||||
|
|
|
@ -28,6 +28,11 @@ public class InetAddressJavaType extends AbstractClassJavaType<InetAddress> {
|
|||
super( InetAddress.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(InetAddress value) {
|
||||
return value == null ? null : value.toString();
|
||||
|
|
|
@ -69,6 +69,11 @@ public class InstantJavaType extends AbstractTemporalJavaType<Instant>
|
|||
return context.getJdbcType( context.getPreferredSqlTypeCodeForInstant() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Instant value) {
|
||||
return DateTimeFormatter.ISO_INSTANT.format( value );
|
||||
|
|
|
@ -31,6 +31,11 @@ public class IntegerJavaType extends AbstractClassJavaType<Integer>
|
|||
super( Integer.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Integer value) {
|
||||
return value == null ? null : value.toString();
|
||||
|
|
|
@ -223,6 +223,16 @@ public interface JavaType<T> extends Serializable {
|
|||
return Objects.deepEquals( one, another );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use {@link Object#equals(Object)} and {@link Object#hashCode()}
|
||||
* or {@link #areEqual(Object, Object)} and {@link #extractHashCode(Object)}
|
||||
* for objects of this java type.
|
||||
* This is useful to avoid mega-morphic callsites.
|
||||
*/
|
||||
default boolean useObjectEqualsHashCode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a loggable representation of the given value.
|
||||
*
|
||||
|
|
|
@ -60,6 +60,11 @@ public class LocalDateJavaType extends AbstractTemporalJavaType<LocalDate> {
|
|||
return (TemporalJavaType<X>) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(LocalDate value) {
|
||||
return DateTimeFormatter.ISO_LOCAL_DATE.format( value );
|
||||
|
|
|
@ -60,6 +60,11 @@ public class LocalDateTimeJavaType extends AbstractTemporalJavaType<LocalDateTim
|
|||
return (TemporalJavaType<X>) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(LocalDateTime value) {
|
||||
return DateTimeFormatter.ISO_DATE_TIME.format( value );
|
||||
|
|
|
@ -65,6 +65,11 @@ public class LocalTimeJavaType extends AbstractTemporalJavaType<LocalTime> {
|
|||
return (TemporalJavaType<X>) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(LocalTime value) {
|
||||
return DateTimeFormatter.ISO_LOCAL_TIME.format( value );
|
||||
|
|
|
@ -31,6 +31,11 @@ public class LocaleJavaType extends AbstractClassJavaType<Locale> {
|
|||
super( Locale.class, ImmutableMutabilityPlan.instance(), LocaleComparator.INSTANCE );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString(Locale value) {
|
||||
return value.toString();
|
||||
}
|
||||
|
|
|
@ -31,6 +31,11 @@ public class LongJavaType extends AbstractClassJavaType<Long>
|
|||
super( Long.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Long value) {
|
||||
return value == null ? null : value.toString();
|
||||
|
|
|
@ -21,6 +21,11 @@ public class ObjectJavaType extends AbstractClassJavaType<Object> {
|
|||
super( Object.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> X unwrap(Object value, Class<X> type, WrapperOptions options) {
|
||||
//noinspection unchecked
|
||||
|
|
|
@ -81,6 +81,11 @@ public class OffsetDateTimeJavaType extends AbstractTemporalJavaType<OffsetDateT
|
|||
return (TemporalJavaType<X>) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(OffsetDateTime value) {
|
||||
return ISO_OFFSET_DATE_TIME.format( value );
|
||||
|
|
|
@ -64,6 +64,11 @@ public class OffsetTimeJavaType extends AbstractTemporalJavaType<OffsetTime> {
|
|||
return (TemporalJavaType<X>) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(OffsetTime value) {
|
||||
return DateTimeFormatter.ISO_OFFSET_TIME.format( value );
|
||||
|
|
|
@ -30,6 +30,11 @@ public class ShortJavaType extends AbstractClassJavaType<Short>
|
|||
super( Short.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Short value) {
|
||||
return value == null ? null : value.toString();
|
||||
|
|
|
@ -32,6 +32,11 @@ public class StringJavaType extends AbstractClassJavaType<String> {
|
|||
super( String.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString(String value) {
|
||||
return value;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,11 @@ public class TimeZoneJavaType extends AbstractClassJavaType<TimeZone> {
|
|||
super( TimeZone.class, ImmutableMutabilityPlan.instance(), TimeZoneComparator.INSTANCE );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString(TimeZone value) {
|
||||
return value.getID();
|
||||
}
|
||||
|
|
|
@ -34,6 +34,11 @@ public class UUIDJavaType extends AbstractClassJavaType<UUID> {
|
|||
return context.getJdbcType( context.getPreferredSqlTypeCodeForUuid() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString(UUID value) {
|
||||
return ToStringTransformer.INSTANCE.transform( value );
|
||||
}
|
||||
|
|
|
@ -32,6 +32,11 @@ public class UrlJavaType extends AbstractClassJavaType<URL> {
|
|||
return context.getJdbcType( SqlTypes.VARCHAR );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString(URL value) {
|
||||
return value.toExternalForm();
|
||||
}
|
||||
|
|
|
@ -30,6 +30,11 @@ public class YearJavaType extends AbstractClassJavaType<Year> {
|
|||
return context.getJdbcType( Types.INTEGER );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Year value) {
|
||||
return value == null ? null : value.format( FORMATTER );
|
||||
|
|
|
@ -31,6 +31,11 @@ public class ZoneIdJavaType extends AbstractClassJavaType<ZoneId> {
|
|||
return indicators.getJdbcType( Types.VARCHAR );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(ZoneId value) {
|
||||
return value == null ? null : value.getId();
|
||||
|
|
|
@ -34,6 +34,11 @@ public class ZoneOffsetJavaType extends AbstractClassJavaType<ZoneOffset> {
|
|||
super( ZoneOffset.class, ImmutableMutabilityPlan.instance(), ZoneOffsetComparator.INSTANCE );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString(ZoneOffset value) {
|
||||
return value.getId();
|
||||
}
|
||||
|
|
|
@ -62,6 +62,11 @@ public class ZonedDateTimeJavaType extends AbstractTemporalJavaType<ZonedDateTim
|
|||
return (TemporalJavaType<X>) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(ZonedDateTime value) {
|
||||
return ISO_ZONED_DATE_TIME.format( value );
|
||||
|
|
|
@ -34,6 +34,11 @@ public class JavaTypeBasicAdaptor<T> extends AbstractClassJavaType<T> {
|
|||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useObjectEqualsHashCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(T value) {
|
||||
return value.toString();
|
||||
|
|
Loading…
Reference in New Issue