HHH-14856 - Introduce @CustomType;

HHH-14865 - Re-work @Any and @ManyToAny support;
HHH-14863 - Compositional definition of basic value mappings;
HHH-14864 - Drop legacy Type-based annotations

* documentation work
* support for using most of the new mapping annotations related to basic and any mappings as meta-annotations
* support for `@Mutability` on AttributeConverter implementations
* additional tests in the User Guide's BitSet mapping Case Study
This commit is contained in:
Steve Ebersole 2021-10-07 16:46:35 -05:00
parent abc8225e9d
commit ede52e9aba
42 changed files with 965 additions and 269 deletions

View File

@ -201,7 +201,7 @@ First, we need to extend the `AbstractSingleColumnStandardBasicType` like this:
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetType.java[tags=basic-custom-type-BitSetType-example]
include::{sourcedir}/basic/bitset/BitSetType.java[tags=basic-custom-type-BitSetType-example]
----
====
@ -214,7 +214,7 @@ On the Java side, we need to use a `BitSetJavaType` instance which can be implem
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetTypeDescriptor.java[tags=basic-custom-type-BitSetTypeDescriptor-example]
include::{sourcedir}/basic/bitset/BitSetTypeDescriptor.java[tags=basic-custom-type-BitSetTypeDescriptor-example]
----
====
@ -227,7 +227,7 @@ The `BasicType` must be registered, and this can be done at bootstrapping time:
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetTypeTest.java[tags=basic-custom-type-register-BasicType-example]
include::{sourcedir}/basic/bitset/BitSetTypeTest.java[tags=basic-custom-type-register-BasicType-example]
----
or using the `MetadataBuilder`
@ -245,7 +245,7 @@ With the new `BitSetType` being registered as `bitset`, the entity mapping looks
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetTypeTest.java[tags=basic-custom-type-BitSetType-mapping-example]
include::{sourcedir}/basic/bitset/BitSetTypeTest.java[tags=basic-custom-type-BitSetType-mapping-example]
----
====
@ -256,7 +256,7 @@ Alternatively, you can use the `@TypeDef` and skip the registration phase:
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetTypeDefTest.java[tags=basic-custom-type-BitSetTypeDef-mapping-example]
include::{sourcedir}/basic/bitset/BitSetTypeDefTest.java[tags=basic-custom-type-BitSetTypeDef-mapping-example]
----
====
@ -267,7 +267,7 @@ To validate this new `BasicType` implementation, we can test it as follows:
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetTypeTest.java[tags=basic-custom-type-BitSetType-persistence-example]
include::{sourcedir}/basic/bitset/BitSetTypeTest.java[tags=basic-custom-type-BitSetType-persistence-example]
----
====
@ -335,7 +335,7 @@ Without registering a name, the `UserType` mapping requires the fully qualified
[source, JAVA, indent=0]
----
@Type( type = "org.hibernate.userguide.mapping.basic.BitSetUserType" )
@Type( type = "org.hibernate.userguide.mapping.basic.bitset.BitSetUserType" )
----
====

View File

@ -501,12 +501,51 @@ a metadata information for the associated entity type.
This is not the usual way of mapping polymorphic associations and you should use this only in special cases (e.g. audit logs, user session data, etc).
====
The `@Any` annotation describes the column holding the metadata information.
To link the value of the metadata information and an actual entity type, the `@AnyDef` and `@AnyDefs` annotations are used.
The `metaType` attribute allows the application to specify a custom type that maps database column values to persistent classes that have identifier properties of the type specified by `idType`.
You must specify the mapping from values of the `metaType` to class names.
To map such an association, Hibernate needs to understand 3 things:
For the next examples, consider the following `Property` class hierarchy:
1. The column and mapping for the discriminator
2. The column and mapping for the key
3. The mapping between discriminator values and entity classes
[[associations-any-discriminator]]
===== The discriminator
The discriminator of an any-style association holds the value that indicates which entity is
referred to by a row.
Its "column" can be specified with either `@Column` or `@Formula`. The mapping type can be influenced by any of:
1. `@AnyDiscriminator` allows re-using the `DiscriminatorType` simplified mappings from Jakarta Persistence for the common cases
2. `@JavaType`
3. `@JdbcType`
4. `@JdbcTypeCode`
[[associations-any-key]]
===== The key
The key of an any-style association holds the matching key for the row
Its "column" can be specified with either `@JoinColumn` (`@JoinFormula` not supported). The mapping
type can be influenced by any of:
1. `@AnyKeyJavaClass`
2. `@AnyKeyJavaType`
3. `@AnyKeyJdbcType`
4. `@AnyKeyJdbcTypeCode`
[[associations-any-values]]
===== The discriminator value mappings
`@AnyDiscriminatorValue` is used to map the discriminator values to the corresponding entity classes
[[associations-any-property]]
==== Example using @Any mapping
For this example, consider the following `Property` class hierarchy:
[[associations-any-property-example]]
.`Property` class hierarchy
@ -521,7 +560,7 @@ include::{sourcedir}/any/StringProperty.java[tags=associations-any-property-exam
----
====
A `PropertyHolder` can reference any such property, and, because each `Property` belongs to a separate table, the `@Any` annotation is, therefore, required.
A `PropertyHolder` entity defines an attribute of type `Property`:
[[associations-any-example]]
.`@Any` mapping usage
@ -537,28 +576,13 @@ include::{extrasdir}/associations-any-example.sql[]
----
====
`PropertyHolder#property` can refer to either `StringProperty` or `IntegerProperty` references, as indicated
by the associated discriminator according to the `@DiscriminatorValue` annotations.
As you can see, there are two columns used to reference a `Property` instance: `property_id` and `property_type`.
The `property_id` is used to match the `id` column of either the `string_property` or `integer_property` tables,
while the `property_type` is used to match the `string_property` or the `integer_property` table.
The table resolving mapping is defined by the `metaDef` attribute which references an `@AnyMetaDef` mapping.
The `package-info.java` contains the `@AnyMetaDef` mapping:
[[associations-any-meta-def-example]]
.`@AnyMetaDef` mapping usage
====
[source, JAVA, indent=0]
----
include::{sourcedir}/any/package-info.java[tags=associations-any-meta-def-example]
----
====
[NOTE]
====
Although the `@AnyMetaDef` mapping could be set right next to the `@Any` annotation, it is good practice to configure it at the class or package level, especially if you need to reuse it for multiple `@Any` mappings.
====
To see the `@Any` annotation in action, consider the next examples.
If we persist an `IntegerProperty` as well as a `StringProperty` entity, and associate
@ -596,12 +620,45 @@ include::{extrasdir}/associations-any-query-example.sql[]
----
====
[[associations-any-meta-annotations]]
===== Using meta-annotations
As mentioned in <<basic-mapping>>, Hibernate's ANY-related annotations can be composed using meta-annotations
to re-use ANY mapping details.
Looking back at <<associations-any-example>>, we can see how cumbersome it would be to duplicate that
information every time `Property` is mapped in the domain model. This description can also be moved
into a single annotation that we can apply in each usage.
[[associations-any-composed-example]]
.`@Any` mapping usage
====
[source, JAVA, indent=0]
----
include::{sourcedir}/any/PropertyHolder2.java[tags=associations-any-def-example]
----
====
Though the mapping has been "simplified", the mapping works exactly as shown in <<associations-any-example>>.
[[associations-many-to-any]]
===== `@ManyToAny` mapping
While the `@Any` mapping is useful to emulate a `@ManyToOne` association when there can be multiple target entities,
to emulate a `@OneToMany` association, the `@ManyToAny` annotation must be used.
The mapping details are the same between `@Any` and `@ManyToAny` except for:
1. The use of `@ManyToAny` instead of `@Any`
2. The use of `@JoinTable`, `@JoinTable#joinColumns` and `@JoinTable#inverseJoinColumns` instead
of just `@JoinColumn`
In the following example, the `PropertyRepository` entity has a collection of `Property` entities.
The `repository_properties` link table holds the associations between `PropertyRepository` and `Property` entities.
@ -657,6 +714,8 @@ include::{extrasdir}/associations-many-to-any-query-example.sql[]
----
====
[[associations-JoinFormula]]
==== `@JoinFormula` mapping

View File

@ -63,7 +63,7 @@ Generally, the `@Basic` annotation can be ignored as it is assumed by default.
examples are ultimately the same.
[[basic-annotation-explicit-example]]
.`@Basic` declared explicitly
.`@Basic` explicit
====
[source, JAVA, indent=0]
----
@ -163,7 +163,7 @@ include::{extrasdir}/basic/mapping-column-formula-persistence-example.sql[]
[NOTE]
====
The SQL fragment defined by the `@Formula` annotation can be as complex as you want, and it can even include subselects.
The SQL fragment defined by the `@Formula` annotation can be as complex as you want, and it can even include sub-selects.
====
@ -206,6 +206,12 @@ This includes removal of the following deprecated legacy annotations:
See the 6.0 migration guide for discussions about migrating uses of these annotations
====
[NOTE]
====
The new annotations added as part of 6.0 support composing mappings in annotations
through "meta-annotations".
====
Looking at <<basic-annotation-implicit-example, this example>>, how does Hibernate know what mapping
to use for these attributes? The annotations do not really provide much information.
@ -1555,160 +1561,6 @@ a Java service (see `java.util.ServiceLoader`).
[[basic-bitset]]
==== Case Study : BitSet
We've covered many ways to specify basic value mappings so far. This section will look at mapping the
`java.util.BitSet` type by applying the different techniques covered so far.
[[basic-bitset-example-implicit]]
.Implicit BitSet mapping
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetImplicitTests.java[tags=basic-bitset-example-implicit]
----
====
As mentioned previously, the worst-case fallback for Hibernate mapping a basic type
which implements `Serializable` is to simply serialize it to the database. BitSet
does implement `Serializable`, so by default Hibernate would handle this mapping by serialization.
That is not an ideal mapping. In the following sections we will look at approaches to change
various aspects of how the BitSet gets mapped to the database.
[[basic-bitset-converter]]
===== Using `AttributeConverter`
We've seen uses of `AttributeConverter` previously.
This works well in most cases and is portable across Jakarta Persistence providers.
[[basic-bitset-example-converter]]
.BitSet AttributeConverter
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetConverterTests.java[tags=basic-bitset-example-convert]
include::{sourcedir}/basic/BitSetConverterTests.java[tags=basic-bitset-example-converter]
----
====
The `@Convert` annotation was used for illustration. Generally such a converter would be auto-applied instead
See <<basic-jpa-convert>> for details.
[NOTE]
====
The use of `AttributeConverter` can have drawbacks related to Hibernate being able
to appropriately handle the mutability aspects of the converted values. `AttributeConverter`
does not indicate whether the converted values are mutable or not, so Hibernate has to assume
they are (the safer assumption) which can lead to excessive conversions back and forth as part
of dirty-checking as well as second-level caching.
See <<basic-jpa-convert-mutability>> for additional details.
====
[[basic-bitset-java-type]]
===== Using a custom `JavaTypeDescriptor`
As covered in <<basic-mapping-explicit>>, we will define a `JavaTypeDescriptor`
for `BitSet` that maps values to `VARCHAR` for storage by default.
[[basic-bitset-example-java-type]]
.BitSet JavaTypeDescriptor
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetJavaType.java[tags=basic-bitset-example-java-type]
----
====
We can either apply that type locally using `@JavaType`
[[basic-bitset-example-java-type-local]]
.@JavaType
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetJavaTypeTests.java[tags=basic-bitset-example-java-type-local,indent=0]
----
====
Or we can apply it globally using `@JavaTypeRegistration`. This allows the registered `JavaTypeDescriptor`
to be used as the default whenever we encounter the `BitSet` type
[[basic-bitset-example-java-type-global]]
.@JavaTypeRegistration
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetJavaTypeRegistrationTests.java[tags=basic-bitset-example-java-type-global,indent=0]
----
====
[[basic-bitset-jdbc-type]]
===== Selecting different `JdbcTypeDescriptor`
Our custom `BitSetJavaType` maps `BitSet` values to `VARCHAR` by default. That was a better option
than direct serialization. But as `BitSet` is ultimately binary data we would probably really want to
map this to `VARBINARY` type instead. One way to do that would be to change `BitSetJavaType#getRecommendedJdbcType`
to instead return `VARBINARY` descriptor. Another option would be to use a local `@JdbcType` or `@JdbcTypeCode`.
The following examples for specifying the `JdbcTypeDescriptor` assume our `BitSetJavaType`
is globally registered.
We will again store the values as `VARBINARY` in the database. The difference now however is that
the coercion methods `#wrap` and `#unwrap` will be used to prepare the value rather than relying on
serialization.
[[basic-bitset-example-jdbc-type-code]]
.@JdbcTypeCode
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetJdbcTypeCodeTests.java[tags=basic-bitset-example-jdbc-type-code,indent=0]
----
====
In this example, `@JdbcTypeCode` has been used to indicate that the `JdbcTypeDescriptor` registered for JDBC's
`VARBINARY` type should be used.
[[basic-bitset-example-jdbc-type-local]]
.@JdbcType
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetJdbcTypeTests.java[tags=basic-bitset-example-jdbc-type-local,indent=0]
----
====
In this example, `@JdbcType` has been used to specify our custom `BitSetJdbcType` descriptor locally for
this attribute.
We could instead replace how Hibernate deals with all `VARBINARY` handling with our custom impl using
`@JdbcTypeRegistration`
[[basic-bitset-example-jdbc-type-global]]
.@JdbcType
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetJdbcTypeRegistrationTests.java[tags=basic-bitset-example-jdbc-type-global,indent=0]
----
====
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[[basic-jpa-convert]]
@ -1953,7 +1805,156 @@ By passing the associated Hibernate `Type`, you can use the `Caption` object whe
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[[basic-bitset]]
==== Case Study : BitSet
We've covered many ways to specify basic value mappings so far. This section will look at mapping the
`java.util.BitSet` type by applying the different techniques covered so far.
[[basic-bitset-example-implicit]]
.Implicit BitSet mapping
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/bitset/BitSetImplicitTests.java[tags=basic-bitset-example-implicit]
----
====
As mentioned previously, the worst-case fallback for Hibernate mapping a basic type
which implements `Serializable` is to simply serialize it to the database. BitSet
does implement `Serializable`, so by default Hibernate would handle this mapping by serialization.
That is not an ideal mapping. In the following sections we will look at approaches to change
various aspects of how the BitSet gets mapped to the database.
[[basic-bitset-converter]]
===== Using `AttributeConverter`
We've seen uses of `AttributeConverter` previously.
This works well in most cases and is portable across Jakarta Persistence providers.
[[basic-bitset-example-converter]]
.BitSet AttributeConverter
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/bitset/BitSetConverterTests.java[tags=basic-bitset-example-convert]
include::{sourcedir}/basic/bitset/BitSetConverterTests.java[tags=basic-bitset-example-converter]
----
====
[NOTE]
====
The `@Convert` annotation was used for illustration. Generally such a converter would be auto-applied instead
See <<basic-jpa-convert>> for details.
====
This greatly improves the reading and writing performance of dealing with these
BitSet values because the `AttributeConverter` does that more efficiently using
a simple externalizable form of the BitSet rather than serializing and deserializing
the values.
See also <<basic-jpa-convert-mutability>>.
[[basic-bitset-java-type]]
===== Using a custom `JavaTypeDescriptor`
As covered in <<basic-mapping-explicit>>, we will define a `JavaTypeDescriptor`
for `BitSet` that maps values to `VARCHAR` for storage by default.
[[basic-bitset-example-java-type]]
.BitSet JavaTypeDescriptor
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/bitset/BitSetJavaType.java[tags=basic-bitset-example-java-type]
----
====
We can either apply that type locally using `@JavaType`
[[basic-bitset-example-java-type-local]]
.@JavaType
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/bitset/BitSetJavaTypeTests.java[tags=basic-bitset-example-java-type-local,indent=0]
----
====
Or we can apply it globally using `@JavaTypeRegistration`. This allows the registered `JavaTypeDescriptor`
to be used as the default whenever we encounter the `BitSet` type
[[basic-bitset-example-java-type-global]]
.@JavaTypeRegistration
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/bitset/BitSetJavaTypeRegistrationTests.java[tags=basic-bitset-example-java-type-global,indent=0]
----
====
[[basic-bitset-jdbc-type]]
===== Selecting different `JdbcTypeDescriptor`
Our custom `BitSetJavaType` maps `BitSet` values to `VARCHAR` by default. That was a better option
than direct serialization. But as `BitSet` is ultimately binary data we would probably really want to
map this to `VARBINARY` type instead. One way to do that would be to change `BitSetJavaType#getRecommendedJdbcType`
to instead return `VARBINARY` descriptor. Another option would be to use a local `@JdbcType` or `@JdbcTypeCode`.
The following examples for specifying the `JdbcTypeDescriptor` assume our `BitSetJavaType`
is globally registered.
We will again store the values as `VARBINARY` in the database. The difference now however is that
the coercion methods `#wrap` and `#unwrap` will be used to prepare the value rather than relying on
serialization.
[[basic-bitset-example-jdbc-type-code]]
.@JdbcTypeCode
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/bitset/BitSetJdbcTypeCodeTests.java[tags=basic-bitset-example-jdbc-type-code,indent=0]
----
====
In this example, `@JdbcTypeCode` has been used to indicate that the `JdbcTypeDescriptor` registered for JDBC's
`VARBINARY` type should be used.
[[basic-bitset-example-jdbc-type-local]]
.@JdbcType
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/bitset/BitSetJdbcTypeTests.java[tags=basic-bitset-example-jdbc-type-local,indent=0]
----
====
In this example, `@JdbcType` has been used to specify our custom `BitSetJdbcType` descriptor locally for
this attribute.
We could instead replace how Hibernate deals with all `VARBINARY` handling with our custom impl using
`@JdbcTypeRegistration`
[[basic-bitset-example-jdbc-type-global]]
.@JdbcType
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/bitset/BitSetJdbcTypeRegistrationTests.java[tags=basic-bitset-example-jdbc-type-global,indent=0]
----
====

View File

@ -0,0 +1,30 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.associations.any;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.hibernate.annotations.AnyDiscriminator;
import org.hibernate.annotations.AnyDiscriminatorValue;
import org.hibernate.annotations.AnyKeyJavaClass;
import jakarta.persistence.DiscriminatorType;
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@AnyDiscriminator( DiscriminatorType.STRING )
@AnyKeyJavaClass( Long.class )
@AnyDiscriminatorValue( discriminator = "S", entity = StringProperty.class )
@AnyDiscriminatorValue( discriminator = "I", entity = IntegerProperty.class )
public @interface PropertyDiscriminationDef {
}

View File

@ -0,0 +1,51 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.userguide.associations.any;
import org.hibernate.annotations.Any;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Table;
//tag::associations-any-def-example[]
@Entity
@Table( name = "property_holder" )
public class PropertyHolder2 {
@Id
private Long id;
@Any
@PropertyDiscriminationDef
@Column( name = "property_type" )
@JoinColumn( name = "property_id" )
private Property property;
//Getters and setters are omitted for brevity
//end::associations-any-def-example[]
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Property getProperty() {
return property;
}
public void setProperty(Property property) {
this.property = property;
}
//tag::associations-any-def-example[]
}
//end::associations-any-def-example[]

View File

@ -43,7 +43,7 @@ import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import org.hibernate.userguide.mapping.basic.BitSetUserType;
import org.hibernate.userguide.mapping.basic.bitset.BitSetUserType;
import org.junit.Test;

View File

@ -0,0 +1,123 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic.bitset;
import java.sql.Types;
import java.util.BitSet;
import org.hibernate.annotations.Immutable;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Convert;
import jakarta.persistence.Converter;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.assertj.core.api.Assertions;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.isOneOf;
/**
* Test using a converter to map the BitSet
*/
@DomainModel( annotatedClasses = BitSetConverterImmutableTests.Product.class )
@SessionFactory
public class BitSetConverterImmutableTests {
@Test
public void verifyMappings(SessionFactoryScope scope) {
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
final MappingMetamodel domainModel = sessionFactory.getDomainModel();
final EntityPersister entityDescriptor = domainModel.findEntityDescriptor( Product.class );
final BasicAttributeMapping attributeMapping = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "bitSet" );
assertThat( attributeMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo( BitSet.class ) );
assertThat( attributeMapping.getValueConverter(), instanceOf( JpaAttributeConverter.class ) );
final JpaAttributeConverter converter = (JpaAttributeConverter) attributeMapping.getValueConverter();
assertThat( converter.getConverterBean().getBeanClass(), equalTo( BitSetConverter.class ) );
Assertions.assertThat( attributeMapping.getExposedMutabilityPlan() ).isNotInstanceOf( BitSetMutabilityPlan.class );
Assertions.assertThat( attributeMapping.getExposedMutabilityPlan() ).isInstanceOf( ImmutableMutabilityPlan.class );
Assertions.assertThat( attributeMapping.getExposedMutabilityPlan().isMutable() ).isFalse();
final BitSet sample = new BitSet();
Assertions.assertThat( ( (MutabilityPlan) attributeMapping.getExposedMutabilityPlan() ).deepCopy( sample ) ).isSameAs( sample );
assertThat(
attributeMapping.getJdbcMapping().getJdbcTypeDescriptor().getJdbcTypeCode(),
isOneOf( Types.VARCHAR, Types.NVARCHAR )
);
assertThat( attributeMapping.getJdbcMapping().getJavaTypeDescriptor().getJavaTypeClass(), equalTo( String.class ) );
}
@Table(name = "products")
//tag::basic-bitset-example-convert[]
@Entity(name = "Product")
public static class Product {
@Id
private Integer id;
@Convert( converter = BitSetConverter.class )
private BitSet bitSet;
//Getters and setters are omitted for brevity
//end::basic-bitset-example-convert[]
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public BitSet getBitSet() {
return bitSet;
}
public void setBitSet(BitSet bitSet) {
this.bitSet = bitSet;
}
//tag::basic-bitset-example-convert[]
}
//end::basic-bitset-example-convert[]
//tag::basic-bitset-example-converter[]
@Immutable
@Converter( autoApply = true )
public static class BitSetConverter implements AttributeConverter<BitSet,String> {
@Override
public String convertToDatabaseColumn(BitSet attribute) {
return BitSetHelper.bitSetToString( attribute );
}
@Override
public BitSet convertToEntityAttribute(String dbData) {
return BitSetHelper.stringToBitSet( dbData );
}
}
//end::basic-bitset-example-converter[]
}

View File

@ -0,0 +1,117 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic.bitset;
import java.sql.Types;
import java.util.BitSet;
import org.hibernate.annotations.Mutability;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Convert;
import jakarta.persistence.Converter;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.assertj.core.api.Assertions;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.isOneOf;
/**
* Basically the same test as {@link BitSetConverterTests} except here we
* specify a mutability-plan on the converter to improve the deep-copying
*/
@DomainModel( annotatedClasses = BitSetConverterMutabilityTests.Product.class )
@SessionFactory
public class BitSetConverterMutabilityTests {
@Test
public void verifyMappings(SessionFactoryScope scope) {
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
final MappingMetamodel domainModel = sessionFactory.getDomainModel();
final EntityPersister entityDescriptor = domainModel.findEntityDescriptor( Product.class );
final BasicAttributeMapping attributeMapping = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "bitSet" );
assertThat( attributeMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo( BitSet.class ) );
assertThat( attributeMapping.getValueConverter(), instanceOf( JpaAttributeConverter.class ) );
final JpaAttributeConverter converter = (JpaAttributeConverter) attributeMapping.getValueConverter();
assertThat( converter.getConverterBean().getBeanClass(), equalTo( BitSetConverter.class ) );
Assertions.assertThat( attributeMapping.getExposedMutabilityPlan() ).isInstanceOf( BitSetMutabilityPlan.class );
assertThat(
attributeMapping.getJdbcMapping().getJdbcTypeDescriptor().getJdbcTypeCode(),
isOneOf( Types.VARCHAR, Types.NVARCHAR )
);
assertThat( attributeMapping.getJdbcMapping().getJavaTypeDescriptor().getJavaTypeClass(), equalTo( String.class ) );
}
@Table(name = "products")
//tag::basic-bitset-example-convert[]
@Entity(name = "Product")
public static class Product {
@Id
private Integer id;
@Convert( converter = BitSetConverter.class )
private BitSet bitSet;
//Getters and setters are omitted for brevity
//end::basic-bitset-example-convert[]
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public BitSet getBitSet() {
return bitSet;
}
public void setBitSet(BitSet bitSet) {
this.bitSet = bitSet;
}
//tag::basic-bitset-example-convert[]
}
//end::basic-bitset-example-convert[]
//tag::basic-bitset-example-converter[]
@Converter( autoApply = true )
@Mutability( BitSetMutabilityPlan.class )
public static class BitSetConverter implements AttributeConverter<BitSet,String> {
@Override
public String convertToDatabaseColumn(BitSet attribute) {
return BitSetHelper.bitSetToString( attribute );
}
@Override
public BitSet convertToEntityAttribute(String dbData) {
return BitSetHelper.stringToBitSet( dbData );
}
}
//end::basic-bitset-example-converter[]
}

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic;
package org.hibernate.userguide.mapping.basic.bitset;
import java.sql.Types;
import java.util.BitSet;
@ -14,10 +14,10 @@ import jakarta.persistence.Converter;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.assertj.core.api.Assertions;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter;
import org.hibernate.persister.entity.EntityPersister;
@ -33,7 +33,7 @@ import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.isOneOf;
/**
* @author Steve Ebersole
* Test using a converter to map the BitSet
*/
@DomainModel( annotatedClasses = BitSetConverterTests.Product.class )
@SessionFactory
@ -53,6 +53,8 @@ public class BitSetConverterTests {
final JpaAttributeConverter converter = (JpaAttributeConverter) attributeMapping.getValueConverter();
assertThat( converter.getConverterBean().getBeanClass(), equalTo( BitSetConverter.class ) );
Assertions.assertThat( attributeMapping.getExposedMutabilityPlan() ).isNotInstanceOf( BitSetMutabilityPlan.class );
assertThat(
attributeMapping.getJdbcMapping().getJdbcTypeDescriptor().getJdbcTypeCode(),
isOneOf( Types.VARCHAR, Types.NVARCHAR )

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic;
package org.hibernate.userguide.mapping.basic.bitset;
import java.util.BitSet;

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic;
package org.hibernate.userguide.mapping.basic.bitset;
import java.sql.Types;
import java.util.BitSet;
@ -15,7 +15,6 @@ import jakarta.persistence.Table;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.orm.junit.DomainModel;
@ -23,13 +22,9 @@ import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import org.hamcrest.Matchers;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isOneOf;
import static org.hamcrest.Matchers.nullValue;
/**

View File

@ -1,4 +1,10 @@
package org.hibernate.userguide.mapping.basic;
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic.bitset;
import java.sql.Types;
import java.util.BitSet;

View File

@ -0,0 +1,86 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic.bitset;
import java.util.BitSet;
import org.hibernate.annotations.JavaTypeRegistration;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
/**
* @author Steve Ebersole
*/
@DomainModel(
annotatedClasses = BitSetJavaTypeContributionTests.Product.class,
typeContributors = BitSetJavaTypeContributor.class
)
@SessionFactory
public class BitSetJavaTypeContributionTests {
@Test
public void testResolution(SessionFactoryScope scope) {
final EntityPersister productType = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel()
.findEntityDescriptor( Product.class );
final SingularAttributeMapping bitSetAttribute = (SingularAttributeMapping) productType.findAttributeMapping( "bitSet" );
// make sure BitSetTypeDescriptor was selected
assertThat( bitSetAttribute.getJavaTypeDescriptor(), instanceOf( BitSetJavaType.class ) );
}
@Table(name = "Product")
//tag::basic-bitset-example-java-type-contrib[]
@Entity(name = "Product")
public static class Product {
@Id
private Integer id;
private BitSet bitSet;
//Constructors, getters, and setters are omitted for brevity
//end::basic-bitset-example-java-type-contrib[]
public Product() {
}
public Product(Number id, BitSet bitSet) {
this.id = id.intValue();
this.bitSet = bitSet;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public BitSet getBitSet() {
return bitSet;
}
public void setBitSet(BitSet bitSet) {
this.bitSet = bitSet;
}
//tag::basic-bitset-example-java-type-contrib[]
}
//end::basic-bitset-example-java-type-contrib[]
}

View File

@ -0,0 +1,21 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic.bitset;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.TypeContributor;
import org.hibernate.service.ServiceRegistry;
/**
* @author Steve Ebersole
*/
public class BitSetJavaTypeContributor implements TypeContributor {
@Override
public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
typeContributions.contributeJavaTypeDescriptor( BitSetJavaType.INSTANCE );
}
}

View File

@ -4,10 +4,10 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic;
package org.hibernate.userguide.mapping.basic.bitset;
import java.util.BitSet;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

View File

@ -4,10 +4,10 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic;
package org.hibernate.userguide.mapping.basic.bitset;
import java.util.BitSet;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic;
package org.hibernate.userguide.mapping.basic.bitset;
import java.sql.Types;
import java.util.BitSet;

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic;
package org.hibernate.userguide.mapping.basic.bitset;
import java.sql.Types;
import java.util.BitSet;
@ -14,6 +14,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.userguide.mapping.basic.CustomBinaryJdbcType;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic;
package org.hibernate.userguide.mapping.basic.bitset;
import java.sql.Types;
import java.util.BitSet;
@ -17,6 +17,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.userguide.mapping.basic.CustomBinaryJdbcType;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic;
package org.hibernate.userguide.mapping.basic.bitset;
import java.io.Serializable;
import java.util.BitSet;
@ -13,7 +13,7 @@ import org.hibernate.SharedSessionContract;
import org.hibernate.type.descriptor.java.MutabilityPlan;
/**
* @author Steve Ebersole
* A BitSet's internal state is mutable, so handle that
*/
public class BitSetMutabilityPlan implements MutabilityPlan<BitSet> {
/**

View File

@ -1,4 +1,10 @@
package org.hibernate.userguide.mapping.basic;
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic.bitset;
import java.io.Serializable;
import java.sql.PreparedStatement;
@ -10,7 +16,6 @@ import java.util.Objects;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.StringType;
import org.hibernate.usertype.UserType;
import org.jboss.logging.Logger;

View File

@ -1,10 +1,10 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic;
package org.hibernate.userguide.mapping.basic.bitset;
import java.util.BitSet;

View File

@ -0,0 +1,13 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
/**
* Tests of various ways to map a basic value using `BitSet` as a case study.
*
* Used as the base for the Case Study section in the documentation
*/
package org.hibernate.userguide.mapping.basic.bitset;

View File

@ -11,6 +11,7 @@ import java.lang.annotation.Retention;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.DiscriminatorType;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -25,7 +26,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
*
* @since 6.0
*/
@java.lang.annotation.Target({METHOD, FIELD})
@java.lang.annotation.Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention( RUNTIME )
public @interface AnyDiscriminator {
/**

View File

@ -9,6 +9,7 @@ package org.hibernate.annotations;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -19,7 +20,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
*
* @since 6.0
*/
@java.lang.annotation.Target({METHOD, FIELD})
@java.lang.annotation.Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention( RUNTIME )
@Repeatable(AnyDiscriminatorValues.class)
public @interface AnyDiscriminatorValue {

View File

@ -8,6 +8,7 @@ package org.hibernate.annotations;
import java.lang.annotation.Retention;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -17,7 +18,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
*
* @since 6.0
*/
@java.lang.annotation.Target({METHOD, FIELD})
@java.lang.annotation.Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention( RUNTIME )
public @interface AnyDiscriminatorValues {
/**

View File

@ -8,6 +8,7 @@ package org.hibernate.annotations;
import java.lang.annotation.Retention;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -23,7 +24,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
*
* @since 6.0
*/
@java.lang.annotation.Target({METHOD, FIELD})
@java.lang.annotation.Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention( RUNTIME )
public @interface AnyKeyJavaClass {
/**

View File

@ -10,6 +10,7 @@ import java.lang.annotation.Retention;
import org.hibernate.type.descriptor.java.BasicJavaTypeDescriptor;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -22,7 +23,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
*
* @since 6.0
*/
@java.lang.annotation.Target({METHOD, FIELD})
@java.lang.annotation.Target({METHOD, FIELD,ANNOTATION_TYPE})
@Retention( RUNTIME )
public @interface AnyKeyJavaType {
/**

View File

@ -10,6 +10,7 @@ import java.lang.annotation.Retention;
import org.hibernate.type.descriptor.jdbc.JdbcTypeDescriptor;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -22,7 +23,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
*
* @since 6.0
*/
@java.lang.annotation.Target({METHOD, FIELD})
@java.lang.annotation.Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
public @interface AnyKeyJdbcType {
/**

View File

@ -8,6 +8,7 @@ package org.hibernate.annotations;
import java.lang.annotation.Retention;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -20,7 +21,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
*
* @since 6.0
*/
@java.lang.annotation.Target({METHOD, FIELD})
@java.lang.annotation.Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
public @interface AnyKeyJdbcTypeCode {
/**

View File

@ -20,7 +20,16 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Registers the BasicJavaDescriptor to use for the given {@link #javaType}
*
* @author Steve Ebersole
* Registrations applied to a package are processed before Hibernate begins to process
* any attributes, etc.
*
* Registrations applied to a class are only applied once Hibernate begins to process
* that class; it will also affect all future processing. However, it will not change
* previous resolutions to use this newly registered one. Because of this randomness
* it is recommended to only apply registrations to packages or to use a
* {@link org.hibernate.boot.model.TypeContributor}.
*
* @see org.hibernate.boot.model.TypeContributor
*
* @since 6.0
*/

View File

@ -17,7 +17,8 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Grouping of {@link JavaTypeRegistration}
*
* @author Steve Ebersole
* See notes on {@link JavaTypeRegistration} about using on packages
* versus use on classes
*
* @since 6.0
*/

View File

@ -13,20 +13,27 @@ import java.lang.annotation.Retention;
import org.hibernate.type.descriptor.jdbc.JdbcTypeDescriptor;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeDescriptorRegistry;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.PACKAGE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Describes a SqlTypeDescriptor to be added to the
* {@link JdbcTypeDescriptorRegistry}
* Describes a SqlTypeDescriptor to be added to the {@link JdbcTypeDescriptorRegistry}
*
* @author Steve Ebersole
* Registrations applied to a package are processed before Hibernate begins to process
* any attributes, etc.
*
* Registrations applied to a class are only applied once Hibernate begins to process
* that class; it will also affect all future processing. However, it will not change
* previous resolutions to use this newly registered one. Because of this randomness
* it is recommended to only apply registrations to packages or to use a
* {@link org.hibernate.boot.model.TypeContributor}.
*
* @see org.hibernate.boot.model.TypeContributor
*
* @since 6.0
*/
@java.lang.annotation.Target({PACKAGE, TYPE, ANNOTATION_TYPE})
@java.lang.annotation.Target({PACKAGE, TYPE})
@Inherited
@Retention(RUNTIME)
@Repeatable( JdbcTypeRegistrations.class )

View File

@ -9,7 +9,6 @@ package org.hibernate.annotations;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.PACKAGE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -17,11 +16,12 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Grouping of {@link JdbcTypeRegistration}
*
* @author Steve Ebersole
* See notes on {@link JdbcTypeRegistration} about using on packages
* versus use on classes
*
* @since 6.0
*/
@java.lang.annotation.Target({PACKAGE, TYPE, ANNOTATION_TYPE})
@java.lang.annotation.Target({PACKAGE, TYPE})
@Inherited
@Retention(RUNTIME)
public @interface JdbcTypeRegistrations {

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
@ -14,6 +15,7 @@ import org.hibernate.type.descriptor.java.MutabilityPlan;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
@ -43,11 +45,14 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* See <a href="package-summary.html#basic-value-mapping"/> for high-level discussion
* of basic value mapping.
*
* @apiNote Valid on {@link ElementType#TYPE} in very limited cases - at the moment
* it is only supported on an AttributeConverter implementation
*
* @see Immutable
*
* @since 6.0
*/
@java.lang.annotation.Target({METHOD, FIELD, ANNOTATION_TYPE})
@java.lang.annotation.Target({METHOD, FIELD, ANNOTATION_TYPE, TYPE})
@Inherited
@Retention(RUNTIME)
public @interface Mutability {

View File

@ -44,6 +44,7 @@ import org.hibernate.annotations.MapKeyMutability;
import org.hibernate.annotations.Mutability;
import org.hibernate.annotations.Nationalized;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.Target;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.annotations.internal.NoJavaTypeDescriptor;
@ -95,6 +96,8 @@ import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import jakarta.persistence.Version;
import static org.hibernate.cfg.annotations.HCANNHelper.findAnnotation;
/**
* @author Steve Ebersole
* @author Emmanuel Bernard
@ -391,7 +394,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
implicitJavaTypeAccess = (typeConfiguration) -> null;
explicitJavaTypeAccess = (typeConfiguration) -> {
final CollectionIdJavaType javaTypeAnn = modelXProperty.getAnnotation( CollectionIdJavaType.class );
final CollectionIdJavaType javaTypeAnn = findAnnotation( modelXProperty, CollectionIdJavaType.class );
if ( javaTypeAnn != null ) {
final Class<? extends BasicJavaTypeDescriptor<?>> javaType = normalizeJavaType( javaTypeAnn.value() );
if ( javaType != null ) {
@ -404,7 +407,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
};
explicitJdbcTypeAccess = (typeConfiguration) -> {
final CollectionIdJdbcType jdbcTypeAnn = modelXProperty.getAnnotation( CollectionIdJdbcType.class );
final CollectionIdJdbcType jdbcTypeAnn = findAnnotation( modelXProperty, CollectionIdJdbcType.class );
if ( jdbcTypeAnn != null ) {
final Class<? extends JdbcTypeDescriptor> jdbcType = normalizeJdbcType( jdbcTypeAnn.value() );
if ( jdbcType != null ) {
@ -413,7 +416,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
}
}
final CollectionIdJdbcTypeCode jdbcTypeCodeAnn = modelXProperty.getAnnotation( CollectionIdJdbcTypeCode.class );
final CollectionIdJdbcTypeCode jdbcTypeCodeAnn = findAnnotation( modelXProperty, CollectionIdJdbcTypeCode.class );
if ( jdbcTypeCodeAnn != null ) {
if ( jdbcTypeCodeAnn.value() != Integer.MIN_VALUE ) {
return typeConfiguration.getJdbcTypeDescriptorRegistry().getDescriptor( jdbcTypeCodeAnn.value() );
@ -424,12 +427,11 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
};
explicitMutabilityAccess = (typeConfiguration) -> {
final CollectionIdMutability mutabilityAnn = modelXProperty.getAnnotation( CollectionIdMutability.class );
final CollectionIdMutability mutabilityAnn = findAnnotation( modelXProperty, CollectionIdMutability.class );
if ( mutabilityAnn != null ) {
final Class<? extends MutabilityPlan<?>> mutability = normalizeMutability( mutabilityAnn.value() );
if ( mutability != null ) {
final ManagedBean<? extends MutabilityPlan<?>> jtdBean = beanRegistry
.getBean( mutability );
final ManagedBean<? extends MutabilityPlan<?>> jtdBean = beanRegistry.getBean( mutability );
return jtdBean.getBeanInstance();
}
}
@ -444,8 +446,14 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
}
}
// if the value is converted, see if the converter Class is annotated `@Immutable`
// if there is a converter, check it for mutability-related annotations
if ( converterDescriptor != null ) {
final Mutability converterMutabilityAnn = converterDescriptor.getAttributeConverterClass().getAnnotation( Mutability.class );
if ( converterMutabilityAnn != null ) {
final ManagedBean<? extends MutabilityPlan<?>> jtdBean = beanRegistry.getBean( converterMutabilityAnn.value() );
return jtdBean.getBeanInstance();
}
if ( converterDescriptor.getAttributeConverterClass().isAnnotationPresent( Immutable.class ) ) {
return ImmutableMutabilityPlan.instance();
}
@ -495,7 +503,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
.getService( ManagedBeanRegistry.class );
explicitJdbcTypeAccess = typeConfiguration -> {
final MapKeyJdbcType jdbcTypeAnn = mapAttribute.getAnnotation( MapKeyJdbcType.class );
final MapKeyJdbcType jdbcTypeAnn = findAnnotation( mapAttribute, MapKeyJdbcType.class );
if ( jdbcTypeAnn != null ) {
final Class<? extends JdbcTypeDescriptor> jdbcTypeImpl = normalizeJdbcType( jdbcTypeAnn.value() );
if ( jdbcTypeImpl != null ) {
@ -504,7 +512,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
}
}
final MapKeyJdbcTypeCode jdbcTypeCodeAnn = mapAttribute.getAnnotation( MapKeyJdbcTypeCode.class );
final MapKeyJdbcTypeCode jdbcTypeCodeAnn = findAnnotation( mapAttribute, MapKeyJdbcTypeCode.class );
if ( jdbcTypeCodeAnn != null ) {
final int jdbcTypeCode = jdbcTypeCodeAnn.value();
if ( jdbcTypeCode != Integer.MIN_VALUE ) {
@ -516,7 +524,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
};
explicitJavaTypeAccess = typeConfiguration -> {
final MapKeyJavaType javaTypeAnn = mapAttribute.getAnnotation( MapKeyJavaType.class );
final MapKeyJavaType javaTypeAnn = findAnnotation( mapAttribute, MapKeyJavaType.class );
if ( javaTypeAnn != null ) {
final Class<? extends BasicJavaTypeDescriptor<?>> jdbcTypeImpl = normalizeJavaType( javaTypeAnn.value() );
if ( jdbcTypeImpl != null ) {
@ -534,7 +542,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
};
explicitMutabilityAccess = typeConfiguration -> {
final MapKeyMutability mutabilityAnn = mapAttribute.getAnnotation( MapKeyMutability.class );
final MapKeyMutability mutabilityAnn = findAnnotation( mapAttribute, MapKeyMutability.class );
if ( mutabilityAnn != null ) {
final Class<? extends MutabilityPlan<?>> mutability = normalizeMutability( mutabilityAnn.value() );
if ( mutability != null ) {
@ -555,6 +563,12 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
// if the value is converted, see if the converter Class is annotated `@Immutable`
if ( converterDescriptor != null ) {
final Mutability converterMutabilityAnn = converterDescriptor.getAttributeConverterClass().getAnnotation( Mutability.class );
if ( converterMutabilityAnn != null ) {
final ManagedBean<? extends MutabilityPlan<?>> jtdBean = managedBeanRegistry.getBean( converterMutabilityAnn.value() );
return jtdBean.getBeanInstance();
}
if ( converterDescriptor.getAttributeConverterClass().isAnnotationPresent( Immutable.class ) ) {
return ImmutableMutabilityPlan.instance();
}
@ -581,7 +595,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
.getService( ManagedBeanRegistry.class );
explicitJavaTypeAccess = (typeConfiguration) -> {
final ListIndexJavaType javaTypeAnn = listAttribute.getAnnotation( ListIndexJavaType.class );
final ListIndexJavaType javaTypeAnn = findAnnotation( listAttribute, ListIndexJavaType.class );
if ( javaTypeAnn != null ) {
final Class<? extends BasicJavaTypeDescriptor<?>> javaType = normalizeJavaType( javaTypeAnn.value() );
if ( javaType != null ) {
@ -594,7 +608,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
};
explicitJdbcTypeAccess = (typeConfiguration) -> {
final ListIndexJdbcType jdbcTypeAnn = listAttribute.getAnnotation( ListIndexJdbcType.class );
final ListIndexJdbcType jdbcTypeAnn = findAnnotation( listAttribute, ListIndexJdbcType.class );
if ( jdbcTypeAnn != null ) {
final Class<? extends JdbcTypeDescriptor> jdbcType = normalizeJdbcType( jdbcTypeAnn.value() );
if ( jdbcType != null ) {
@ -603,7 +617,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
}
}
final ListIndexJdbcTypeCode jdbcTypeCodeAnn = listAttribute.getAnnotation( ListIndexJdbcTypeCode.class );
final ListIndexJdbcTypeCode jdbcTypeCodeAnn = findAnnotation( listAttribute, ListIndexJdbcTypeCode.class );
if ( jdbcTypeCodeAnn != null ) {
return typeConfiguration.getJdbcTypeDescriptorRegistry().getDescriptor( jdbcTypeCodeAnn.value() );
}
@ -736,7 +750,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
}
private void prepareAnyDiscriminator(XProperty modelXProperty) {
final AnyDiscriminator anyDiscriminatorAnn = modelXProperty.getAnnotation( AnyDiscriminator.class );
final AnyDiscriminator anyDiscriminatorAnn = findAnnotation( modelXProperty, AnyDiscriminator.class );
implicitJavaTypeAccess = (typeConfiguration) -> {
if ( anyDiscriminatorAnn != null ) {
@ -783,7 +797,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
.getService( ManagedBeanRegistry.class );
explicitJavaTypeAccess = (typeConfiguration) -> {
final AnyKeyJavaType javaTypeAnn = modelXProperty.getAnnotation( AnyKeyJavaType.class );
final AnyKeyJavaType javaTypeAnn = findAnnotation( modelXProperty, AnyKeyJavaType.class );
if ( javaTypeAnn != null ) {
final Class<? extends BasicJavaTypeDescriptor<?>> javaType = normalizeJavaType( javaTypeAnn.value() );
@ -793,7 +807,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
}
}
final AnyKeyJavaClass javaClassAnn = modelXProperty.getAnnotation( AnyKeyJavaClass.class );
final AnyKeyJavaClass javaClassAnn = findAnnotation( modelXProperty, AnyKeyJavaClass.class );
if ( javaClassAnn != null ) {
//noinspection rawtypes
return (BasicJavaTypeDescriptor) typeConfiguration
@ -805,7 +819,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
};
explicitJdbcTypeAccess = (typeConfiguration) -> {
final AnyKeyJdbcType jdbcTypeAnn = modelXProperty.getAnnotation( AnyKeyJdbcType.class );
final AnyKeyJdbcType jdbcTypeAnn = findAnnotation( modelXProperty, AnyKeyJdbcType.class );
if ( jdbcTypeAnn != null ) {
final Class<? extends JdbcTypeDescriptor> jdbcType = normalizeJdbcType( jdbcTypeAnn.value() );
if ( jdbcType != null ) {
@ -814,7 +828,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
}
}
final AnyKeyJdbcTypeCode jdbcTypeCodeAnn = modelXProperty.getAnnotation( AnyKeyJdbcTypeCode.class );
final AnyKeyJdbcTypeCode jdbcTypeCodeAnn = findAnnotation( modelXProperty, AnyKeyJdbcTypeCode.class );
if ( jdbcTypeCodeAnn != null ) {
if ( jdbcTypeCodeAnn.value() != Integer.MIN_VALUE ) {
return typeConfiguration.getJdbcTypeDescriptorRegistry().getDescriptor( jdbcTypeCodeAnn.value() );
@ -833,7 +847,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
.getServiceRegistry()
.getService( ManagedBeanRegistry.class );
final JdbcType jdbcTypeAnn = attributeXProperty.getAnnotation( JdbcType.class );
final JdbcType jdbcTypeAnn = findAnnotation( attributeXProperty, JdbcType.class );
if ( jdbcTypeAnn != null ) {
final Class<? extends JdbcTypeDescriptor> jdbcType = normalizeJdbcType( jdbcTypeAnn.value() );
if ( jdbcType != null ) {
@ -842,7 +856,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
}
}
final JdbcTypeCode jdbcTypeCodeAnn = attributeXProperty.getAnnotation( JdbcTypeCode.class );
final JdbcTypeCode jdbcTypeCodeAnn = findAnnotation( attributeXProperty, JdbcTypeCode.class );
if ( jdbcTypeCodeAnn != null ) {
final int jdbcTypeCode = jdbcTypeCodeAnn.value();
if ( jdbcTypeCode != Integer.MIN_VALUE ) {
@ -862,7 +876,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
.getService( ManagedBeanRegistry.class );
explicitMutabilityAccess = typeConfiguration -> {
final Mutability mutabilityAnn = attributeXProperty.getAnnotation( Mutability.class );
final Mutability mutabilityAnn = findAnnotation( attributeXProperty, Mutability.class );
if ( mutabilityAnn != null ) {
final Class<? extends MutabilityPlan<?>> mutability = normalizeMutability( mutabilityAnn.value() );
if ( mutability != null ) {
@ -888,6 +902,12 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
// if the value is converted, see if the converter Class is annotated `@Immutable`
if ( converterDescriptor != null ) {
final Mutability converterMutabilityAnn = converterDescriptor.getAttributeConverterClass().getAnnotation( Mutability.class );
if ( converterMutabilityAnn != null ) {
final ManagedBean<? extends MutabilityPlan<?>> jtdBean = managedBeanRegistry.getBean( converterMutabilityAnn.value() );
return jtdBean.getBeanInstance();
}
if ( converterDescriptor.getAttributeConverterClass().isAnnotationPresent( Immutable.class ) ) {
return ImmutableMutabilityPlan.instance();
}
@ -913,7 +933,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
.getService( ManagedBeanRegistry.class );
explicitJavaTypeAccess = typeConfiguration -> {
final JavaType javaTypeAnn = attributeXProperty.getAnnotation( JavaType.class );
final JavaType javaTypeAnn = findAnnotation( attributeXProperty, JavaType.class );
if ( javaTypeAnn != null ) {
final Class<? extends BasicJavaTypeDescriptor<?>> javaType = normalizeJavaType( javaTypeAnn.value() );
@ -923,6 +943,13 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
}
}
final Target targetAnn = findAnnotation( attributeXProperty, Target.class );
if ( targetAnn != null ) {
return (BasicJavaTypeDescriptor) typeConfiguration
.getJavaTypeDescriptorRegistry()
.getDescriptor( targetAnn.value() );
}
return null;
};
@ -1095,10 +1122,6 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
basicValue.setTemporalPrecision( temporalPrecision );
}
// todo (6.0) : explicit SqlTypeDescriptor / JDBC type-code
// todo (6.0) : explicit mutability / immutable
// todo (6.0) : explicit Comparator
linkWithValue();
boolean isInSecondPass = buildingContext.getMetadataCollector().isInSecondPass();
@ -1285,7 +1308,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
@Override
public Class<? extends UserType<?>> customType(XProperty xProperty) {
final CustomType customType = xProperty.getAnnotation( CustomType.class );
final CustomType customType = findAnnotation( xProperty, CustomType.class );
if ( customType == null ) {
return null;
}
@ -1295,7 +1318,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
@Override
public Parameter[] customTypeParameters(XProperty xProperty) {
final CustomType customType = xProperty.getAnnotation( CustomType.class );
final CustomType customType = findAnnotation( xProperty, CustomType.class );
if ( customType == null ) {
return null;
}
@ -1340,7 +1363,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
@Override
public Class<? extends UserType<?>> customType(XProperty xProperty) {
final MapKeyCustomType customType = xProperty.getAnnotation( MapKeyCustomType.class );
final MapKeyCustomType customType = findAnnotation( xProperty, MapKeyCustomType.class );
if ( customType == null ) {
return null;
}
@ -1350,7 +1373,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
@Override
public Parameter[] customTypeParameters(XProperty xProperty) {
final MapKeyCustomType customType = xProperty.getAnnotation( MapKeyCustomType.class );
final MapKeyCustomType customType = findAnnotation( xProperty, MapKeyCustomType.class );
if ( customType == null ) {
return null;
}
@ -1363,7 +1386,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
@Override
public Class<? extends UserType<?>> customType(XProperty xProperty) {
final CollectionIdCustomType customType = xProperty.getAnnotation( CollectionIdCustomType.class );
final CollectionIdCustomType customType = findAnnotation( xProperty, CollectionIdCustomType.class );
if ( customType == null ) {
return null;
}
@ -1373,7 +1396,7 @@ public class BasicValueBinder<T> implements JdbcTypeDescriptorIndicators {
@Override
public Parameter[] customTypeParameters(XProperty xProperty) {
final CollectionIdCustomType customType = xProperty.getAnnotation( CollectionIdCustomType.class );
final CollectionIdCustomType customType = findAnnotation( xProperty, CollectionIdCustomType.class );
if ( customType == null ) {
return null;
}

View File

@ -6,8 +6,11 @@
*/
package org.hibernate.cfg.annotations;
import java.lang.annotation.Annotation;
import java.lang.reflect.Member;
import org.hibernate.Internal;
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.annotations.common.reflection.java.JavaXMember;
@ -16,6 +19,7 @@ import org.hibernate.annotations.common.reflection.java.JavaXMember;
*
* @author Steve Ebersole
*/
@Internal
public final class HCANNHelper {
/**
@ -47,4 +51,35 @@ public final class HCANNHelper {
public static Member getUnderlyingMember(final JavaXMember jxProperty) {
return jxProperty.getMember();
}
/**
* Locate an annotation on an annotated member, allowing for composed annotations (meta-annotations).
*
* @implNote Searches only one level deep
*/
static <T extends Annotation> T findAnnotation(XAnnotatedElement xAnnotatedElement, Class<T> annotationType) {
// first, see if we can find it directly...
final T direct = xAnnotatedElement.getAnnotation( annotationType );
if ( direct != null ) {
return direct;
}
// or as composed...
for ( int i = 0; i < xAnnotatedElement.getAnnotations().length; i++ ) {
final Annotation annotation = xAnnotatedElement.getAnnotations()[ i ];
if ( annotationType.equals( annotation.getClass() ) ) {
// we would have found this on the direct search, so no need
// to check its meta-annotations
continue;
}
// we only check one level deep
final T metaAnn = annotation.annotationType().getAnnotation( annotationType );
if ( metaAnn != null ) {
return metaAnn;
}
}
return null;
}
}

View File

@ -0,0 +1,53 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.any.annotations;
import org.hibernate.annotations.Any;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Table;
@DomainModel( annotatedClasses = {
ComposedAnyDiscriminatorMappingsTests.EntityWithComposedAnyDiscriminatorMappings.class,
CharProperty.class,
StringProperty.class,
IntegerProperty.class
} )
@SessionFactory
public class ComposedAnyDiscriminatorMappingsTests {
@Test
public void testUsage(SessionFactoryScope scope) {
// atm this will blow up because the mapping will fail
scope.inTransaction( (session) -> {
session.createQuery( "select s from StringProperty s" ).list();
session.createQuery( "select ph from PropertyHolder ph" ).list();
} );
}
@Entity( name = "PropertyHolder" )
@Table( name = "t_any_discrim_composed" )
public static class EntityWithComposedAnyDiscriminatorMappings {
@Id
private Integer id;
private String name;
@Any
@Column(name = "property_type")
@JoinColumn(name = "property_id")
@PropertyDiscriminatorMapping
private Property property;
}
}

View File

@ -0,0 +1,33 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.any.annotations;
import java.lang.annotation.Retention;
import org.hibernate.annotations.AnyDiscriminator;
import org.hibernate.annotations.AnyDiscriminatorValue;
import org.hibernate.annotations.AnyKeyJavaClass;
import jakarta.persistence.DiscriminatorType;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@java.lang.annotation.Target({METHOD, FIELD})
@Retention( RUNTIME )
// this is the default behavior anyway, but let's check that it is found
@AnyDiscriminator( DiscriminatorType.STRING )
@AnyKeyJavaClass( Integer.class )
@AnyDiscriminatorValue( discriminator = "S", entity = StringProperty.class )
@AnyDiscriminatorValue( discriminator = "I", entity = IntegerProperty.class )
public @interface PropertyDiscriminatorMapping {
}

View File

@ -25,7 +25,6 @@ import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static com.ibm.icu.impl.ValidIdentifiers.Datatype.u;
import static org.hibernate.cfg.AvailableSettings.GENERATE_STATISTICS;
import static org.hibernate.cfg.AvailableSettings.USE_QUERY_CACHE;
import static org.hibernate.cfg.AvailableSettings.USE_SECOND_LEVEL_CACHE;
@ -34,7 +33,6 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
/**
* @author Gavin King
@ -353,7 +351,7 @@ public class MutableNaturalIdTest {
.using( "name", "steve" )
.using( "org", "hb" )
.load();
assertNotNull( u );
assertNotNull( beforeEvict );
assertEquals( statistics.getPrepareStatementCount(), 1 );
session.evict( beforeEvict );

View File

@ -14,6 +14,7 @@ import java.lang.annotation.Target;
import jakarta.persistence.SharedCacheMode;
import org.hibernate.boot.model.TypeContributor;
import org.hibernate.cache.spi.access.AccessType;
import org.hibernate.testing.orm.domain.DomainModelDescriptor;
@ -108,6 +109,8 @@ public @interface DomainModel {
AccessType accessType() default AccessType.READ_WRITE;
Class<? extends TypeContributor>[] typeContributors() default {};
@interface ExtraQueryImport {
String name();
Class<?> importedClass();

View File

@ -14,14 +14,16 @@ import java.util.Optional;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.internal.MetadataBuilderImpl;
import org.hibernate.boot.model.TypeContributor;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.internal.util.JavaHelper;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.RootClass;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.type.BlobType;
import org.hibernate.type.ClobType;
import org.hibernate.type.NClobType;
@ -88,6 +90,7 @@ public class DomainModelExtension
final DomainModel domainModelAnnotation = domainModelAnnotationWrapper.get();
final MetadataSources metadataSources = new MetadataSources( serviceRegistry );
final ManagedBeanRegistry managedBeanRegistry = serviceRegistry.getService( ManagedBeanRegistry.class );
for ( String annotatedPackageName : domainModelAnnotation.annotatedPackageNames() ) {
metadataSources.addPackage( annotatedPackageName );
@ -127,12 +130,20 @@ public class DomainModelExtension
metadataSources.addQueryImport( importedClass.getSimpleName(), importedClass );
}
MetadataImplementor metadataImplementor = (MetadataImplementor) metadataSources.buildMetadata();
final MetadataBuilderImpl metadataBuilder = (MetadataBuilderImpl) metadataSources.getMetadataBuilder();
for ( Class<? extends TypeContributor> contributorType : domainModelAnnotation.typeContributors() ) {
final TypeContributor contributor = managedBeanRegistry.getBean( contributorType ).getBeanInstance();
contributor.contribute( metadataBuilder, serviceRegistry );
}
MetadataImplementor metadataImplementor = metadataBuilder.build();
applyCacheSettings(
metadataImplementor,
domainModelAnnotation.overrideCacheStrategy(),
domainModelAnnotation.concurrencyStrategy()
);
return metadataImplementor;
};
}