diff --git a/README.adoc b/README.adoc index cc38a7d3069a..1a5f48f86e5b 100644 --- a/README.adoc +++ b/README.adoc @@ -18,7 +18,7 @@ See link:MAINTAINERS.md#ci[MAINTAINERS.md] for information about CI. == Building from sources -The build requires at least JDK 21, and produces Java 17 bytecode. +The build requires at least JDK 25, and produces Java 17 bytecode. Hibernate uses https://gradle.org[Gradle] as its build tool. See the _Gradle Primer_ section below if you are new to Gradle. diff --git a/changelog.txt b/changelog.txt index 5335460b4196..eacba2aab168 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,28 @@ Hibernate 7 Changelog ======================= +Changes in 7.2.0.CR4 (December 10, 2025) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/36476 + + +** Bug + * HHH-19980 In JTA after-completion callbacks may get ignored + * HHH-19979 processor should handle @NamedEntityGraph with defaulted name + * HHH-19975 Calling entityManager.find(clazz, id) with null id throws NullPointerException + * HHH-19972 OptionalTableUpdateOperation can fail on PostgreSQL < 15 and CockroachDB + * HHH-19963 Wrong references in entity fields with circular associations + * HHH-19958 `` tag in orm.xml is not implemented + * HHH-19955 Thread-safety issue in EntityEntryContext resulting in NullPointerException for Session.contains() calls. + * HHH-19843 Bean Validation may fail on operations with stateless session + * HHH-18871 Nested NativeQuery mappings causing 'Could not locate TableGroup' exception after migration + * HHH-18217 StatelessSession.upsert() for entity with all-null non-id fields, or no non-id field + +** Improvement + * HHH-19943 Comparison of generic nested EmbeddedId's fails for JPQL and Criteria API + * HHH-19215 Extends Dialect#addQueryHints to support straight_join syntax + Changes in 7.2.0.CR3 (November 25, 2025) ------------------------------------------------------------------------------------------------------------------------ diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 5278d3463e4d..05eb3569d4bc 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -222,15 +222,9 @@ pipeline { withEnv([ "DISABLE_REMOTE_GRADLE_CACHE=true" ]) { - def notesFiles = findFiles(glob: 'release_notes.md') - if ( notesFiles.length < 1 ) { - throw new IllegalStateException( "Could not locate `release_notes.md`" ) - } - if ( notesFiles.length > 1 ) { - throw new IllegalStateException( "Located more than 1 `release_notes.md`" ) - } + def ghReleaseNote = sh(script: 'realpath -e release_notes.md 2>/dev/null', returnStdout: true).trim() - sh ".release/scripts/publish.sh -j --notes=${notesFiles[0].path} ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH} " + sh ".release/scripts/publish.sh -j ${ghReleaseNote != '' ? '--notes=' + ghReleaseNote : ''} ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH} " } } } diff --git a/docker_db.sh b/docker_db.sh index bf8d8b088076..9dd075d35371 100755 --- a/docker_db.sh +++ b/docker_db.sh @@ -218,13 +218,19 @@ mariadb_setup() { echo "MySQL databases were successfully setup" } +POSTGRESQL_PLATFORM_OPTION="" +if [[ "$IS_OSX" == "true" ]]; then + # PostGIS images only support amd64, so we force emulation on macOS + POSTGRESQL_PLATFORM_OPTION="--platform linux/amd64" +fi + postgresql() { postgresql_18 } postgresql_13() { $CONTAINER_CLI rm -f postgres || true - $CONTAINER_CLI run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 --tmpfs /var/lib/postgresql/data -d ${DB_IMAGE_POSTGRESQL_13:-docker.io/postgis/postgis:13-3.1} \ + $CONTAINER_CLI run --name postgres ${POSTGRESQL_PLATFORM_OPTION} -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 --tmpfs /var/lib/postgresql/data -d ${DB_IMAGE_POSTGRESQL_13:-docker.io/postgis/postgis:13-3.1} \ -c fsync=off -c synchronous_commit=off -c full_page_writes=off -c shared_buffers=256MB -c maintenance_work_mem=256MB -c max_wal_size=1GB -c checkpoint_timeout=1d $CONTAINER_CLI exec postgres bash -c '/usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y && apt install -y postgresql-13-pgvector' postgresql_setup @@ -232,7 +238,7 @@ postgresql_13() { postgresql_14() { $CONTAINER_CLI rm -f postgres || true - $CONTAINER_CLI run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 --tmpfs /var/lib/postgresql/data -d ${DB_IMAGE_POSTGRESQL_14:-docker.io/postgis/postgis:14-3.3} \ + $CONTAINER_CLI run --name postgres ${POSTGRESQL_PLATFORM_OPTION} -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 --tmpfs /var/lib/postgresql/data -d ${DB_IMAGE_POSTGRESQL_14:-docker.io/postgis/postgis:14-3.3} \ -c fsync=off -c synchronous_commit=off -c full_page_writes=off -c shared_buffers=256MB -c maintenance_work_mem=256MB -c max_wal_size=1GB -c checkpoint_timeout=1d $CONTAINER_CLI exec postgres bash -c '/usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y && apt install -y postgresql-14-pgvector' postgresql_setup @@ -240,7 +246,7 @@ postgresql_14() { postgresql_15() { $CONTAINER_CLI rm -f postgres || true - $CONTAINER_CLI run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 --tmpfs /var/lib/postgresql/data -d ${DB_IMAGE_POSTGRESQL_15:-docker.io/postgis/postgis:15-3.3} \ + $CONTAINER_CLI run --name postgres ${POSTGRESQL_PLATFORM_OPTION} -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 --tmpfs /var/lib/postgresql/data -d ${DB_IMAGE_POSTGRESQL_15:-docker.io/postgis/postgis:15-3.3} \ -c fsync=off -c synchronous_commit=off -c full_page_writes=off -c shared_buffers=256MB -c maintenance_work_mem=256MB -c max_wal_size=1GB -c checkpoint_timeout=1d $CONTAINER_CLI exec postgres bash -c '/usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y && apt install -y postgresql-15-pgvector' postgresql_setup @@ -248,7 +254,7 @@ postgresql_15() { postgresql_16() { $CONTAINER_CLI rm -f postgres || true - $CONTAINER_CLI run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 --tmpfs /var/lib/postgresql/data -d ${DB_IMAGE_POSTGRESQL_16:-docker.io/postgis/postgis:16-3.4} \ + $CONTAINER_CLI run --name postgres ${POSTGRESQL_PLATFORM_OPTION} -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 --tmpfs /var/lib/postgresql/data -d ${DB_IMAGE_POSTGRESQL_16:-docker.io/postgis/postgis:16-3.4} \ -c fsync=off -c synchronous_commit=off -c full_page_writes=off -c shared_buffers=256MB -c maintenance_work_mem=256MB -c max_wal_size=1GB -c checkpoint_timeout=1d $CONTAINER_CLI exec postgres bash -c '/usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y && apt install -y postgresql-16-pgvector' postgresql_setup @@ -256,7 +262,7 @@ postgresql_16() { postgresql_17() { $CONTAINER_CLI rm -f postgres || true - $CONTAINER_CLI run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 --tmpfs /var/lib/postgresql/data -d ${DB_IMAGE_POSTGRESQL_17:-docker.io/postgis/postgis:17-3.5} \ + $CONTAINER_CLI run --name postgres ${POSTGRESQL_PLATFORM_OPTION} -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 --tmpfs /var/lib/postgresql/data -d ${DB_IMAGE_POSTGRESQL_17:-docker.io/postgis/postgis:17-3.5} \ -c fsync=off -c synchronous_commit=off -c full_page_writes=off -c shared_buffers=256MB -c maintenance_work_mem=256MB -c max_wal_size=1GB -c checkpoint_timeout=1d $CONTAINER_CLI exec postgres bash -c '/usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y && apt install -y postgresql-17-pgvector' postgresql_setup @@ -264,7 +270,7 @@ postgresql_17() { postgresql_18() { $CONTAINER_CLI rm -f postgres || true - $CONTAINER_CLI run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 --tmpfs /var/lib/postgresql -d ${DB_IMAGE_POSTGRESQL_17:-docker.io/postgis/postgis:18-3.6} \ + $CONTAINER_CLI run --name postgres ${POSTGRESQL_PLATFORM_OPTION} -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 --tmpfs /var/lib/postgresql -d ${DB_IMAGE_POSTGRESQL_18:-docker.io/postgis/postgis:18-3.6} \ -c fsync=off -c synchronous_commit=off -c full_page_writes=off -c shared_buffers=256MB -c maintenance_work_mem=256MB -c max_wal_size=1GB -c checkpoint_timeout=1d $CONTAINER_CLI exec postgres bash -c '/usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y && apt install -y postgresql-18-pgvector' postgresql_setup @@ -1282,7 +1288,68 @@ EOF } tidb() { - tidb_5_4 + tidb_8_5 +} + +tidb_8_5() { + $CONTAINER_CLI rm -f tidb || true + $CONTAINER_CLI run --name tidb -p4000:4000 -d ${DB_IMAGE_TIDB_8_5:-docker.io/pingcap/tidb:v8.5.4} + + # Wait for TiDB to start + OUTPUT= + n=0 + until [ "$n" -gt 15 ]; do + OUTPUT=$($CONTAINER_CLI logs tidb 2>&1) + if [[ $OUTPUT == *"server is running"* ]]; then + break; + fi + n=$((n+1)) + echo "Waiting for TiDB to start..." + sleep 5 + done + + if [ "$n" -gt 15 ]; then + echo "TiDB failed to start after 75 seconds" + exit 1 + else + echo "TiDB successfully started" + fi + + # Wait for TiDB to accept connections + n=0 + until [ "$n" -gt 10 ]; do + if $CONTAINER_CLI run --rm --network container:tidb docker.io/mysql:8.0 \ + mysqladmin -h 127.0.0.1 -P 4000 -uroot ping >/dev/null 2>&1; then + break; + fi + n=$((n+1)) + echo "Waiting for TiDB to be ready..." + sleep 3 + done + + if [ "$n" -gt 10 ]; then + echo "TiDB failed to become ready after 30 seconds" + exit 1 + else + echo "TiDB is ready" + fi + + # Create databases + databases=() + for n in $(seq 1 $DB_COUNT) + do + databases+=("hibernate_orm_test_${n}") + done + create_cmd= + create_cmd+="CREATE DATABASE IF NOT EXISTS hibernate_orm_test;" + create_cmd+="CREATE USER IF NOT EXISTS 'hibernate_orm_test'@'%' IDENTIFIED BY 'hibernate_orm_test';" + create_cmd+="GRANT ALL ON hibernate_orm_test.* TO 'hibernate_orm_test'@'%';" + for i in "${!databases[@]}";do + create_cmd+="CREATE DATABASE IF NOT EXISTS ${databases[i]}; GRANT ALL ON ${databases[i]}.* TO 'hibernate_orm_test'@'%';" + done + $CONTAINER_CLI run --rm --network container:tidb docker.io/mysql:8.0 \ + mysql -h 127.0.0.1 -P 4000 -uroot -e "${create_cmd}" 2>/dev/null + echo "TiDB databases were successfully setup" } tidb_5_4() { @@ -1370,6 +1437,38 @@ informix_12_10() { fi } +spanner() { + spanner_emulator +} + +spanner_emulator() { + + $CONTAINER_CLI rm -f spanner || true + # Run emulator (gRPC on 9010, REST on 9020) + $CONTAINER_CLI run --name spanner -d \ + -p 9010:9010 \ + -p 9020:9020 \ + ${SPANNER_EMULATOR:-gcr.io/cloud-spanner-emulator/emulator:1.5.45} + + # Wait for emulator to be ready (check logs for known messages) + n=0 + until [ "$n" -ge 20 ]; do + OUTPUT="$($CONTAINER_CLI logs spanner 2>&1 || true)" + if [[ "$OUTPUT" == *"gRPC server listening"* ]] || [[ "$OUTPUT" == *"Cloud Spanner emulator running"* ]]; then + echo "Cloud Spanner emulator started." + break + fi + echo "Waiting for Cloud Spanner emulator to start..." + n=$((n+1)) + sleep 3 + done + + if [ "$n" -ge 20 ]; then + echo "Cloud Spanner emulator failed to start after 1 minute" + exit 1 + fi +} + if [ -z ${1} ]; then echo "No db name provided" echo "Provide one of:" @@ -1413,10 +1512,13 @@ if [ -z ${1} ]; then echo -e "\tpostgresql_13" echo -e "\tsybase" echo -e "\ttidb" + echo -e "\ttidb_8_5" echo -e "\ttidb_5_4" echo -e "\informix" echo -e "\informix_14_10" echo -e "\informix_12_10" + echo -e "\tspanner" + echo -e "\tspanner_emulator" else ${1} fi diff --git a/documentation/src/main/asciidoc/introduction/Advanced.adoc b/documentation/src/main/asciidoc/introduction/Advanced.adoc index e874f867d68d..f9e6b165aabf 100644 --- a/documentation/src/main/asciidoc/introduction/Advanced.adoc +++ b/documentation/src/main/asciidoc/introduction/Advanced.adoc @@ -886,6 +886,17 @@ On the other hand, the following annotations specify how a collection should be Under the covers, Hibernate uses a `TreeSet` or `TreeMap` to maintain the collection in sorted order. +[CAUTION] +==== +The unowned (`mappedBy`) side of a bidirectional association is not responsible for specifying column mappings. +So it's wrong in principle to use `@OrderColumn` or `@MapKeyColumn` on the unowned side of an association mapping. +But for unowned collections, we may use `@OrderBy` or `@MapKey` instead. +That is: + +- You can use `@OrderColumn` or `@MapKeyColumn` with an `@ElementCollection`, owned `@ManyToMany`, or owned `@OneToMany`. +- But use `@OrderBy` or `@MapKey` when it's an _unowned_ `@ManyToMany` or `@OneToMany`. +==== + [[any]] === Any mappings diff --git a/documentation/src/main/asciidoc/introduction/Configuration.adoc b/documentation/src/main/asciidoc/introduction/Configuration.adoc index 26af305533cd..e9a412b316e0 100644 --- a/documentation/src/main/asciidoc/introduction/Configuration.adoc +++ b/documentation/src/main/asciidoc/introduction/Configuration.adoc @@ -116,8 +116,8 @@ and `org.ehcache:ehcache` and `com.github.ben-manes.caffeine:jcache` | Distributed second-level cache support via {infinispan}[Infinispan] | `org.infinispan:infinispan-hibernate-cache-v60` // | SCRAM authentication support for PostgreSQL | `com.ongres.scram:client:2.1` -| A JSON serialization library for working with JSON datatypes, for example, {jackson}[Jackson] or {yasson}[Yasson] | -`com.fasterxml.jackson.core:jackson-databind` + +| A JSON serialization library for working with JSON datatypes, for example, {jackson}[Jackson 2], {jackson3}[Jackson 3] or {yasson}[Yasson] | +`com.fasterxml.jackson.core:jackson-databind`, `tools.jackson.core:jackson-databind` + or `org.eclipse:yasson` | <> | `org.hibernate.orm:hibernate-spatial` | <>, for auditing historical data | `org.hibernate.orm:hibernate-envers` diff --git a/documentation/src/main/asciidoc/introduction/Entities.adoc b/documentation/src/main/asciidoc/introduction/Entities.adoc index 8ad97a494825..5b025d478766 100644 --- a/documentation/src/main/asciidoc/introduction/Entities.adoc +++ b/documentation/src/main/asciidoc/introduction/Entities.adoc @@ -526,6 +526,21 @@ Hibernate automatically generates a `UNIQUE` constraint on the columns mapped by Consider using the natural id attributes to implement <>. ==== +In cases where the natural id is defined by multiple attributes, Hibernate also offers the link:{doc-javadoc-url}org/hibernate/annotations/NaturalIdClass.html[`@NaturalIdClass`] annotation which acts similarly to the Jakarta Persistence `@IdClass` annotation for <> - + +[source,java] +---- +record BookKey(String isbn, int printing) {} + +@Entity +@NaturalIdClass(BookKey.class) +class Book { + ... +} +---- + +See the link:{doc-user-guide-url}#naturalid[User Guide] for more details about natural ids. + The payoff for doing this extra work, as we will see <>, is that we can take advantage of optimized natural id lookups that make use of the second-level cache. Note that even when you've identified a natural key, we still recommend the use of a generated surrogate key in foreign keys, since this makes your data model _much_ easier to change. @@ -586,6 +601,7 @@ Hibernate slightly extends this list with the following types: | Additional date/time types | `java.time` | `Duration`, `ZoneId`, `ZoneOffset`, and even `ZonedDateTime` | JDBC LOB types | `java.sql` | `Blob`, `Clob`, `NClob` | Java class object | `java.lang` | `Class` +| Internet addresses | `java.net` | `InetAddress` | Miscellaneous types | `java.util` | `Currency`, `Locale`, `URL`, `TimeZone` |==== diff --git a/documentation/src/main/asciidoc/introduction/Interacting.adoc b/documentation/src/main/asciidoc/introduction/Interacting.adoc index f6ac49148954..68eb7284528f 100644 --- a/documentation/src/main/asciidoc/introduction/Interacting.adoc +++ b/documentation/src/main/asciidoc/introduction/Interacting.adoc @@ -276,6 +276,7 @@ Modifications are automatically detected when the session is <>. On the other hand, except for `getReference()`, the following operations all result in immediate access to the database: +[[methods-for-reading]] .Methods for reading and locking data [%breakable,cols="30,~"] |=== @@ -368,9 +369,30 @@ The following code results in a single SQL `select` statement: List books = session.findMultiple(Book.class, bookIds); ---- +[[load-by-natural-id]] +As discussed <>, Hibernate offers the ability to map a natural id and perform load operations using that natural id. +This is accomplished using the `KeyType#NATURAL` `FindOption` - + +[source,java] +---- +var bookKey = new BookKey(...); +var book = session.find(Book.class, bookKey, NATURAL); +var books = session.findMultiple(Book.class, List.of(bookKey), NATURAL); +---- + +When loading by natural id, the type of value accepted depends on the type of natural id. +For single-attribute natural ids, whether defined by a basic or embedded type, the attribute type should be used. +For multi-attribute natural ids, Hibernate will accept a number of forms: + +* If a link:{doc-javadoc-url}org/hibernate/annotations/NaturalIdClass.html[`@NaturalIdClass`] is defined, an instance of the natural id class may be used. +* An array of the individual attribute values, ordered alphabetically by name, may be used. +* A `Map` of the individual attribute values, keyed by the attribute name, may be used. + Each of the operations we've seen so far affects a single entity instance passed as an argument. But there's a way to set things up so that an operation will propagate to associated entities. +See the link:{doc-user-guide-url}#find-by-natural-id[User Guide] for more details about loading by natural ids. + [[cascade]] === Cascading persistence operations @@ -595,7 +617,8 @@ Therefore, Hibernate has some APIs that streamline certain more complicated look [NOTE] ==== -Since the introduction of `FindOption` in JPA 3.2, `byId()` is now much less useful. +Since the introduction of `FindOption` in JPA 3.2, `byId()` is now much less useful and deprecated. +Instead, use `find()` and `findMultiple()` as discussed <>. ==== Batch loading is very useful when we need to retrieve multiple instances of the same entity class by id: @@ -627,6 +650,8 @@ We also have some operations for working with lookups by <> may be used to choose between 0-based and 1-based indexing on the database side. [[collections-bidirectional-ordered-list]] ===== Bidirectional ordered lists @@ -701,54 +703,6 @@ include::{example-dir-collection}/BidirectionalOrderByListTest.java[tags=collect Just like with the unidirectional `@OrderBy` list, the `number` column is used to order the statement on the SQL level. -When using the `@OrderColumn` annotation, the `order_id` column is going to be embedded in the child table: - -[[collections-bidirectional-ordered-list-order-column-example]] -.Bidirectional `@OrderColumn` list -==== -[source,java] ----- -include::{example-dir-collection}/BidirectionalOrderColumnListTest.java[tags=collections-bidirectional-ordered-list-order-column-example,indent=0] ----- - -[source,sql] ----- -include::{extrasdir}/collections-bidirectional-ordered-list-order-column-example.sql[] ----- -==== - -When fetching the collection, Hibernate will use the fetched ordered columns to sort the elements according to the `@OrderColumn` mapping. - -[[collections-customizing-ordered-list-ordinal]] -===== Customizing ordered list ordinal - -You can customize the ordinal of the underlying ordered list by using the https://docs.hibernate.org/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/ListIndexBase.html[`@ListIndexBase`] annotation. - -[[collections-customizing-ordered-list-ordinal-mapping-example]] -.`@ListIndexBase` mapping example -==== -[source,java] ----- -include::{example-dir-collection}/OrderColumnListIndexBaseTest.java[tags=collections-customizing-ordered-list-ordinal-mapping-example,indent=0] ----- -==== - -When inserting two `Phone` records, Hibernate is going to start the List index from 100 this time. - -[[collections-customizing-ordered-list-ordinal-persist-example]] -.`@ListIndexBase` persist example -==== -[source,java] ----- -include::{example-dir-collection}/OrderColumnListIndexBaseTest.java[tags=collections-customizing-ordered-list-ordinal-persist-example,indent=0] ----- - -[source,sql] ----- -include::{extrasdir}/collections-customizing-ordered-list-ordinal-persist-example.sql[] ----- -==== - [[collections-customizing-ordered-by-sql-clause]] ===== Customizing ORDER BY SQL clause diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/natural_id.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/natural_id.adoc index 5641caec6619..142ea62774b1 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/natural_id.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/natural_id.adoc @@ -1,22 +1,15 @@ [[naturalid]] === Natural Ids :core-project-dir: {root-project-dir}/hibernate-core -:example-dir-naturalid: {core-project-dir}/src/test/java/org/hibernate/orm/test/mapping/identifier +:core-test-base-dir: {core-project-dir}/src/test/java/org/hibernate/orm/test +:example-dir-naturalid: {core-test-base-dir}/mapping/identifier :jcache-project-dir: {root-project-dir}/hibernate-jcache :example-dir-caching: {jcache-project-dir}/src/test/java/org/hibernate/orm/test/caching :extrasdir: extras -Natural ids represent domain model unique identifiers that have a meaning in the real world too. +Natural ids are unique identifiers in the domain model that have a meaning in the real world too. Even if a natural id does not make a good primary key (surrogate keys being usually preferred), it's still useful to tell Hibernate about it. -As we will see later, Hibernate provides a dedicated, efficient API for loading an entity by its natural id much like it offers for loading by identifier (PK). - -[IMPORTANT] -==== -All values used in a natural id must be non-nullable. - -For natural id mappings using a to-one association, this precludes the use of not-found -mappings which effectively define a nullable mapping. -==== +As we will see <>, Hibernate provides an efficient means for loading an entity by its natural id much like it offers for loading by identifier (PK). [[naturalid-mapping]] ==== Natural Id Mapping @@ -50,104 +43,97 @@ include::{example-dir-naturalid}/MultipleNaturalIdTest.java[tags=naturalid-multi ---- ==== -[[naturalid-api]] -==== Natural Id API - -As stated before, Hibernate provides an API for loading entities by their associated natural id. -This is represented by the `org.hibernate.NaturalIdLoadAccess` contract obtained via Session#byNaturalId. +Natural ids defined using multiple persistent attributes may also define a link:{doc-javadoc-url}org/hibernate/annotations/NaturalIdClass.html[`@NaturalIdClass`] which can be used for <>. -[NOTE] -==== -If the entity does not define a natural id, trying to load an entity by its natural id will throw an exception. -==== - -[[naturalid-load-access-example]] -.Using NaturalIdLoadAccess +[[naturalidclass-example]] +.Natural id with @NaturalIdClass ==== [source,java] ---- -include::{example-dir-naturalid}/SimpleNaturalIdTest.java[tags=naturalid-load-access-example,indent=0] +include::{core-test-base-dir}/mapping/naturalid/idclass/SimpleNaturalIdClassTests.java[tags=naturalidclass-mapping-example,indent=0] ---- +==== -[source,java] ----- -include::{example-dir-naturalid}/CompositeNaturalIdTest.java[tags=naturalid-load-access-example,indent=0] ----- +[[natural-id-mutability]] +==== Natural id mutability + +A natural id may be mutable or immutable. By default, the `@NaturalId` annotation marks the attribute as immutable. +An immutable natural id is expected to never change its value. +In fact, Hibernate will check at flush-time to ensure that the value has not been altered. + +If the value(s) of the natural id attribute(s) may change, `@NaturalId(mutable = true)` should be used instead. + +[[naturalid-mutable-mapping-example]] +.Mutable natural id mapping +==== [source,java] ---- -include::{example-dir-naturalid}/MultipleNaturalIdTest.java[tags=naturalid-load-access-example,indent=0] +include::{example-dir-naturalid}/MutableNaturalIdTest.java[tags=naturalid-mutable-mapping-example,indent=0] ---- ==== -NaturalIdLoadAccess offers 2 distinct methods for obtaining the entity: - -`load()`:: obtains a reference to the entity, making sure that the entity state is initialized. -`getReference()`:: obtains a reference to the entity. The state may or may not be initialized. -If the entity is already associated with the current running Session, that reference (loaded or not) is returned. -If the entity is not loaded in the current Session and the entity supports proxy generation, an uninitialized proxy is generated and returned, otherwise the entity is loaded from the database and returned. - -`NaturalIdLoadAccess` allows loading an entity by natural id and at the same time applies a pessimistic lock. -For additional details on locking, see the <> chapter. -We will discuss the last method available on NaturalIdLoadAccess ( `setSynchronizationEnabled()` ) in <>. +[[natural-id-caching]] +==== Natural id resolution caching -Because the `Book` entities in the first two examples define "simple" natural ids, we can load them as follows: +Within the Session, Hibernate maintains a cross reference of the resolutions from natural id values to entity identifier (PK) values. +We can also have this value resolution cached in the second level cache if second level caching is enabled. -[[naturalid-simple-load-access-example]] -.Loading by simple natural id +[[naturalid-caching]] +.Natural id caching ==== [source,java] ---- -include::{example-dir-naturalid}/SimpleNaturalIdTest.java[tags=naturalid-simple-load-access-example,indent=0] +include::{example-dir-caching}/CacheableNaturalIdTest.java[tags=naturalid-cacheable-mapping-example,indent=0] ---- +==== -[source,java] ----- -include::{example-dir-naturalid}/CompositeNaturalIdTest.java[tags=naturalid-simple-load-access-example,indent=0] ----- +[IMPORTANT] +==== +Think carefully before caching resolutions for natural ids which are partially or fully <> in the second level cache as this will often have a negative impact on performance. ==== -Here we see the use of the `org.hibernate.SimpleNaturalIdLoadAccess` contract, -obtained via `Session#bySimpleNaturalId()`. -`SimpleNaturalIdLoadAccess` is similar to `NaturalIdLoadAccess` except that it does not define the using method. -Instead, because these _simple_ natural ids are defined based on just one attribute we can directly pass -the corresponding natural id attribute value directly to the `load()` and `getReference()` methods. + +[[find-by-natural-id]] +[[naturalid-api]] +==== Loading by natural id + +Hibernate provides a means to load one or more entities by natural id using the `KeyType.NATURAL` `FindOption` passed to `find()` or `findMultiple()`. [NOTE] ==== -If the entity does not define a natural id, or if the natural id is not of a "simple" type, an exception will be thrown there. +Hibernate historically offered the dedicated `byNaturalId()`, `bySimpleNaturalId()` and `byMultipleNaturalId()` APIs for loading one or more entities by natural id using its legacy "load access" approach. However, with JPA 3.2 and the introduction of `FindOption`, etc., these "load access" approaches are considered deprecated and are not discussed here. ==== -[[naturalid-mutability-caching]] -==== Natural Id - Mutability and Caching - -A natural id may be mutable or immutable. By default the `@NaturalId` annotation marks an immutable natural id attribute. -An immutable natural id is expected to never change its value. -If the value(s) of the natural id attribute(s) change, `@NaturalId(mutable = true)` should be used instead. - -[[naturalid-mutable-mapping-example]] -.Mutable natural id mapping +[[find-by-natural-id-example]] +.Loading by natural id ==== [source,java] ---- -include::{example-dir-naturalid}/MutableNaturalIdTest.java[tags=naturalid-mutable-mapping-example,indent=0] +include::{example-dir-naturalid}/SimpleNaturalIdTest.java[tags=naturalid-loading-example,indent=0] ---- ==== -Within the Session, Hibernate maintains a mapping from natural id values to entity identifiers (PK) values. -If natural ids values changed, it is possible for this mapping to become out of date until a flush occurs. +When loading by natural id, the type of value accepted depends on the definition of the natural id. -To work around this condition, Hibernate will attempt to discover any such pending changes and adjust them when the `load()` or `getReference()` methods are executed. -To be clear: this is only pertinent for mutable natural ids. +* For single-attribute natural ids, whether defined by a basic or embedded type, the attribute type should be used. +* For multi-attribute natural ids, Hibernate will accept a number of forms: + +** If a link:{doc-javadoc-url}org/hibernate/annotations/NaturalIdClass.html[`@NaturalIdClass`] is defined, an instance of the natural id class may be used. +** An array of the individual attribute values, ordered alphabetically by name, may be used. +** A `Map` of the individual attribute values, keyed by the attribute name, may be used. + +There are a few differences to be aware of when loading by natural id compared to loading by primary key. Most importantly, if the natural id is mutable and its values have changed, it is possible for the resolution caching to become out of date until a flush occurs resulting in incorrect results. +To work around this condition, Hibernate will attempt to discover any such pending changes and adjust them prior to performing the load. [IMPORTANT] ==== -This _discovery and adjustment_ have a performance impact. -If you are certain that none of the mutable natural ids already associated with the current `Session` have changed, you can disable this checking by calling `setSynchronizationEnabled(false)` (the default is `true`). -This will force Hibernate to circumvent the checking of mutable natural ids. +This _discovery and adjustment_ (synchronization) has a performance impact. +If you are certain that none of the mutable natural ids already associated with the current `Session` have changed, you can disable this using the `NaturalIdSynchronization.DISABLED` option which will force Hibernate to skip the checking of mutable natural ids. +To be clear: this is only pertinent for mutable natural ids. ==== [[naturalid-mutable-synchronized-example]] @@ -159,13 +145,3 @@ include::{example-dir-naturalid}/MutableNaturalIdTest.java[tags=naturalid-mutabl ---- ==== -Not only can this NaturalId-to-PK resolution be cached in the Session, but we can also have it cached in the second-level cache if second level caching is enabled. - -[[naturalid-caching]] -.Natural id caching -==== -[source,java] ----- -include::{example-dir-caching}/CacheableNaturalIdTest.java[tags=naturalid-cacheable-mapping-example,indent=0] ----- -==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc index ae6daa913311..51bb150442e7 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -1220,6 +1220,8 @@ The following functions deal with SQL array types, which are not supported on ev | <> | Creates a sub-array of the based on lower and upper index | <> | Creates array copy replacing a given element with another | <> | Creates array copy trimming the last _N_ elements +| <> | Returns a copy of the array with elements in reverse order +| <> | Returns a sorted copy of the array | <> | Creates array filled with the same element _N_ times | <> | Like `array_fill`, but returns the result as `List` | <> | String representation of array @@ -1596,6 +1598,46 @@ include::{array-example-dir-hql}/ArrayTrimTest.java[tags=hql-array-trim-example] ---- ==== +[[hql-array-reverse-functions]] +===== `array_reverse()` + +Returns a copy of the array with elements in reverse order. Returns `null` if the argument is `null`. + +[[hql-array-reverse-example]] +==== +[source, java, indent=0] +---- +include::{array-example-dir-hql}/ArrayReverseTest.java[tags=hql-array-reverse-example] +---- +==== + +[[hql-array-sort-functions]] +===== `array_sort()` + +Returns a sorted copy of the array. When called with no optional arguments, elements are sorted in ascending order with `null` elements placed last. +The optional second argument allows specifying descending order, and the optional third argument controls the position of `null` elements. +Returns `null` if the first argument is `null`. + +[[hql-array-sort-example]] +==== +[source, java, indent=0] +---- +include::{array-example-dir-hql}/ArraySortTest.java[tags=hql-array-sort-example] +---- +==== + +The second argument controls sort direction: `false` for ascending (default), `true` for descending. +The third argument controls `null` placement: `false` for nulls last, `true` for nulls first. +When the third argument is omitted, it defaults to the value of the second argument. + +[[hql-array-sort-descending-nulls-last-example]] +==== +[source, java, indent=0] +---- +include::{array-example-dir-hql}/ArraySortTest.java[tags=hql-array-sort-descending-nulls-last-example] +---- +==== + [[hql-array-fill-functions]] ===== `array_fill()` and `array_fill_list()` diff --git a/gradle/version.properties b/gradle/version.properties index 8f31599e762f..e7d6eb152e2d 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=7.2.0-SNAPSHOT \ No newline at end of file +hibernateVersion=7.3.0-SNAPSHOT \ No newline at end of file diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java index 83b691ec7557..f58eeb2cdb96 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java @@ -141,6 +141,7 @@ * A {@linkplain Dialect SQL dialect} for CockroachDB. * * @author Gavin King + * @author Yoobin Yoon */ public class CockroachLegacyDialect extends Dialect { @@ -405,7 +406,7 @@ protected void contributeCockroachTypes(TypeContributions typeContributions, Ser ObjectNullAsBinaryTypeJdbcType.INSTANCE, typeContributions.getTypeConfiguration() .getJavaTypeRegistry() - .getDescriptor( Object.class ) + .resolveDescriptor( Object.class ) ) ); @@ -485,6 +486,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.arraySlice_operator(); functionFactory.arrayReplace(); functionFactory.arrayTrim_unnest(); + functionFactory.arrayReverse_unnest(); + functionFactory.arraySort_unnest(); functionFactory.arrayFill_cockroachdb(); functionFactory.arrayToString_postgresql(); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java index 82810235d43e..04270bd3699b 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java @@ -1135,7 +1135,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry ObjectNullResolvingJdbcType.INSTANCE, typeContributions.getTypeConfiguration() .getJavaTypeRegistry() - .getDescriptor( Object.class ) + .resolveDescriptor( Object.class ) ) ); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyDialect.java index 5cb7bc92015d..ab10429cd830 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyDialect.java @@ -671,7 +671,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry ObjectNullResolvingJdbcType.INSTANCE, typeContributions.getTypeConfiguration() .getJavaTypeRegistry() - .getDescriptor( Object.class ) + .resolveDescriptor( Object.class ) ) ); } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java index 38c77c1a2b65..2d31d2f6b2bf 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java @@ -666,7 +666,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry ObjectNullResolvingJdbcType.INSTANCE, typeContributions.getTypeConfiguration() .getJavaTypeRegistry() - .getDescriptor( Object.class ) + .resolveDescriptor( Object.class ) ) ); } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBArrayJdbcType.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBArrayJdbcType.java index 0f8f03695b30..f40daa2aa0d9 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBArrayJdbcType.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBArrayJdbcType.java @@ -10,7 +10,6 @@ import java.sql.Types; import org.hibernate.HibernateException; -import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BasicPluralJavaType; @@ -35,63 +34,68 @@ public GaussDBArrayJdbcType(JdbcType elementJdbcType) { @Override public ValueBinder getBinder(final JavaType javaTypeDescriptor) { - @SuppressWarnings("unchecked") - final BasicPluralJavaType pluralJavaType = (BasicPluralJavaType) javaTypeDescriptor; - final ValueBinder elementBinder = getElementJdbcType().getBinder( pluralJavaType.getElementJavaType() ); - return new BasicBinder<>( javaTypeDescriptor, this ) { + return new Binder<>( javaTypeDescriptor, + (BasicPluralJavaType) javaTypeDescriptor ); + } - @Override - protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { - st.setArray( index, getArray( value, options ) ); - } + private class Binder extends BasicBinder { + private final BasicPluralJavaType pluralJavaType; - @Override - protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) - throws SQLException { - final java.sql.Array arr = getArray( value, options ); - try { - st.setObject( name, arr, java.sql.Types.ARRAY ); - } - catch (SQLException ex) { - throw new HibernateException( "JDBC driver does not support named parameters for setArray. Use positional.", ex ); - } - } + private Binder(JavaType javaType, BasicPluralJavaType pluralJavaType) { + super( javaType, GaussDBArrayJdbcType.this ); + this.pluralJavaType = pluralJavaType; + } + + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + st.setArray( index, getArray( value, options ) ); + } - @Override - public Object getBindValue(X value, WrapperOptions options) throws SQLException { - return ( (GaussDBArrayJdbcType) getJdbcType() ).getArray( this, elementBinder, value, options ); + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + final java.sql.Array arr = getArray( value, options ); + try { + st.setObject( name, arr, Types.ARRAY ); } + catch (SQLException ex) { + throw new HibernateException( + "JDBC driver does not support named parameters for setArray. Use positional.", ex ); + } + } + + @Override + public Object[] getBindValue(X value, WrapperOptions options) throws SQLException { + final var elementBinder = getElementJdbcType().getBinder( pluralJavaType.getElementJavaType() ); + return convertToArray( this, elementBinder, pluralJavaType, value, options ); + } - private java.sql.Array getArray(X value, WrapperOptions options) throws SQLException { - final GaussDBArrayJdbcType arrayJdbcType = (GaussDBArrayJdbcType) getJdbcType(); - final Object[] objects; + private java.sql.Array getArray(X value, WrapperOptions options) throws SQLException { + final var session = options.getSession(); + return session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection() + .createArrayOf( getElementTypeName( getJavaType(), session ), + elements( value, options, GaussDBArrayJdbcType.this ) ); + } - final JdbcType elementJdbcType = arrayJdbcType.getElementJdbcType(); - if ( elementJdbcType instanceof AggregateJdbcType ) { - // The GaussDB JDBC driver does not support arrays of structs, which contain byte[] - final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) elementJdbcType; - final Object[] domainObjects = getJavaType().unwrap( - value, - Object[].class, - options - ); - objects = new Object[domainObjects.length]; - for ( int i = 0; i < domainObjects.length; i++ ) { - if ( domainObjects[i] != null ) { - objects[i] = aggregateJdbcType.createJdbcValue( domainObjects[i], options ); - } + private Object[] elements(X value, WrapperOptions options, GaussDBArrayJdbcType arrayJdbcType) + throws SQLException { + final var elementJdbcType = arrayJdbcType.getElementJdbcType(); + if ( elementJdbcType instanceof AggregateJdbcType aggregateJdbcType ) { + // The GaussDB JDBC driver does not support arrays of structs, which contain byte[] + final var domainObjects = getJavaType().unwrap( value, Object[].class, options ); + final var objects = new Object[domainObjects.length]; + for ( int i = 0; i < domainObjects.length; i++ ) { + if ( domainObjects[i] != null ) { + objects[i] = aggregateJdbcType.createJdbcValue( domainObjects[i], options ); } } - else { - objects = arrayJdbcType.getArray( this, elementBinder, value, options ); - } - - final SharedSessionContractImplementor session = options.getSession(); - final String typeName = arrayJdbcType.getElementTypeName( getJavaType(), session ); - return session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection() - .createArrayOf( typeName, objects ); + return objects; + } + else { + return getBindValue( value, options ); } - }; + } } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBDialect.java index ec1882995618..c4abd86b5dc1 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBDialect.java @@ -1270,7 +1270,7 @@ protected void contributeGaussDBTypes(TypeContributions typeContributions) { ObjectNullAsBinaryTypeJdbcType.INSTANCE, typeContributions.getTypeConfiguration() .getJavaTypeRegistry() - .getDescriptor( Object.class ) + .resolveDescriptor( Object.class ) ) ); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java index 8df31e5d4d5d..5858a24a105d 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java @@ -128,6 +128,7 @@ * * @author Thomas Mueller * @author Jürgen Kreitler + * @author Yoobin Yoon */ public class H2LegacyDialect extends Dialect { @@ -409,6 +410,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.arraySlice(); functionFactory.arrayReplace_h2( getMaximumArraySize() ); functionFactory.arrayTrim_trim_array(); + functionFactory.arrayReverse_h2( getMaximumArraySize() ); + functionFactory.arraySort_h2( getMaximumArraySize() ); functionFactory.arrayFill_h2(); functionFactory.arrayToString_h2( getMaximumArraySize() ); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HANALegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HANALegacyDialect.java index 2f93f805a350..a2569b8f4dbf 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HANALegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HANALegacyDialect.java @@ -935,6 +935,11 @@ public Identifier toIdentifier(String text, boolean quoted) { return normalizeQuoting( Identifier.toIdentifier( text, quoted ) ); } + @Override + public Identifier toIdentifier(String text, boolean quoted, boolean isExplicit) { + return normalizeQuoting( Identifier.toIdentifier( text, quoted, isExplicit ) ); + } + @Override public Identifier normalizeQuoting(Identifier identifier) { Identifier normalizedIdentifier = this.helper.normalizeQuoting( identifier ); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java index 6b53fe514060..c55d6f81a2d0 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java @@ -95,6 +95,7 @@ * @author Christoph Sturm * @author Phillip Baird * @author Fred Toussi + * @author Yoobin Yoon */ public class HSQLLegacyDialect extends Dialect { @@ -269,6 +270,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.arraySlice_unnest(); functionFactory.arrayReplace_unnest(); functionFactory.arrayTrim_trim_array(); + functionFactory.arrayReverse_unnest(); + functionFactory.arraySort_hsql(); functionFactory.arrayFill_hsql(); functionFactory.arrayToString_hsql(); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java index 18b4ba25d4a7..42b1fa0f8c8d 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java @@ -473,7 +473,9 @@ protected SqlAstTranslator buildTranslator( @Override public String extractPattern(TemporalUnit unit) { return switch ( unit ) { - case SECOND -> getVersion().isBefore( 11, 70 ) ? "to_number(to_char(?2,'%S%F3'))" : "to_number(to_char(?2,'%S.%F3'))"; + case SECOND -> getVersion().isBefore( 11, 70 ) + ? "to_number(to_char(?2,'%S%F3'))" + : "to_number(to_char(?2,'%S.%F3'))"; case MINUTE -> "to_number(to_char(?2,'%M'))"; case HOUR -> "to_number(to_char(?2,'%H'))"; case DAY_OF_WEEK -> "(weekday(?2)+1)"; @@ -543,7 +545,8 @@ public String getAddPrimaryKeyConstraintString(String constraintName) { @Override public String getTruncateTableStatement(String tableName) { - return super.getTruncateTableStatement( tableName ) + " reuse storage" + return super.getTruncateTableStatement( tableName ) + + " reuse storage" + ( getVersion().isSameOrAfter( 12, 10 ) ? " keep statistics" : "" ); } @@ -778,7 +781,9 @@ public boolean isCurrentTimestampSelectStringCallable() { @Override public String getCurrentTimestampSelectString() { - return "select sysdate" + (getVersion().isBefore( 12, 10 ) ? " from informix.systables where tabid=1" : ""); + return getVersion().isBefore( 12, 10 ) + ? "select sysdate from informix.systables where tabid=1" + : "select sysdate"; } @Override @SuppressWarnings("deprecation") @@ -1128,7 +1133,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry ObjectNullAsBinaryTypeJdbcType.INSTANCE, typeContributions.getTypeConfiguration() .getJavaTypeRegistry() - .getDescriptor( Object.class ) + .resolveDescriptor( Object.class ) ) ); } @@ -1194,6 +1199,8 @@ public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, @ @Override public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() { - return getVersion().isSameOrAfter( 12,10 ) ? DmlTargetColumnQualifierSupport.TABLE_ALIAS : DmlTargetColumnQualifierSupport.NONE; + return getVersion().isSameOrAfter( 12,10 ) + ? DmlTargetColumnQualifierSupport.TABLE_ALIAS + : DmlTargetColumnQualifierSupport.NONE; } } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java index 0e9fadbb6f8c..66e67c1ac372 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java @@ -756,7 +756,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry NullJdbcType.INSTANCE, typeContributions.getTypeConfiguration() .getJavaTypeRegistry() - .getDescriptor( Object.class ) + .resolveDescriptor( Object.class ) ) ); } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java index c7a0a2d09167..1fd3c6f1a1ad 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java @@ -170,6 +170,7 @@ * @author Steve Ebersole * @author Gavin King * @author Loïc Lefèvre + * @author Yoobin Yoon */ public class OracleLegacyDialect extends Dialect { @@ -384,6 +385,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.arraySlice_oracle(); functionFactory.arrayReplace_oracle(); functionFactory.arrayTrim_oracle(); + functionFactory.arrayReverse_oracle(); + functionFactory.arraySort_oracle(); functionFactory.arrayFill_oracle(); functionFactory.arrayToString_oracle(); @@ -1029,7 +1032,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry NullJdbcType.INSTANCE, typeContributions.getTypeConfiguration() .getJavaTypeRegistry() - .getDescriptor( Object.class ) + .resolveDescriptor( Object.class ) ) ); typeContributions.contributeType( @@ -1037,7 +1040,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry ObjectNullAsNullTypeJdbcType.INSTANCE, typeContributions.getTypeConfiguration() .getJavaTypeRegistry() - .getDescriptor( Object.class ) + .resolveDescriptor( Object.class ) ) ); } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java index 9d29ba01e9c3..d5cc07b6270f 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java @@ -171,6 +171,7 @@ * A {@linkplain Dialect SQL dialect} for PostgreSQL 8 and above. * * @author Gavin King + * @author Yoobin Yoon */ public class PostgreSQLLegacyDialect extends Dialect { @@ -668,6 +669,14 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio else { functionFactory.arrayTrim_unnest(); } + if ( getVersion().isSameOrAfter( 18 ) ) { + functionFactory.arrayReverse(); + functionFactory.arraySort(); + } + else { + functionFactory.arrayReverse_unnest(); + functionFactory.arraySort_unnest(); + } functionFactory.arrayFill_postgresql(); functionFactory.arrayToString_postgresql(); @@ -1558,7 +1567,7 @@ protected void contributePostgreSQLTypes(TypeContributions typeContributions, Se ObjectNullAsBinaryTypeJdbcType.INSTANCE, typeContributions.getTypeConfiguration() .getJavaTypeRegistry() - .getDescriptor( Object.class ) + .resolveDescriptor( Object.class ) ) ); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SingleStoreDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SingleStoreDialect.java index 76d2f97ecce8..f33e9d4cefa4 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SingleStoreDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SingleStoreDialect.java @@ -665,7 +665,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry NullJdbcType.INSTANCE, typeContributions.getTypeConfiguration() .getJavaTypeRegistry() - .getDescriptor( Object.class ) + .resolveDescriptor( Object.class ) ) ); jdbcTypeRegistry.addDescriptor( EnumJdbcType.INSTANCE ); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java index d025598c4c15..1bdfdce0ff27 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java @@ -262,7 +262,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry ObjectNullAsBinaryTypeJdbcType.INSTANCE, typeContributions.getTypeConfiguration() .getJavaTypeRegistry() - .getDescriptor( Object.class ) + .resolveDescriptor( Object.class ) ) ); typeContributions.contributeType( @@ -270,7 +270,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry ObjectNullAsBinaryTypeJdbcType.INSTANCE, typeContributions.getTypeConfiguration() .getJavaTypeRegistry() - .getDescriptor( Object.class ) + .resolveDescriptor( Object.class ) ) ); } diff --git a/hibernate-core/hibernate-core.gradle b/hibernate-core/hibernate-core.gradle index a9bd5791df96..89a0da7869b1 100644 --- a/hibernate-core/hibernate-core.gradle +++ b/hibernate-core/hibernate-core.gradle @@ -27,7 +27,6 @@ dependencies { api jakartaLibs.jta implementation libs.hibernateModels - implementation libs.classmate implementation libs.byteBuddy implementation jakartaLibs.jaxbApi @@ -42,6 +41,8 @@ dependencies { compileOnly jakartaLibs.jsonbApi compileOnly libs.jackson compileOnly libs.jacksonXml + compileOnly libs.jackson3 + compileOnly libs.jackson3Xml compileOnly jdbcLibs.postgresql compileOnly jdbcLibs.edb @@ -79,6 +80,8 @@ dependencies { testImplementation libs.jackson testRuntimeOnly libs.jacksonXml testRuntimeOnly libs.jacksonJsr310 + testImplementation libs.jackson3 + testImplementation libs.jackson3Xml testAnnotationProcessor project( ':hibernate-processor' ) diff --git a/hibernate-core/src/main/java/org/hibernate/DetachedObjectException.java b/hibernate-core/src/main/java/org/hibernate/DetachedObjectException.java new file mode 100644 index 000000000000..c177ade998c0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/DetachedObjectException.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate; + +/** + * Thrown if a detached instance of an entity class is passed to + * a {@link Session} method that expects a managed instance. + * + * @author Gavin King + * + * @since 7.0 + */ +@Incubating +public class DetachedObjectException extends HibernateException { + public DetachedObjectException(String message) { + super( message ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/KeyType.java b/hibernate-core/src/main/java/org/hibernate/KeyType.java new file mode 100644 index 000000000000..72c3270c6650 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/KeyType.java @@ -0,0 +1,34 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate; + +import jakarta.persistence.FindOption; + +/// FindOption allowing to load based on either id (default) or natural-id. +/// +/// @see jakarta.persistence.EntityManager#find +/// @see Session#findMultiple +/// +/// @since 7.3 +/// +/// @author Steve Ebersole +/// @author Gavin King +public enum KeyType implements FindOption { + /// Indicates to find by the entity's identifier. The default. + /// + /// @see jakarta.persistence.Id + /// @see jakarta.persistence.EmbeddedId + /// @see jakarta.persistence.IdClass + IDENTIFIER, + + /// Indicates to find based on the entity's natural-id, if one. + /// + /// @see org.hibernate.annotations.NaturalId + /// @see org.hibernate.annotations.NaturalIdClass + /// + /// @implSpec Will trigger an [IllegalArgumentException] if the entity does + /// not define a natural-id. + NATURAL +} diff --git a/hibernate-core/src/main/java/org/hibernate/NaturalIdLoadAccess.java b/hibernate-core/src/main/java/org/hibernate/NaturalIdLoadAccess.java index d60e404cfc9b..099036bc55f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/NaturalIdLoadAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/NaturalIdLoadAccess.java @@ -5,7 +5,6 @@ package org.hibernate; import jakarta.persistence.EntityGraph; - import jakarta.persistence.PessimisticLockScope; import jakarta.persistence.Timeout; import jakarta.persistence.metamodel.SingularAttribute; @@ -35,7 +34,10 @@ * @see Session#byNaturalId(Class) * @see org.hibernate.annotations.NaturalId * @see SimpleNaturalIdLoadAccess + * + * @deprecated (since 7.3) Use {@linkplain Session#findByNaturalId} instead. */ +@Deprecated public interface NaturalIdLoadAccess { /** diff --git a/hibernate-core/src/main/java/org/hibernate/NaturalIdMultiLoadAccess.java b/hibernate-core/src/main/java/org/hibernate/NaturalIdMultiLoadAccess.java index 29c21475af44..d38813665990 100644 --- a/hibernate-core/src/main/java/org/hibernate/NaturalIdMultiLoadAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/NaturalIdMultiLoadAccess.java @@ -36,7 +36,10 @@ * * @see Session#byMultipleNaturalId(Class) * @see org.hibernate.annotations.NaturalId + * + * @deprecated (since 7.3) Use {@linkplain Session#findMultipleByNaturalId} instead. */ +@Deprecated public interface NaturalIdMultiLoadAccess { /** diff --git a/hibernate-core/src/main/java/org/hibernate/NaturalIdSynchronization.java b/hibernate-core/src/main/java/org/hibernate/NaturalIdSynchronization.java new file mode 100644 index 000000000000..4aee7d7deb36 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/NaturalIdSynchronization.java @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate; + +import jakarta.persistence.FindOption; + +/// Indicates whether to perform synchronization (a sort of flush) +/// before a [find by natural-id][KeyType#NATURAL]. +/// +/// @author Steve Ebersole +public enum NaturalIdSynchronization implements FindOption { + /// Perform the synchronization. + ENABLED, + + /// Do not perform the synchronization. + DISABLED; +} diff --git a/hibernate-core/src/main/java/org/hibernate/OrderingMode.java b/hibernate-core/src/main/java/org/hibernate/OrderingMode.java index 4e9e21d44759..0f119c7b9be8 100644 --- a/hibernate-core/src/main/java/org/hibernate/OrderingMode.java +++ b/hibernate-core/src/main/java/org/hibernate/OrderingMode.java @@ -28,7 +28,7 @@ * The default is {@link #ORDERED}. * * @see org.hibernate.Session#findMultiple(Class, List, FindOption...) - * @see org.hibernate.Session#findMultiple(EntityGraph, List , FindOption...) + * @see org.hibernate.Session#findMultiple(EntityGraph, List, FindOption...) * * @since 7.2 */ diff --git a/hibernate-core/src/main/java/org/hibernate/RemovalsMode.java b/hibernate-core/src/main/java/org/hibernate/RemovalsMode.java index 21a62d511ef0..018419386de5 100644 --- a/hibernate-core/src/main/java/org/hibernate/RemovalsMode.java +++ b/hibernate-core/src/main/java/org/hibernate/RemovalsMode.java @@ -18,7 +18,7 @@ * The default is {@link #REPLACE}. * * @see org.hibernate.Session#findMultiple(Class, List, FindOption...) - * @see org.hibernate.Session#findMultiple(EntityGraph, List , FindOption...) + * @see org.hibernate.Session#findMultiple(EntityGraph, List, FindOption...) * * @since 7.2 */ @@ -31,5 +31,15 @@ public enum RemovalsMode implements FindMultipleOption { /** * The default. Removed entities are replaced with {@code null} in the load result. */ - REPLACE + REPLACE, + /** + * Removed entities are excluded from the load result. + *

+ * This option is incompatible with {@link OrderingMode#ORDERED}. + * It must be used in conjunction with {@link OrderingMode#UNORDERED} + * and {@link SessionCheckMode#ENABLED}. + * + * @since 7.3 + */ + EXCLUDE } diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index cd4109bc9a24..c6482eecf1a4 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -4,1518 +4,1365 @@ */ package org.hibernate; -import java.util.Collection; -import java.util.List; -import java.util.function.Consumer; - -import jakarta.persistence.FindOption; -import jakarta.persistence.LockOption; -import jakarta.persistence.RefreshOption; -import jakarta.persistence.metamodel.EntityType; -import org.hibernate.graph.RootGraph; -import org.hibernate.jdbc.Work; -import org.hibernate.query.Query; -import org.hibernate.stat.SessionStatistics; - import jakarta.persistence.CacheRetrieveMode; import jakarta.persistence.CacheStoreMode; import jakarta.persistence.EntityGraph; import jakarta.persistence.EntityManager; +import jakarta.persistence.FindOption; import jakarta.persistence.FlushModeType; import jakarta.persistence.LockModeType; +import jakarta.persistence.LockOption; +import jakarta.persistence.RefreshOption; import jakarta.persistence.TypedQueryReference; import jakarta.persistence.criteria.CriteriaDelete; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.CriteriaUpdate; +import jakarta.persistence.metamodel.EntityType; +import org.hibernate.graph.RootGraph; +import org.hibernate.query.NativeQuery; +import org.hibernate.query.Query; +import org.hibernate.stat.SessionStatistics; -/** - * The main runtime interface between a Java application and Hibernate. Represents the - * notion of a persistence context, a set of managed entity instances associated - * with a logical transaction. - *

- * The lifecycle of a {@code Session} is bounded by the beginning and end of the logical - * transaction. But a long logical transaction might span several database transactions. - *

- * The primary purpose of the {@code Session} is to offer create, read, and delete - * operations for instances of mapped entity classes. An instance may be in one of three - * states with respect to a given open session: - *

    - *
  • transient: never persistent, and not associated with the {@code Session}, - *
  • persistent: currently associated with the {@code Session}, or - *
  • detached: previously persistent, but not currently associated with the - * {@code Session}. - *
- *

- * Each persistent instance has a persistent identity determined by its type - * and identifier value. There may be at most one persistent instance with a given - * persistent identity associated with a given session. A persistent identity is - * assigned when an {@linkplain #persist(Object) instance is made persistent}. - *

- * An instance of an entity class may be associated with at most one open session. - * Distinct sessions represent state with the same persistent identity using distinct - * persistent instances of the mapped entity class. - *

- * Any instance returned by {@link #get(Class, Object)}, {@link #find(Class, Object)}, - * or by a query is persistent. A persistent instance might hold references to other - * entity instances, and sometimes these references are proxied by an - * intermediate object. When an associated entity has not yet been fetched from the - * database, references to the unfetched entity are represented by uninitialized - * proxies. The state of an unfetched entity is automatically fetched from the - * database when a method of its proxy is invoked, if and only if the proxy is - * associated with an open session. Otherwise, {@link #getReference(Object)} may be - * used to trade a proxy belonging to a closed session for a new proxy associated - * with the current session. - *

- * A transient instance may be made persistent by calling {@link #persist(Object)}. - * A persistent instance may be made detached by calling {@link #detach(Object)}. - * A persistent instance may be marked for removal, and eventually made transient, - * by calling {@link #remove(Object)}. - *

- * Persistent instances are held in a managed state by the persistence context. Any - * change to the state of a persistent instance is automatically detected and eventually - * flushed to the database. This process of automatic change detection is called - * dirty checking and can be expensive in some circumstances. Dirty checking - * may be disabled by marking an entity as read-only using - * {@link #setReadOnly(Object, boolean)} or simply by {@linkplain #detach(Object) evicting} - * it from the persistence context. A session may be set to load entities as read-only - * {@linkplain #setDefaultReadOnly(boolean) by default}, or this may be controlled at the - * {@linkplain Query#setReadOnly(boolean) query level}. - *

- * The state of a transient or detached instance may be made persistent by copying it to - * a persistent instance using {@link #merge(Object)}. All older operations which moved a - * detached instance to the persistent state are now deprecated, and clients should now - * migrate to the use of {@code merge()}. - *

- * The persistent state of a managed entity may be refreshed from the database, discarding - * all modifications to the object held in memory, by calling {@link #refresh(Object)}. - *

- * From {@linkplain FlushMode time to time}, a {@linkplain #flush() flush operation} is - * triggered, and the session synchronizes state held in memory with persistent state - * held in the database by executing SQL {@code insert}, {@code update}, and {@code delete} - * statements. Note that SQL statements are often not executed synchronously by the methods - * of the {@code Session} interface. If synchronous execution of SQL is desired, the - * {@link StatelessSession} allows this. - *

- * Each managed instance has an associated {@link LockMode}. By default, the session - * obtains only {@link LockMode#READ} on an entity instance it reads from the database - * and {@link LockMode#WRITE} on an entity instance it writes to the database. This - * behavior is appropriate for programs which use optimistic locking. - *

    - *
  • A different lock level may be obtained by explicitly specifying the mode using - * {@link #find(Class,Object,LockModeType)}, {@link #find(Class,Object,FindOption...)}, - * {@link #refresh(Object,LockModeType)}, {@link #refresh(Object,RefreshOption...)}, - * or {@link org.hibernate.query.SelectionQuery#setLockMode(LockModeType)}. - *
  • The lock level of a managed instance already held by the session may be upgraded - * to a more restrictive lock level by calling {@link #lock(Object, LockMode)} or - * {@link #lock(Object, LockModeType)}. - *
- *

- * A persistence context holds hard references to all its entities and prevents them - * from being garbage collected. Therefore, a {@code Session} is a short-lived object, - * and must be discarded as soon as a logical transaction ends. In extreme cases, - * {@link #clear()} and {@link #detach(Object)} may be used to control memory usage. - * However, for processes which read many entities, a {@link StatelessSession} should - * be used. - *

- * A session might be associated with a container-managed JTA transaction, or it might be - * in control of its own resource-local database transaction. In the case of a - * resource-local transaction, the client must demarcate the beginning and end of the - * transaction using a {@link Transaction}. A typical resource-local transaction should - * use the following idiom: - *

- * Session session = factory.openSession();
- * Transaction tx = null;
- * try {
- *     tx = session.beginTransaction();
- *     //do some work
- *     ...
- *     tx.commit();
- * }
- * catch (Exception e) {
- *     if (tx!=null) tx.rollback();
- *     throw e;
- * }
- * finally {
- *     session.close();
- * }
- * 
- *

- * It's crucially important to appreciate the following restrictions and why they exist: - *

    - *
  • If the {@code Session} throws an exception, the current transaction must be rolled - * back and the session must be discarded. The internal state of the {@code Session} - * cannot be expected to be consistent with the database after the exception occurs. - *
  • At the end of a logical transaction, the session must be explicitly {@linkplain - * #close() destroyed}, so that all JDBC resources may be released. - *
  • If a transaction is rolled back, the state of the persistence context and of its - * associated entities must be assumed inconsistent with the database, and the - * session must be discarded. - *
  • A {@code Session} is never thread-safe. It contains various different sorts of - * fragile mutable state. Each thread or transaction must obtain its own dedicated - * instance from the {@link SessionFactory}. - *
- *

- * An easy way to be sure that session and transaction management is being done correctly - * is to {@linkplain SessionFactory#inTransaction(Consumer) let the factory do it}: - *

- * sessionFactory.inTransaction(session -> {
- *     //do the work
- *     ...
- * });
- * 
- *

- * A session may be used to {@linkplain #doWork(Work) execute JDBC work} using its JDBC - * connection and transaction: - *

- * session.doWork(connection -> {
- *     try ( PreparedStatement ps = connection.prepareStatement( " ... " ) ) {
- *         ps.execute();
- *     }
- * });
- * 
- *

- * A {@code Session} instance is serializable if its entities are serializable. - *

- * Every {@code Session} is a JPA {@link EntityManager}. Furthermore, when Hibernate is - * acting as the JPA persistence provider, the method {@link EntityManager#unwrap(Class)} - * may be used to obtain the underlying {@code Session}. - *

- * Hibernate, unlike JPA, allows a persistence unit where an entity class is mapped multiple - * times, with different entity names, usually to different tables. In this case, the session - * needs a way to identify the entity name of a given instance of the entity class. Therefore, - * some operations of this interface, including operations inherited from {@code EntityManager}, - * are overloaded with a form that accepts an explicit entity name along with the instance. An - * alternative solution to this problem is to provide an {@link EntityNameResolver}. - * - * @see SessionFactory - * - * @author Gavin King - * @author Steve Ebersole - */ +import java.util.Collection; +import java.util.List; + +/// The main runtime interface between a Java application and Hibernate. Represents the +/// notion of a _persistence context_, a set of managed entity instances associated +/// with a logical transaction. +/// +/// The lifecycle of a `Session` is bounded by the beginning and end of the logical +/// transaction. But a long logical transaction might span several database transactions. +/// +/// The primary purpose of the `Session` is to offer create, read, and delete +/// operations for instances of mapped entity classes. An instance may be in one of three +/// states with respect to a given open session: +/// +/// - _transient:_ never persistent, and not associated with the `Session`, +/// - _persistent:_ currently associated with the `Session`, or +/// - _detached:_ previously persistent, but not currently associated with the +/// `Session`. +/// +/// Each persistent instance has a _persistent identity_ determined by its type +/// and identifier value. There may be at most one persistent instance with a given +/// persistent identity associated with a given session. A persistent identity is +/// assigned when an {@linkplain #persist(Object) instance is made persistent}. +/// +/// An instance of an entity class may be associated with at most one open session. +/// Distinct sessions represent state with the same persistent identity using distinct +/// persistent instances of the mapped entity class. +/// +/// Any instance returned by [#get(Class,Object)], [#find(Class,Object)], +/// or by a query is persistent. A persistent instance might hold references to other +/// entity instances, and sometimes these references are _proxied_ by an +/// intermediate object. When an associated entity has not yet been fetched from the +/// database, references to the unfetched entity are represented by uninitialized +/// proxies. The state of an unfetched entity is automatically fetched from the +/// database when a method of its proxy is invoked, if and only if the proxy is +/// associated with an open session. Otherwise, [#getReference(Object)] may be +/// used to trade a proxy belonging to a closed session for a new proxy associated +/// with the current session. +/// +/// A transient instance may be made persistent by calling [#persist(Object)]. +/// A persistent instance may be made detached by calling [#detach(Object)]. +/// A persistent instance may be marked for removal, and eventually made transient, +/// by calling [#remove(Object)]. +/// +/// Persistent instances are held in a managed state by the persistence context. Any +/// change to the state of a persistent instance is automatically detected and eventually +/// flushed to the database. This process of automatic change detection is called +/// _dirty checking_ and can be expensive in some circumstances. Dirty checking +/// may be disabled by marking an entity as read-only using +/// [#setReadOnly(Object,boolean)] or simply by [evicting][#detach(Object)] +/// it from the persistence context. A session may be set to load entities as read-only +/// [by default][#setDefaultReadOnly(boolean)], or this may be controlled at the +/// [query level][Query#setReadOnly(boolean)]. +/// +/// The state of a transient or detached instance may be made persistent by copying it to +/// a persistent instance using [#merge(Object)]. All older operations which moved a +/// detached instance to the persistent state are now deprecated, and clients should now +/// migrate to the use of `merge()`. +/// +/// The persistent state of a managed entity may be refreshed from the database, discarding +/// all modifications to the object held in memory, by calling [#refresh(Object)]. +/// +/// From {@linkplain FlushMode time to time}, a {@linkplain #flush() flush operation} is +/// triggered, and the session synchronizes state held in memory with persistent state +/// held in the database by executing SQL `insert`, `update`, and `delete` +/// statements. Note that SQL statements are often not executed synchronously by the methods +/// of the `Session` interface. If synchronous execution of SQL is desired, the +/// [StatelessSession] allows this. +/// +/// Each managed instance has an associated [LockMode]. By default, the session +/// obtains only [LockMode#READ] on an entity instance it reads from the database +/// and [LockMode#WRITE] on an entity instance it writes to the database. This +/// behavior is appropriate for programs which use optimistic locking. +/// +/// - A different lock level may be obtained by explicitly specifying the mode using +/// [#find(Class,Object,LockModeType)], [#find(Class,Object,FindOption...)], +/// [#refresh(Object,LockModeType)], [#refresh(Object,RefreshOption...)], +/// or [org.hibernate.query.SelectionQuery#setLockMode(LockModeType)]. +/// - The lock level of a managed instance already held by the session may be upgraded +/// to a more restrictive lock level by calling [#lock(Object,LockMode)] or +/// [#lock(Object,LockModeType)]. +/// +/// A persistence context holds hard references to all its entities and prevents them +/// from being garbage collected. Therefore, a `Session` is a short-lived object, +/// and must be discarded as soon as a logical transaction ends. In extreme cases, +/// [#clear()] and [#detach(Object)] may be used to control memory usage. +/// However, for processes which read many entities, a [StatelessSession] should +/// be used. +/// +/// A session might be associated with a container-managed JTA transaction, or it might be +/// in control of its own _resource-local_ database transaction. In the case of a +/// resource-local transaction, the client must demarcate the beginning and end of the +/// transaction using a [Transaction]. A typical resource-local transaction should +/// use the following idiom: +/// +/// ```java +/// try (Session session = factory.openSession()) { +/// Transaction tx = null; +/// try { +/// tx = session.beginTransaction(); +/// //do some work +/// ... +/// tx.commit(); +/// } +/// catch (Exception e) { +/// if (tx!=null) tx.rollback(); +/// throw e; +/// } +/// } +/// ``` +/// +/// It's crucially important to appreciate the following restrictions and why they exist: +/// +/// - If the `Session` throws an exception, the current transaction must be rolled +/// back and the session must be discarded. The internal state of the `Session` +/// cannot be expected to be consistent with the database after the exception occurs. +/// - At the end of a logical transaction, the session must be explicitly +/// [destroyed][#close()], so that all JDBC resources may be released. +/// - If a transaction is rolled back, the state of the persistence context and of its +/// associated entities must be assumed inconsistent with the database, and the +/// session must be discarded. +/// - A `Session` is never thread-safe. It contains various different sorts of +/// fragile mutable state. Each thread or transaction must obtain its own dedicated +/// instance from the [SessionFactory]. +/// +/// An easy way to be sure that session and transaction management is being done correctly +/// is to [let the factory do it][SessionFactory#inTransaction(java.util.function.Consumer)]: +/// +/// ```java +/// sessionFactory.inTransaction(session -> { +/// //do the work +/// ... +/// }); +/// ``` +/// +/// A session may be used to [execute JDBC work][#doWork] using its JDBC +/// connection and transaction: +/// +/// ```java +/// session.doWork(connection -> { +/// try ( PreparedStatement ps = connection.prepareStatement(...) ) { +/// ps.execute(); +/// } +/// }); +/// ``` +/// +/// A `Session` instance is serializable if its entities are serializable. +/// +/// Every `Session` is a JPA [EntityManager]. Furthermore, when Hibernate is +/// acting as the JPA persistence provider, the method [#unwrap(Class)] +/// may be used to obtain the underlying `Session`. +/// +/// Hibernate, unlike JPA, allows a persistence unit where an entity class is mapped multiple +/// times, with different entity names, usually to different tables. In this case, the session +/// needs a way to identify the entity name of a given instance of the entity class. Therefore, +/// some operations of this interface, including operations inherited from `EntityManager`, +/// are overloaded with a form that accepts an explicit entity name along with the instance. An +/// alternative solution to this problem is to provide an [EntityNameResolver]. +/// +/// @see SessionFactory +/// +/// @author Gavin King +/// @author Steve Ebersole public interface Session extends SharedSessionContract, EntityManager { - /** - * Force this session to flush. Must be called at the end of a unit of work, - * before the transaction is committed. Depending on the current - * {@linkplain #setHibernateFlushMode(FlushMode) flush mode}, the session might - * automatically flush when {@link Transaction#commit()} is called, and it is not - * necessary to call this method directly. - *

- * Flushing is the process of synchronizing the underlying persistent - * store with persistable state held in memory. - * - * @throws HibernateException if changes could not be synchronized with the database - */ + /// Force this session to flush. Must be called at the end of a unit of work, + /// before the transaction is committed. Depending on the current + /// [flush mode][#getHibernateFlushMode()], the session might + /// automatically flush when [Transaction#commit()] is called, and it is not + /// necessary to call this method directly. + /// + /// _Flushing_ is the process of synchronizing the underlying persistent + /// store with persistable state held in memory. + /// + /// @throws HibernateException if changes could not be synchronized with the database @Override void flush(); - /** - * Set the current {@linkplain FlushModeType JPA flush mode} for this session. - *

- * Flushing is the process of synchronizing the underlying persistent - * store with persistable state held in memory. The current flush mode determines - * when the session is automatically flushed. - * - * @param flushMode the new {@link FlushModeType} - * - * @see #setHibernateFlushMode(FlushMode) - */ + /// Set the current [JPA flush mode][FlushModeType] for this session. + /// + /// _Flushing_ is the process of synchronizing the underlying persistent + /// store with persistable state held in memory. The current flush mode determines + /// when the session is automatically flushed. + /// + /// @param flushMode the new [FlushModeType] + /// + /// @see #setHibernateFlushMode(FlushMode) @Override void setFlushMode(FlushModeType flushMode); - /** - * Set the current {@linkplain FlushMode flush mode} for this session. - *

- * Flushing is the process of synchronizing the underlying persistent - * store with persistable state held in memory. The current flush mode determines - * when the session is automatically flushed. - *

- * The {@linkplain FlushMode#AUTO default flush mode} is sometimes unnecessarily - * aggressive. For a logically "read only" session, it's reasonable to set the - * session's flush mode to {@link FlushMode#MANUAL} at the start of the session - * in order to avoid some unnecessary work. - *

- * Note that {@link FlushMode} defines more options than {@link FlushModeType}. - * - * @param flushMode the new {@link FlushMode} - */ + /// Set the current [flush mode][FlushMode] for this session. + /// + /// _Flushing_ is the process of synchronizing the underlying persistent + /// store with persistable state held in memory. The current flush mode determines + /// when the session is automatically flushed. + /// + /// The [default flush mode][FlushMode#AUTO] is sometimes unnecessarily + /// aggressive. For a logically "read only" session, it's reasonable to set the + /// session's flush mode to [FlushMode#MANUAL] at the start of the session + /// in order to avoid some unnecessary work. + /// + /// Note that [FlushMode] defines more options than [FlushModeType]. + /// + /// @param flushMode the new [FlushMode] void setHibernateFlushMode(FlushMode flushMode); - /** - * Get the current {@linkplain FlushModeType JPA flush mode} for this session. - * - * @return the {@link FlushModeType} currently in effect - * - * @see #getHibernateFlushMode() - */ + /// Get the current [JPA flush mode][FlushModeType] for this session. + /// + /// @return the [FlushModeType] currently in effect + /// + /// @see #getHibernateFlushMode() @Override FlushModeType getFlushMode(); - /** - * Get the current {@linkplain FlushMode flush mode} for this session. - * - * @return the {@link FlushMode} currently in effect - */ + /// Get the current [flush mode][FlushMode] for this session. + /// + /// @return the [FlushMode] currently in effect FlushMode getHibernateFlushMode(); - /** - * Set the current {@linkplain CacheMode cache mode} for this session. - *

- * The cache mode determines the manner in which this session can interact with - * the second level cache. - * - * @param cacheMode the new cache mode - */ + /// Set the current [cache mode][CacheMode] for this session. + /// + /// The cache mode determines the manner in which this session can interact with + /// the second level cache. + /// + /// @param cacheMode the new cache mode void setCacheMode(CacheMode cacheMode); - /** - * Get the current {@linkplain CacheMode cache mode} for this session. - * - * @return the current cache mode - */ + /// Get the current [cache mode][CacheMode] for this session. + /// + /// @return the current cache mode CacheMode getCacheMode(); - /** - * The JPA-defined {@link CacheStoreMode}. - * - * @see #getCacheMode() - * - * @since 6.2 - */ + /// The JPA-defined [CacheStoreMode]. + /// + /// @see #getCacheMode() + /// + /// @since 6.2 @Override CacheStoreMode getCacheStoreMode(); - /** - * The JPA-defined {@link CacheRetrieveMode}. - * - * @see #getCacheMode() - * - * @since 6.2 - */ + /// The JPA-defined [CacheRetrieveMode]. + /// + /// @see #getCacheMode() + /// + /// @since 6.2 @Override CacheRetrieveMode getCacheRetrieveMode(); - /** - * Enable or disable writes to the second-level cache. - * - * @param cacheStoreMode a JPA-defined {@link CacheStoreMode} - * - * @see #setCacheMode(CacheMode) - * - * @since 6.2 - */ + /// Enable or disable writes to the second-level cache. + /// + /// @param cacheStoreMode a JPA-defined [CacheStoreMode] + /// + /// @see #setCacheMode(CacheMode) + /// + /// @since 6.2 @Override void setCacheStoreMode(CacheStoreMode cacheStoreMode); - /** - * Enable or disable reads from the second-level cache. - * - * @param cacheRetrieveMode a JPA-defined {@link CacheRetrieveMode} - * - * @see #setCacheMode(CacheMode) - * - * @since 6.2 - */ + /// Enable or disable reads from the second-level cache. + /// + /// @param cacheRetrieveMode a JPA-defined [CacheRetrieveMode] + /// + /// @see #setCacheMode(CacheMode) + /// + /// @since 6.2 @Override void setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode); - /** - * Get the maximum batch size for batch fetching associations by - * id in this session. - * - * @since 6.3 - */ + /// Get the maximum batch size for batch fetching associations by + /// id in this session. + /// + /// @since 6.3 int getFetchBatchSize(); - /** - * Set the maximum batch size for batch fetching associations by - * id in this session. Override the - * {@linkplain org.hibernate.boot.spi.SessionFactoryOptions#getDefaultBatchFetchSize() - * factory-level} default controlled by the configuration property - * {@value org.hibernate.cfg.AvailableSettings#DEFAULT_BATCH_FETCH_SIZE}. - *

- *

    - *
  • If {@code batchSize>1}, then batch fetching is enabled. - *
  • If {@code batchSize<0}, the batch size is inherited from - * the factory-level setting. - *
  • Otherwise, batch fetching is disabled. - *
- * - * @param batchSize the maximum batch size for batch fetching - * - * @since 6.3 - * - * @see org.hibernate.cfg.AvailableSettings#DEFAULT_BATCH_FETCH_SIZE - */ + /// Set the maximum batch size for batch fetching associations by + /// id in this session. Override the + /// [factory-level][org.hibernate.boot.spi.SessionFactoryOptions#getDefaultBatchFetchSize()] + /// default controlled by the configuration property + /// {@value org.hibernate.cfg.AvailableSettings#DEFAULT_BATCH_FETCH_SIZE}. + /// + /// - If `batchSize>1`, then batch fetching is enabled. + /// - If `batchSize<0`, the batch size is inherited from + /// the factory-level setting. + /// - Otherwise, batch fetching is disabled. + /// + /// @param batchSize the maximum batch size for batch fetching + /// + /// @since 6.3 + /// + /// @see org.hibernate.cfg.AvailableSettings#DEFAULT_BATCH_FETCH_SIZE void setFetchBatchSize(int batchSize); - /** - * Determine if subselect fetching is enabled in this session. - * - * @return {@code true} is subselect fetching is enabled - * - * @since 6.3 - */ + /// Determine if subselect fetching is enabled in this session. + /// + /// @return `true` is subselect fetching is enabled + /// + /// @since 6.3 boolean isSubselectFetchingEnabled(); - /** - * Enable or disable subselect fetching in this session. Override the - * {@linkplain org.hibernate.boot.spi.SessionFactoryOptions#isSubselectFetchEnabled() - * factory-level} default controlled by the configuration property - * {@value org.hibernate.cfg.AvailableSettings#USE_SUBSELECT_FETCH}. - * - * @param enabled {@code true} to enable subselect fetching - * - * @since 6.3 - * - * @see org.hibernate.cfg.AvailableSettings#USE_SUBSELECT_FETCH - */ + /// Enable or disable subselect fetching in this session. Override the + /// [factory-level][org.hibernate.boot.spi.SessionFactoryOptions#isSubselectFetchEnabled()] + /// default controlled by the configuration property + /// {@value org.hibernate.cfg.AvailableSettings#USE_SUBSELECT_FETCH}. + /// + /// @param enabled `true` to enable subselect fetching + /// + /// @since 6.3 + /// + /// @see org.hibernate.cfg.AvailableSettings#USE_SUBSELECT_FETCH void setSubselectFetchingEnabled(boolean enabled); - /** - * Get the session factory which created this session. - * - * @return the session factory - * - * @see SessionFactory - */ + /// Get the session factory which created this session. + /// + /// @return the session factory + /// + /// @see SessionFactory SessionFactory getSessionFactory(); - /** - * Cancel the execution of the current query. - *

- * This is the sole method on session which may be safely called from - * another thread. - * - * @throws HibernateException if there was a problem cancelling the query - */ + /// Cancel the execution of the current query. + /// + /// This is the sole method on session which may be safely called from + /// another thread. + /// + /// @throws HibernateException if there was a problem cancelling the query void cancelQuery(); - /** - * Does this session contain any changes which must be synchronized with - * the database? In other words, would any DML operations be executed if - * we flushed this session? - * - * @return {@code true} if the session contains pending changes; - * {@code false} otherwise. - */ + /// Whether this session contains any changes which must be synchronized with + /// the database. In other words, would any DML operations be executed if + /// we flushed this session? + /// + /// @return `true` if the session contains pending changes; `false` otherwise. boolean isDirty(); - /** - * Will entities and proxies that are loaded into this session be made - * read-only by default? - *

- * To determine the read-only/modifiable setting for a particular entity - * or proxy use {@link #isReadOnly(Object)}. - * - * @see #isReadOnly(Object) - * - * @return {@code true}, loaded entities/proxies will be made read-only by default; - * {@code false}, loaded entities/proxies will be made modifiable by default. - */ + /// Will entities and proxies that are loaded into this session be made + /// read-only by default? + /// + /// To determine the read-only/modifiable setting for a particular entity + /// or proxy use [#isReadOnly(Object)]. + /// + /// @see #isReadOnly(Object) + /// + /// @return `true`, loaded entities/proxies will be made read-only by default; + /// `false`, loaded entities/proxies will be made modifiable by default. boolean isDefaultReadOnly(); - /** - * Change the default for entities and proxies loaded into this session - * from modifiable to read-only mode, or from read-only to modifiable mode. - *

- * Read-only entities are not dirty-checked, and snapshots of persistent - * state are not maintained. Read-only entities can be modified, but a - * modification to a field of a read-only entity is not made persistent. - *

- * When a proxy is initialized, the loaded entity will have the same - * read-only/modifiable setting as the uninitialized proxy, regardless of - * the {@linkplain #isDefaultReadOnly current default read-only mode} - * of the session. - *

- * To change the read-only/modifiable setting for a particular entity - * or proxy that already belongs to this session, use - * {@link #setReadOnly(Object, boolean)}. - *

- * To override the default read-only mode of the current session for - * all entities and proxies returned by a given {@code Query}, use - * {@link Query#setReadOnly(boolean)}. - *

- * Every instance of an {@linkplain org.hibernate.annotations.Immutable - * immutable} entity is loaded in read-only mode. - * - * @see #setReadOnly(Object,boolean) - * @see Query#setReadOnly(boolean) - * - * @param readOnly {@code true}, the default for loaded entities/proxies is read-only; - * {@code false}, the default for loaded entities/proxies is modifiable - * @throws SessionException if the session was originally - * {@linkplain SessionBuilder#readOnly created in read-only mode} - */ + /// Change the default for entities and proxies loaded into this session + /// from modifiable to read-only mode, or from read-only to modifiable mode. + /// + /// Read-only entities are not dirty-checked, and snapshots of persistent + /// state are not maintained. Read-only entities can be modified, but a + /// modification to a field of a read-only entity is not made persistent. + /// + /// When a proxy is initialized, the loaded entity will have the same + /// read-only/modifiable setting as the uninitialized proxy, regardless of + /// the [current default read-only mode][#isDefaultReadOnly] + /// of the session. + /// + /// To change the read-only/modifiable setting for a particular entity + /// or proxy that already belongs to this session, use + /// [#setReadOnly(Object,boolean)]. + /// + /// To override the default read-only mode of the current session for + /// all entities and proxies returned by a given `Query`, use + /// [Query#setReadOnly(boolean)]. + /// + /// Every instance of an [immutable][org.hibernate.annotations.Immutable] + /// entity is loaded in read-only mode. + /// + /// @see #setReadOnly(Object,boolean) + /// @see Query#setReadOnly(boolean) + /// + /// @param readOnly `true`, the default for loaded entities/proxies is read-only; + /// `false`, the default for loaded entities/proxies is modifiable + /// @throws SessionException if the session was originally + /// {@linkplain SessionBuilder#readOnly created in read-only mode} void setDefaultReadOnly(boolean readOnly); - /** - * Return the identifier value of the given entity associated with this session. - * An exception is thrown if the given entity instance is transient or detached - * in relation to this session. - * - * @param object a persistent instance associated with this session - * - * @return the identifier - * - * @throws TransientObjectException if the instance is transient or associated with - * a different session - */ + /// Return the identifier value of the given entity associated with this session. + /// An exception is thrown if the given entity instance is transient or detached + /// in relation to this session. + /// + /// @param object a persistent instance associated with this session + /// + /// @return the identifier + /// + /// @throws TransientObjectException if the instance is transient or associated with + /// a different session Object getIdentifier(Object object); - /** - * Determine if the given entity is associated with this session. - * - * @param entityName the entity name - * @param object an instance of a persistent class - * - * @return {@code true} if the given instance is associated with this {@code Session} - * - * @deprecated Use {@link #contains(Object)} instead. - */ + /// Determine if the given entity is associated with this session. + /// + /// @param entityName the entity name + /// @param object an instance of a persistent class + /// + /// @return `true` if the given instance is associated with this `Session` + /// + /// @deprecated Use [#contains(Object)] instead. @Deprecated(since = "7.2", forRemoval = true) boolean contains(String entityName, Object object); - /** - * Remove this instance from the session cache. Changes to the instance will - * not be synchronized with the database. This operation cascades to associated - * instances if the association is mapped with - * {@link jakarta.persistence.CascadeType#DETACH}. - * - * @param object the managed instance to detach - */ + /// Remove this instance from the session cache. Changes to the instance will + /// not be synchronized with the database. This operation cascades to associated + /// instances if the association is mapped with [jakarta.persistence.CascadeType#DETACH]. + /// + /// @param object the managed instance to detach @Override void detach(Object object); - /** - * Remove this instance from the session cache. Changes to the instance will - * not be synchronized with the database. This operation cascades to associated - * instances if the association is mapped with - * {@link jakarta.persistence.CascadeType#DETACH}. - *

- * This operation is a synonym for {@link #detach(Object)}. - * - * @param object the managed entity to evict - * - * @throws IllegalArgumentException if the given object is not an entity - */ + /// Remove this instance from the session cache. Changes to the instance will + /// not be synchronized with the database. This operation cascades to associated + /// instances if the association is mapped with [jakarta.persistence.CascadeType#DETACH]. + /// + /// This operation is a synonym for [#detach(Object)]. + /// + /// @param object the managed entity to evict + /// + /// @throws IllegalArgumentException if the given object is not an entity void evict(Object object); - /** - * Return the persistent instance of the given entity class with the given identifier, - * or null if there is no such persistent instance. If the instance is already associated - * with the session, return that instance. This method never returns an uninitialized - * instance. - *

- * The object returned by {@code get()} or {@code find()} is either an unproxied instance - * of the given entity class, or a fully-fetched proxy object. - *

- * This operation requests {@link LockMode#NONE}, that is, no lock, allowing the object - * to be retrieved from the cache without the cost of database access. However, if it is - * necessary to read the state from the database, the object will be returned with the - * lock mode {@link LockMode#READ}. - *

- * To bypass the {@linkplain Cache second-level cache}, and ensure that the state of the - * requested instance is read directly from the database, either: - *

    - *
  • call {@link #find(Class, Object, FindOption...)}, passing - * {@link CacheRetrieveMode#BYPASS} as an option, - *
  • call {@link #find(Class, Object, FindOption...)} with the explicit lock mode - * {@link LockMode#READ}, or - *
  • {@linkplain #setCacheRetrieveMode set the cache mode} to - * {@link CacheRetrieveMode#BYPASS} before calling this method. - *
- * - * @apiNote This operation is very similar to {@link #get(Class, Object)}. - * - * @param entityType the entity type - * @param id an identifier - * - * @return a fully-fetched persistent instance or null - */ + /// Return the persistent instance of the given entity class with the given identifier, + /// or null if there is no such persistent instance. If the instance is already associated + /// with the session, return that instance. This method never returns an uninitialized + /// instance. + /// + /// The object returned by `get()` or `find()` is either an unproxied instance + /// of the given entity class, or a fully-fetched proxy object. + /// + /// This operation requests [LockMode#NONE], that is, no lock, allowing the object + /// to be retrieved from the cache without the cost of database access. However, if it is + /// necessary to read the state from the database, the object will be returned with the + /// lock mode [LockMode#READ]. + /// + /// To bypass the [second-level cache][Cache], and ensure that the state of the + /// requested instance is read directly from the database, either: + /// + /// - call [#find(Class,Object,FindOption...)], passing + /// [CacheRetrieveMode#BYPASS] as an option, + /// - call [#find(Class,Object,FindOption...)] with the explicit lock mode + /// [LockMode#READ], or + /// - {@linkplain #setCacheRetrieveMode set the cache mode} to + /// [CacheRetrieveMode#BYPASS] before calling this method. + /// + /// @apiNote This operation is very similar to [#get(Class,Object)]. + /// + /// @param entityType the entity type + /// @param id an identifier + /// + /// @return a fully-fetched persistent instance or null @Override T find(Class entityType, Object id); - /** - * Return the persistent instance of the named entity type with the given identifier, - * or null if there is no such persistent instance. - *

- * Differs from {@linkplain #find(Class, Object)} in that this form accepts - * the entity name of a {@linkplain org.hibernate.metamodel.RepresentationMode#MAP dynamic entity}. - * - * @see #find(Class, Object) - */ + /// {@inheritDoc} + /// + /// @implNote Note that Hibernate's implementation of this method can + /// also be used for loading an entity by its [natural-id][org.hibernate.annotations.NaturalId] + /// by passing [KeyType#NATURAL] as a [FindOption] and the natural-id value as the `key` to load. + /// + /// @param entityType the entity type + /// @param id an identifier + /// @param options options controlling the behavior of the operation + @Override + T find(Class entityType, Object id, FindOption... options); + + /// Return the persistent instance of the named entity type with the given identifier, + /// or null if there is no such persistent instance. + /// + /// Differs from {@linkplain #find(Class, Object)} in that this form accepts + /// the entity name of a [dynamic entity][org.hibernate.metamodel.RepresentationMode#MAP]. + /// + /// @see #find(Class, Object) Object find(String entityName, Object primaryKey); - /** - * Return the persistent instance of the named entity type with the given identifier - * using the specified options, or null if there is no such persistent instance. - *

- * Differs from {@linkplain #find(Class, Object, FindOption...)} in that this form accepts - * the entity name of a {@linkplain org.hibernate.metamodel.RepresentationMode#MAP dynamic entity}. - * - * @see #find(Class, Object, FindOption...) - */ + /// Return the persistent instance of the named entity type with the given identifier + /// using the specified options, or null if there is no such persistent instance. + /// + /// Differs from [#find(Class, Object, FindOption...)] in that this form accepts + /// the entity name of a [dynamic entity][org.hibernate.metamodel.RepresentationMode#MAP]. + /// + /// @see #find(Class, Object, FindOption...) Object find(String entityName, Object primaryKey, FindOption... options); - /** - * Return the persistent instances of the given entity class with the given identifiers - * as a list. The position of an instance in the returned list matches the position of its - * identifier in the given list of identifiers, and the returned list contains a null value - * if there is no persistent instance matching a given identifier. If an instance is already - * associated with the session, that instance is returned. This method never returns an - * uninitialized instance. - *

- * Every object returned by {@code findMultiple()} is either an unproxied instance of the - * given entity class, or a fully-fetched proxy object. - *

- * This method accepts {@link BatchSize} as an option, allowing control over the number of - * records retrieved in a single database request. The performance impact of setting a batch - * size depends on whether a SQL array may be used to pass the list of identifiers to the - * database: - *

    - *
  • for databases which {@linkplain org.hibernate.dialect.Dialect#supportsStandardArrays - * support standard SQL arrays}, a smaller batch size might be extremely inefficient - * compared to a very large batch size or no batching at all, but - *
  • on the other hand, for databases with no SQL array type, a large batch size results - * in long SQL statements with many JDBC parameters. - *
- * - * @param entityType the entity type - * @param ids the list of identifiers - * @param options options, if any - * - * @return an ordered list of persistent instances, with null elements representing missing - * entities, whose positions in the list match the positions of their ids in the - * given list of identifiers - * - * @see FindMultipleOption - * - * @since 7.0 - */ + /// Return the persistent instances of the given entity class with the given identifiers + /// as a list. The position of an instance in the returned list matches the position of its + /// identifier in the given list of identifiers, and the returned list contains a null value + /// if there is no persistent instance matching a given identifier. If an instance is already + /// associated with the session, that instance is returned. This method never returns an + /// uninitialized instance. + /// + /// Every object returned by `findMultiple()` is either an unproxied instance of the + /// given entity class, or a fully-fetched proxy object. + /// + /// This method accepts [BatchSize] as an option, allowing control over the number of + /// records retrieved in a single database request. The performance impact of setting a batch + /// size depends on whether a SQL array may be used to pass the list of identifiers to the + /// database: + /// + /// - for databases which [support standard SQL arrays][org.hibernate.dialect.Dialect#supportsStandardArrays], + /// a smaller batch size might be extremely inefficient compared to a very large batch size or + /// no batching at all, but + /// - on the other hand, for databases with no SQL array type, a large batch size results + /// in long SQL statements with many JDBC parameters. + /// + /// @param entityType the entity type + /// @param ids the list of identifiers + /// @param options options, if any + /// + /// @return an ordered list of persistent instances, with null elements representing missing + /// entities, whose positions in the list match the positions of their ids in the + /// given list of identifiers + /// + /// @see FindMultipleOption + /// + /// @since 7.0 List findMultiple(Class entityType, List ids, FindOption... options); - /** - * Return the persistent instances of the root entity of the given {@link EntityGraph} - * with the given identifiers as a list, fetching the associations specified by the - * graph, which is interpreted as a {@linkplain org.hibernate.graph.GraphSemantic#LOAD - * load graph}. The position of an instance in the returned list matches the position of - * its identifier in the given list of identifiers, and the returned list contains a null - * value if there is no persistent instance matching a given identifier. If an instance - * is already associated with the session, that instance is returned. This method never - * returns an uninitialized instance. - *

- * Every object returned by {@code findMultiple()} is either an unproxied instance of the - * given entity class, or a fully-fetched proxy object. - *

- * This method accepts {@link BatchSize} as an option, allowing control over the number of - * records retrieved in a single database request. The performance impact of setting a batch - * size depends on whether a SQL array may be used to pass the list of identifiers to the - * database: - *

    - *
  • for databases which {@linkplain org.hibernate.dialect.Dialect#supportsStandardArrays - * support standard SQL arrays}, a smaller batch size might be extremely inefficient - * compared to a very large batch size or no batching at all, but - *
  • on the other hand, for databases with no SQL array type, a large batch size results - * in long SQL statements with many JDBC parameters. - *
- * - * @param entityGraph the entity graph interpreted as a load graph - * @param ids the list of identifiers - * @param options options, if any - * - * @return an ordered list of persistent instances, with null elements representing missing - * entities, whose positions in the list match the positions of their ids in the - * given list of identifiers - * - * @see FindMultipleOption - * - * @since 7.0 - */ + /// Return the persistent instances of the root entity of the given [EntityGraph] + /// with the given identifiers as a list, fetching the associations specified by the + /// graph, which is interpreted as a [load graph][org.hibernate.graph.GraphSemantic#LOAD]. + /// The position of an instance in the returned list matches the position of + /// its identifier in the given list of identifiers, and the returned list contains a null + /// value if there is no persistent instance matching a given identifier. If an instance + /// is already associated with the session, that instance is returned. This method never + /// returns an uninitialized instance. + /// + /// Every object returned by `findMultiple()` is either an unproxied instance of the + /// given entity class, or a fully-fetched proxy object. + /// + /// This method accepts [BatchSize] as an option, allowing control over the number of + /// records retrieved in a single database request. The performance impact of setting a batch + /// size depends on whether a SQL array may be used to pass the list of identifiers to the + /// database: + /// + /// - for databases which [support standard SQL arrays][org.hibernate.dialect.Dialect#supportsStandardArrays] + /// a smaller batch size might be extremely inefficient + /// compared to a very large batch size or no batching at all, but + /// - on the other hand, for databases with no SQL array type, a large batch size results + /// in long SQL statements with many JDBC parameters. + /// + /// @param entityGraph the entity graph interpreted as a load graph + /// @param ids the list of identifiers + /// @param options options, if any + /// + /// @return an ordered list of persistent instances, with null elements representing missing + /// entities, whose positions in the list match the positions of their ids in the + /// given list of identifiers + /// + /// @see FindMultipleOption + /// + /// @since 7.0 List findMultiple(EntityGraph entityGraph, List ids, FindOption... options); - /** - * Read the persistent state associated with the given identifier into the given - * transient instance. - * - * @param object a transient instance of an entity class - * @param id an identifier - */ + /// Read the persistent state associated with the given identifier into the given + /// transient instance. + /// + /// @param object a transient instance of an entity class + /// @param id an identifier void load(Object object, Object id); - /** - * Persist the state of the given detached instance, reusing the current - * identifier value. This operation cascades to associated instances if - * the association is mapped with - * {@link org.hibernate.annotations.CascadeType#REPLICATE}. - * - * @param object a detached instance of a persistent class - * @param replicationMode the replication mode to use - * - * @deprecated With no real replacement. For some use cases try - * {@link StatelessSession#upsert(Object)}. - */ + /// Persist the state of the given detached instance, reusing the current + /// identifier value. This operation cascades to associated instances if + /// the association is mapped with [org.hibernate.annotations.CascadeType#REPLICATE]. + /// + /// @param object a detached instance of a persistent class + /// @param replicationMode the replication mode to use + /// + /// @deprecated With no real replacement. For some use cases try [StatelessSession#upsert(Object)]. @Deprecated( since = "6.0" ) void replicate(Object object, ReplicationMode replicationMode); - /** - * Persist the state of the given detached instance, reusing the current - * identifier value. This operation cascades to associated instances if - * the association is mapped with - * {@link org.hibernate.annotations.CascadeType#REPLICATE}. - * - * @param entityName the entity name - * @param object a detached instance of a persistent class - * @param replicationMode the replication mode to use - * - * @deprecated With no real replacement. For some use cases try - * {@link StatelessSession#upsert(Object)}. - */ + /// Persist the state of the given detached instance, reusing the current + /// identifier value. This operation cascades to associated instances if + /// the association is mapped with [org.hibernate.annotations.CascadeType#REPLICATE]. + /// + /// @param entityName the entity name + /// @param object a detached instance of a persistent class + /// @param replicationMode the replication mode to use + /// + /// @deprecated With no real replacement. For some use cases try [StatelessSession#upsert(Object)]. @Deprecated( since = "6.0" ) void replicate(String entityName, Object object, ReplicationMode replicationMode) ; - /** - * Copy the state of the given object onto the persistent object with the same - * identifier. If there is no persistent instance currently associated with - * the session, it will be loaded. Return the persistent instance. If the - * given instance is unsaved, save a copy and return it as a newly persistent - * instance. The given instance does not become associated with the session. - * This operation cascades to associated instances if the association is mapped - * with {@link jakarta.persistence.CascadeType#MERGE}. - * - * @param object a detached instance with state to be copied - * - * @return an updated persistent instance - */ + /// Copy the state of the given object onto the persistent object with the same + /// identifier. If there is no persistent instance currently associated with + /// the session, it will be loaded. Return the persistent instance. If the + /// given instance is unsaved, save a copy and return it as a newly persistent + /// instance. The given instance does not become associated with the session. + /// This operation cascades to associated instances if the association is mapped + /// with [jakarta.persistence.CascadeType#MERGE]. + /// + /// @param object a detached instance with state to be copied + /// + /// @return an updated persistent instance @Override T merge(T object); - /** - * Copy the state of the given object onto the persistent object with the same - * identifier. If there is no persistent instance currently associated with - * the session, it will be loaded. Return the persistent instance. If the - * given instance is unsaved, save a copy and return it as a newly persistent - * instance. The given instance does not become associated with the session. - * This operation cascades to associated instances if the association is mapped - * with {@link jakarta.persistence.CascadeType#MERGE}. - * - * @param entityName the entity name - * @param object a detached instance with state to be copied - * - * @return an updated persistent instance - */ + /// Copy the state of the given object onto the persistent object with the same + /// identifier. If there is no persistent instance currently associated with + /// the session, it will be loaded. Return the persistent instance. If the + /// given instance is unsaved, save a copy and return it as a newly persistent + /// instance. The given instance does not become associated with the session. + /// This operation cascades to associated instances if the association is mapped + /// with [jakarta.persistence.CascadeType#MERGE]. + /// + /// @param entityName the entity name + /// @param object a detached instance with state to be copied + /// + /// @return an updated persistent instance T merge(String entityName, T object); - /** - * Copy the state of the given object onto the persistent object with the same - * identifier. If there is no persistent instance currently associated with - * the session, it is loaded using the given {@link EntityGraph}, which is - * interpreted as a load graph. Return the persistent instance. If the given - * instance is unsaved, save a copy and return it as a newly persistent instance. - * The given instance does not become associated with the session. This operation - * cascades to associated instances if the association is mapped with - * {@link jakarta.persistence.CascadeType#MERGE}. - * - * @param object a detached instance with state to be copied - * @param loadGraph an entity graph interpreted as a load graph - * - * @return an updated persistent instance - * - * @since 7.0 - */ + /// Copy the state of the given object onto the persistent object with the same + /// identifier. If there is no persistent instance currently associated with + /// the session, it is loaded using the given [EntityGraph], which is + /// interpreted as a load graph. Return the persistent instance. If the given + /// instance is unsaved, save a copy and return it as a newly persistent instance. + /// The given instance does not become associated with the session. This operation + /// cascades to associated instances if the association is mapped with + /// [jakarta.persistence.CascadeType#MERGE]. + /// + /// @param object a detached instance with state to be copied + /// @param loadGraph an entity graph interpreted as a load graph + /// + /// @return an updated persistent instance + /// + /// @since 7.0 T merge(T object, EntityGraph loadGraph); - /** - * Make a transient instance persistent and mark it for later insertion in the - * database. This operation cascades to associated instances if the association - * is mapped with {@link jakarta.persistence.CascadeType#PERSIST}. - *

- * For an entity with a {@linkplain jakarta.persistence.GeneratedValue generated} - * id, {@code persist()} ultimately results in generation of an identifier for - * the given instance. But this may happen asynchronously, when the session is - * {@linkplain #flush() flushed}, depending on the identifier generation strategy. - * - * @param object a transient instance to be made persistent - */ + /// Make a transient instance persistent and mark it for later insertion in the + /// database. This operation cascades to associated instances if the association + /// is mapped with [jakarta.persistence.CascadeType#PERSIST]. + /// + /// For entities with a [generated id][jakarta.persistence.GeneratedValue], + /// `persist()` ultimately results in generation of an identifier for the + /// given instance. But this may happen asynchronously, when the session is + /// [flushed][#flush()], depending on the identifier generation strategy. + /// + /// @param object a transient instance to be made persistent @Override void persist(Object object); - /** - * Make a transient instance persistent and mark it for later insertion in the - * database. This operation cascades to associated instances if the association - * is mapped with {@link jakarta.persistence.CascadeType#PERSIST}. - *

- * For entities with a {@link jakarta.persistence.GeneratedValue generated id}, - * {@code persist()} ultimately results in generation of an identifier for the - * given instance. But this may happen asynchronously, when the session is - * {@linkplain #flush() flushed}, depending on the identifier generation strategy. - * - * @param entityName the entity name - * @param object a transient instance to be made persistent - */ + /// Make a transient instance persistent and mark it for later insertion in the + /// database. This operation cascades to associated instances if the association + /// is mapped with [jakarta.persistence.CascadeType#PERSIST]. + /// + /// For entities with a [generated id][jakarta.persistence.GeneratedValue], + /// `persist()` ultimately results in generation of an identifier for the + /// given instance. But this may happen asynchronously, when the session is + /// [flushed][#flush()], depending on the identifier generation strategy. + /// + /// @param entityName the entity name + /// @param object a transient instance to be made persistent void persist(String entityName, Object object); - /** - * Obtain the specified lock level on the given managed instance associated - * with this session. This operation may be used to: - *

    - *
  • perform a version check on an entity read from the second-level cache - * by requesting {@link LockMode#READ}, - *
  • schedule a version check at transaction commit by requesting - * {@link LockMode#OPTIMISTIC}, - *
  • schedule a version increment at transaction commit by requesting - * {@link LockMode#OPTIMISTIC_FORCE_INCREMENT} - *
  • upgrade to a pessimistic lock with {@link LockMode#PESSIMISTIC_READ} - * or {@link LockMode#PESSIMISTIC_WRITE}, or - *
  • immediately increment the version of the given instance by requesting - * {@link LockMode#PESSIMISTIC_FORCE_INCREMENT}. - *
- *

- * If the requested lock mode is already held on the given entity, this - * operation has no effect. - *

- * This operation cascades to associated instances if the association is - * mapped with {@link org.hibernate.annotations.CascadeType#LOCK}. - *

- * The modes {@link LockMode#WRITE} and {@link LockMode#UPGRADE_SKIPLOCKED} - * are not legal arguments to {@code lock()}. - * - * @param object a persistent instance associated with this session - * @param lockMode the lock level - * - * @see #lock(Object, LockModeType) - */ + /// Obtain the specified lock level on the given managed instance associated + /// with this session. This operation may be used to: + /// + /// - perform a version check on an entity read from the second-level cache + /// by requesting [LockMode#READ], + /// - schedule a version check at transaction commit by requesting + /// [LockMode#OPTIMISTIC], + /// - schedule a version increment at transaction commit by requesting + /// [LockMode#OPTIMISTIC_FORCE_INCREMENT] + /// - upgrade to a pessimistic lock with [LockMode#PESSIMISTIC_READ] + /// or [LockMode#PESSIMISTIC_WRITE], or + /// - immediately increment the version of the given instance by requesting + /// [LockMode#PESSIMISTIC_FORCE_INCREMENT]. + /// + /// If the requested lock mode is already held on the given entity, this + /// operation has no effect. + /// + /// This operation cascades to associated instances if the association is + /// mapped with [org.hibernate.annotations.CascadeType#LOCK]. + /// + /// The modes [LockMode#WRITE] and [LockMode#UPGRADE_SKIPLOCKED] + /// are not legal arguments to `lock()`. + /// + /// @param object a persistent instance associated with this session + /// @param lockMode the lock level + /// + /// @see #lock(Object, LockModeType) void lock(Object object, LockMode lockMode); - /** - * Obtain the specified lock level on the given managed instance associated - * with this session, applying any other specified options. This operation may - * be used to: - *

    - *
  • perform a version check on an entity read from the second-level cache - * by requesting {@link LockMode#READ}, - *
  • schedule a version check at transaction commit by requesting - * {@link LockMode#OPTIMISTIC}, - *
  • schedule a version increment at transaction commit by requesting - * {@link LockMode#OPTIMISTIC_FORCE_INCREMENT} - *
  • upgrade to a pessimistic lock with {@link LockMode#PESSIMISTIC_READ} - * or {@link LockMode#PESSIMISTIC_WRITE}, or - *
  • immediately increment the version of the given instance by requesting - * {@link LockMode#PESSIMISTIC_FORCE_INCREMENT}. - *
- *

- * If the requested lock mode is already held on the given entity, this - * operation has no effect. - *

- * This operation cascades to associated instances if the association is - * mapped with {@link org.hibernate.annotations.CascadeType#LOCK}. - *

- * The modes {@link LockMode#WRITE} and {@link LockMode#UPGRADE_SKIPLOCKED} - * are not legal arguments to {@code lock()}. - * - * @param object a persistent instance associated with this session - * @param lockMode the lock level - * - * @see #lock(Object, LockModeType, LockOption...) - */ + /// Obtain the specified lock level on the given managed instance associated + /// with this session, applying any other specified options. This operation may + /// be used to: + /// + /// - perform a version check on an entity read from the second-level cache + /// by requesting [LockMode#READ], + /// - schedule a version check at transaction commit by requesting + /// [LockMode#OPTIMISTIC], + /// - schedule a version increment at transaction commit by requesting + /// [LockMode#OPTIMISTIC_FORCE_INCREMENT] + /// - upgrade to a pessimistic lock with [LockMode#PESSIMISTIC_READ] + /// or [LockMode#PESSIMISTIC_WRITE], or + /// - immediately increment the version of the given instance by requesting + /// [LockMode#PESSIMISTIC_FORCE_INCREMENT]. + /// + /// If the requested lock mode is already held on the given entity, this + /// operation has no effect. + /// + /// This operation cascades to associated instances if the association is + /// mapped with [org.hibernate.annotations.CascadeType#LOCK]. + /// + /// The modes [LockMode#WRITE] and [LockMode#UPGRADE_SKIPLOCKED] + /// are not legal arguments to `lock()`. + /// + /// @param object a persistent instance associated with this session + /// @param lockMode the lock level + /// + /// @see #lock(Object, LockModeType, LockOption...) void lock(Object object, LockMode lockMode, LockOption... lockOptions); - /** - * Reread the state of the given managed instance associated with this session - * from the underlying database. This may be useful: - *

    - *
  • when a database trigger alters the object state upon insert or update, - *
  • after {@linkplain #createMutationQuery(String) executing} any HQL update - * or delete statement, - *
  • after {@linkplain #createNativeMutationQuery(String) executing} a native - * SQL statement, or - *
  • after inserting a {@link java.sql.Blob} or {@link java.sql.Clob}. - *
- *

- * This operation cascades to associated instances if the association is mapped - * with {@link jakarta.persistence.CascadeType#REFRESH}. - *

- * This operation requests {@link LockMode#READ}. To obtain a stronger lock, - * call {@link #refresh(Object, RefreshOption...)}, passing the appropriate - * {@link LockMode} as an option. - * - * @param object a persistent instance associated with this session - */ + /// Reread the state of the given managed instance associated with this session + /// from the underlying database. This may be useful: + /// + /// - when a database trigger alters the object state upon insert or update, + /// - after {@linkplain #createMutationQuery(String) executing} any HQL update + /// or delete statement, + /// - after {@linkplain #createNativeMutationQuery(String) executing} a native + /// SQL statement, or + /// - after inserting a [java.sql.Blob] or [java.sql.Clob]. + /// + /// This operation cascades to associated instances if the association is mapped + /// with [jakarta.persistence.CascadeType#REFRESH]. + /// + /// This operation requests [LockMode#READ]. To obtain a stronger lock, + /// call [#refresh(Object,RefreshOption...)], passing the appropriate + /// [LockMode] as an option. + /// + /// @param object a persistent instance associated with this session @Override void refresh(Object object); - /** - * Mark a persistence instance associated with this session for removal from - * the underlying database. Ths operation cascades to associated instances if - * the association is mapped {@link jakarta.persistence.CascadeType#REMOVE}. - *

- * Except when operating in fully JPA-compliant mode, this operation does, - * contrary to the JPA specification, accept a detached entity instance. - * - * @param object the managed persistent instance to remove, or a detached - * instance unless operating in fully JPA-compliant mode - */ + /// {@inheritDoc} + /// + /// @param object a persistent instance associated with this session + /// @param options options controlling the behavior of the operation + @Override + void refresh(Object object, RefreshOption... options); + + /// Mark a persistence instance associated with this session for removal from + /// the underlying database. This operation cascades to associated instances + /// if the association is mapped [jakarta.persistence.CascadeType#REMOVE]. + /// + /// Except when operating in fully JPA-compliant mode, this operation does, + /// contrary to the JPA specification, accept a detached entity instance. + /// + /// @param object the managed persistent instance to remove, or a detached + /// instance unless operating in fully JPA-compliant mode @Override void remove(Object object); - /** - * Determine the current {@linkplain LockMode lock mode} held on the given - * managed instance associated with this session. - *

- * Unlike the JPA-standard {@link #getLockMode}, this operation may be - * called when no transaction is active, in which case it should return - * {@link LockMode#NONE}, indicating that no pessimistic lock is held on - * the given entity. - * - * @param object a persistent instance associated with this session - * - * @return the lock mode currently held on the given entity - * - * @throws IllegalStateException if the given instance is not associated - * with this persistence context - * @throws ObjectDeletedException if the given instance was already - * {@linkplain #remove removed} - */ + /// Determine the current [lock mode][LockMode] held on the given + /// managed instance associated with this session. + /// + /// Unlike the JPA-standard [#getLockMode], this operation may be + /// called when no transaction is active, in which case it should return + /// [LockMode#NONE], indicating that no pessimistic lock is held on + /// the given entity. + /// + /// @param object a persistent instance associated with this session + /// + /// @return the lock mode currently held on the given entity + /// + /// @throws IllegalStateException if the given instance is not associated + /// with this persistence context + /// @throws ObjectDeletedException if the given instance was already + /// {@linkplain #remove removed} LockMode getCurrentLockMode(Object object); - /** - * Completely clear the persistence context. Evict all loaded instances, - * causing every managed entity currently associated with this session to - * transition to the detached state, and cancel all pending insertions, - * updates, and deletions. - *

- * Does not close open iterators or instances of {@link ScrollableResults}. - */ + /// Completely clear the persistence context. Evict all loaded instances, + /// causing every managed entity currently associated with this session to + /// transition to the detached state, and cancel all pending insertions, + /// updates, and deletions. + /// + /// Does not close open iterators or instances of [ScrollableResults]. @Override void clear(); - /** - * Return the persistent instance of the given entity class with the given identifier, - * or null if there is no such persistent instance. If the instance is already associated - * with the session, return that instance. This method never returns an uninitialized - * instance. - *

- * The object returned by {@code get()} or {@code find()} is either an unproxied instance - * of the given entity class, or a fully-fetched proxy object. - *

- * This operation requests {@link LockMode#NONE}, that is, no lock, allowing the object - * to be retrieved from the cache without the cost of database access. However, if it is - * necessary to read the state from the database, the object will be returned with the - * lock mode {@link LockMode#READ}. - *

- * To bypass the second-level cache, and ensure that the state is read from the database, - * either: - *

    - *
  • call {@link #get(Class, Object, LockMode)} with the explicit lock mode - * {@link LockMode#READ}, or - *
  • {@linkplain #setCacheMode set the cache mode} to {@link CacheMode#IGNORE} - * before calling this method. - *
- * - * @apiNote This operation is very similar to {@link #find(Class, Object)}. - * - * @param entityType the entity type - * @param id an identifier - * - * @return a persistent instance or null - * - * @deprecated Because the semantics of this method may change in a future release. - * Use {@link #find(Class, Object)} instead. - */ + /// Return the persistent instance of the given entity class with the given identifier, + /// or null if there is no such persistent instance. If the instance is already associated + /// with the session, return that instance. This method never returns an uninitialized + /// instance. + /// + /// The object returned by `get()` or `find()` is either an unproxied instance + /// of the given entity class, or a fully-fetched proxy object. + /// + /// This operation requests [LockMode#NONE], that is, no lock, allowing the object + /// to be retrieved from the cache without the cost of database access. However, if it is + /// necessary to read the state from the database, the object will be returned with the + /// lock mode [LockMode#READ]. + /// + /// To bypass the second-level cache, and ensure that the state is read from the database, + /// either: + /// + /// - call [#get(Class,Object,LockMode)] with the explicit lock mode + /// [LockMode#READ], or + /// - {@linkplain #setCacheMode set the cache mode} to [CacheMode#IGNORE] + /// before calling this method. + /// + /// @apiNote This operation is very similar to [#find(Class,Object)]. + /// + /// @param entityType the entity type + /// @param id an identifier + /// + /// @return a persistent instance or null + /// + /// @deprecated Because the semantics of this method may change in a future release. + /// Use [#find(Class,Object)] instead. @Deprecated(since = "7.0", forRemoval = true) T get(Class entityType, Object id); - /** - * Return the persistent instance of the given entity class with the given identifier, - * or null if there is no such persistent instance. If the instance is already associated - * with the session, return that instance. This method never returns an uninitialized - * instance. Obtain the specified lock mode if the instance exists. - * - * @apiNote This operation is very similar to {@link #find(Class, Object, LockModeType)}. - * - * @param entityType the entity type - * @param id an identifier - * @param lockMode the lock mode - * - * @return a persistent instance or null - * - * @deprecated Use {@link #find(Class, Object, FindOption...)} instead. - */ + /// Return the persistent instance of the given entity class with the given identifier, + /// or null if there is no such persistent instance. If the instance is already associated + /// with the session, return that instance. This method never returns an uninitialized + /// instance. Obtain the specified lock mode if the instance exists. + /// + /// @apiNote This operation is very similar to [#find(Class,Object,LockModeType)]. + /// + /// @param entityType the entity type + /// @param id an identifier + /// @param lockMode the lock mode + /// + /// @return a persistent instance or null + /// + /// @deprecated Use [#find(Class,Object,FindOption...)] instead. @Deprecated(since = "7.0", forRemoval = true) T get(Class entityType, Object id, LockMode lockMode); - /** - * Return the persistent instance of the given named entity with the given identifier, - * or null if there is no such persistent instance. If the instance is already associated - * with the session, return that instance. This method never returns an uninitialized - * instance. - * - * @param entityName the entity name - * @param id an identifier - * - * @return a persistent instance or null - * - * @deprecated The semantics of this method may change in a future release. - * Use {@link SessionFactory#createGraphForDynamicEntity(String)} - * together with {@link #find(EntityGraph, Object, FindOption...)} - * to load {@link org.hibernate.metamodel.RepresentationMode#MAP - * dynamic entities}. - * - * @see SessionFactory#createGraphForDynamicEntity(String) - * @see #find(EntityGraph, Object, FindOption...) - */ + /// Return the persistent instance of the given named entity with the given identifier, + /// or null if there is no such persistent instance. If the instance is already associated + /// with the session, return that instance. This method never returns an uninitialized + /// instance. + /// + /// @param entityName the entity name + /// @param id an identifier + /// + /// @return a persistent instance or null + /// + /// @deprecated The semantics of this method may change in a future release. + /// Use [SessionFactory#createGraphForDynamicEntity(String)] + /// together with [#find(EntityGraph,Object,FindOption...)] + /// to load [dynamic entities][org.hibernate.metamodel.RepresentationMode#MAP]. + /// + /// @see SessionFactory#createGraphForDynamicEntity(String) + /// @see #find(EntityGraph, Object, FindOption...) @Deprecated(since = "7", forRemoval = true) Object get(String entityName, Object id); - /** - * Return the persistent instance of the given entity class with the given identifier, - * or null if there is no such persistent instance. If the instance is already associated - * with the session, return that instance. This method never returns an uninitialized - * instance. Obtain the specified lock mode if the instance exists. - * - * @param entityName the entity name - * @param id an identifier - * @param lockMode the lock mode - * - * @return a persistent instance or null - * - * @see #get(String, Object, LockOptions) - * - * @deprecated The semantics of this method may change in a future release. - */ + /// Return the persistent instance of the given entity class with the given identifier, + /// or null if there is no such persistent instance. If the instance is already associated + /// with the session, return that instance. This method never returns an uninitialized + /// instance. Obtain the specified lock mode if the instance exists. + /// + /// @param entityName the entity name + /// @param id an identifier + /// @param lockMode the lock mode + /// + /// @return a persistent instance or null + /// + /// @see #get(String, Object, LockOptions) + /// + /// @deprecated The semantics of this method may change in a future release. @Deprecated(since = "7.0", forRemoval = true) Object get(String entityName, Object id, LockMode lockMode); - /** - * Return the persistent instance of the given entity class with the given identifier, - * or null if there is no such persistent instance. If the instance is already associated - * with the session, return that instance. This method never returns an uninitialized - * instance. Obtain the specified lock mode if the instance exists. - * - * @param entityType the entity type - * @param id an identifier - * @param lockOptions the lock mode - * - * @return a persistent instance or null - * - * @deprecated This method will be removed. - * Use {@link #find(Class, Object, FindOption...)} instead. - */ + /// Return the persistent instance of the given entity class with the given identifier, + /// or null if there is no such persistent instance. If the instance is already associated + /// with the session, return that instance. This method never returns an uninitialized + /// instance. Obtain the specified lock mode if the instance exists. + /// + /// @param entityType the entity type + /// @param id an identifier + /// @param lockOptions the lock mode + /// + /// @return a persistent instance or null + /// + /// @deprecated This method will be removed. + /// Use [#find(Class,Object,FindOption...)] instead. @Deprecated(since = "7.0", forRemoval = true) T get(Class entityType, Object id, LockOptions lockOptions); - /** - * Return the persistent instance of the given entity class with the given identifier, - * or null if there is no such persistent instance. If the instance is already associated - * with the session, return that instance. This method never returns an uninitialized - * instance. Obtain the specified lock mode if the instance exists. - * - * @param entityName the entity name - * @param id an identifier - * @param lockOptions contains the lock mode - * - * @return a persistent instance or null - * - * @deprecated This method will be removed. - * Use {@link SessionFactory#createGraphForDynamicEntity(String)} - * together with {@link #find(EntityGraph, Object, FindOption...)} - * to load {@link org.hibernate.metamodel.RepresentationMode#MAP - * dynamic entities}. - */ + /// Return the persistent instance of the given entity class with the given identifier, + /// or null if there is no such persistent instance. If the instance is already associated + /// with the session, return that instance. This method never returns an uninitialized + /// instance. Obtain the specified lock mode if the instance exists. + /// + /// @param entityName the entity name + /// @param id an identifier + /// @param lockOptions contains the lock mode + /// + /// @return a persistent instance or null + /// + /// @deprecated This method will be removed. + /// Use [SessionFactory#createGraphForDynamicEntity(String)] + /// together with [#find(EntityGraph,Object,FindOption...)] + /// to load [dynamic entities][org.hibernate.metamodel.RepresentationMode#MAP]. @Deprecated(since = "7.0", forRemoval = true) Object get(String entityName, Object id, LockOptions lockOptions); - /** - * Obtain a lock on the given managed instance associated with this session, - * using the given {@linkplain LockOptions lock options}. - *

- * This operation cascades to associated instances if the association is - * mapped with {@link org.hibernate.annotations.CascadeType#LOCK}. - * - * @param object a persistent instance associated with this session - * @param lockOptions the lock options - * - * @since 6.2 - * - * @deprecated This method will be removed. - * Use {@linkplain #lock(Object, LockModeType, LockOption...)} instead - */ + /// Obtain a lock on the given managed instance associated with this session, + /// using the given [lock options][LockOptions]. + /// + /// This operation cascades to associated instances if the association is + /// mapped with [org.hibernate.annotations.CascadeType#LOCK]. + /// + /// @param object a persistent instance associated with this session + /// @param lockOptions the lock options + /// + /// @since 6.2 + /// + /// @deprecated This method will be removed. + /// Use [#lock(Object, LockModeType, LockOption...)] instead @Deprecated(since = "7.0", forRemoval = true) void lock(Object object, LockOptions lockOptions); - /** - * Reread the state of the given managed instance from the underlying database, - * obtaining the given {@link LockMode}. - * - * @param object a persistent instance associated with this session - * @param lockOptions contains the lock mode to use - * - * @deprecated This method will be removed. - * Use {@linkplain #refresh(Object, RefreshOption...)} instead - */ + /// Reread the state of the given managed instance from the underlying database, + /// obtaining the given [LockMode]. + /// + /// @param object a persistent instance associated with this session + /// @param lockOptions contains the lock mode to use + /// + /// @deprecated This method will be removed. + /// Use [#refresh(Object, RefreshOption...)] instead @Deprecated(since = "7.0", forRemoval = true) void refresh(Object object, LockOptions lockOptions); - /** - * Return the entity name for the given persistent entity. - *

- * If the given entity is an uninitialized proxy, the proxy is initialized by - * side effect. - * - * @param object a persistent entity associated with this session - * - * @return the entity name - */ + /// Return the entity name for the given persistent entity. + /// + /// If the given entity is an uninitialized proxy, the proxy is initialized by + /// side effect. + /// + /// @param object a persistent entity associated with this session + /// + /// @return the entity name String getEntityName(Object object); - /** - * Return a reference to the persistent instance with the given class and identifier, - * making the assumption that the instance is still persistent in the database. This - * method never results in access to the underlying data store, and thus might return - * a proxy that is initialized on-demand, when a non-identifier method is accessed. - *

- * Note that {@link Hibernate#createDetachedProxy(SessionFactory, Class, Object)} - * may be used to obtain a detached reference. - *

- * It's sometimes necessary to narrow a reference returned by {@code getReference()} - * to a subtype of the given entity type. A direct Java typecast should never be used - * in this situation. Instead, the method {@link Hibernate#unproxy(Object, Class)} is - * the recommended way to narrow the type of a proxy object. Alternatively, a new - * reference may be obtained by simply calling {@code getReference()} again, passing - * the subtype. Either way, the narrowed reference will usually not be identical to - * the original reference, when the references are compared using the {@code ==} - * operator. - * - * @param entityType the entity type - * @param id the identifier of a persistent instance that exists in the database - * - * @return the persistent instance or proxy - */ + /// Return a reference to the persistent instance with the given class and identifier, + /// making the assumption that the instance is still persistent in the database. This + /// method never results in access to the underlying data store, and thus might return + /// a proxy that is initialized on-demand, when a non-identifier method is accessed. + /// + /// Note that [Hibernate#createDetachedProxy(SessionFactory,Class,Object)] + /// may be used to obtain a _detached_ reference. + /// + /// It's sometimes necessary to narrow a reference returned by `getReference()` + /// to a subtype of the given entity type. A direct Java typecast should never be used + /// in this situation. Instead, the method [Hibernate#unproxy(Object,Class)] is + /// the recommended way to narrow the type of a proxy object. Alternatively, a new + /// reference may be obtained by simply calling `getReference()` again, passing + /// the subtype. Either way, the narrowed reference will usually not be identical to + /// the original reference, when the references are compared using the `==` + /// operator. + /// + /// @param entityType the entity type + /// @param id the identifier of a persistent instance that exists in the database + /// + /// @return the persistent instance or proxy @Override T getReference(Class entityType, Object id); - /** - * Return a reference to the persistent instance of the given named entity with the - * given identifier, making the assumption that the instance is still persistent in - * the database. This method never results in access to the underlying data store, - * and thus might return a proxy that is initialized on-demand, when a non-identifier - * method is accessed. - * - * @param entityName the entity name - * @param id the identifier of a persistent instance that exists in the database - * - * @return the persistent instance or proxy - */ + /// Return a reference to the persistent instance of the given named entity with the + /// given identifier, making the assumption that the instance is still persistent in + /// the database. This method never results in access to the underlying data store, + /// and thus might return a proxy that is initialized on-demand, when a non-identifier + /// method is accessed. + /// + /// @param entityName the entity name + /// @param id the identifier of a persistent instance that exists in the database + /// + /// @return the persistent instance or proxy Object getReference(String entityName, Object id); - /** - * Return a reference to the persistent instance with the same identity as the given - * instance, which might be detached, making the assumption that the instance is still - * persistent in the database. This method never results in access to the underlying - * data store, and thus might return a proxy that is initialized on-demand, when a - * non-identifier method is accessed. - * - * @param object a detached persistent instance - * - * @return the persistent instance or proxy - * - * @since 6.0 - */ + /// Return a reference to the persistent instance with the same identity as the given + /// instance, which might be detached, making the assumption that the instance is still + /// persistent in the database. This method never results in access to the underlying + /// data store, and thus might return a proxy that is initialized on-demand, when a + /// non-identifier method is accessed. + /// + /// @param object a detached persistent instance + /// + /// @return the persistent instance or proxy + /// + /// @since 6.0 @Override T getReference(T object); - /** - * Create an {@link IdentifierLoadAccess} instance to retrieve an instance of the given - * entity type by its primary key. - * - * @param entityClass the entity type to be retrieved - * - * @return an instance of {@link IdentifierLoadAccess} for executing the lookup - * - * @throws HibernateException If the given class does not resolve as a mapped entity - * - * @deprecated This method will be removed. - * Use {@link #find(Class, Object, FindOption...)} instead. - * See {@link FindOption}. - */ + /// Create an [IdentifierLoadAccess] instance to retrieve an instance of the given + /// entity type by its primary key. + /// + /// @param entityClass the entity type to be retrieved + /// + /// @return an instance of [IdentifierLoadAccess] for executing the lookup + /// + /// @throws HibernateException If the given class does not resolve as a mapped entity + /// + /// @deprecated This method will be removed. + /// Use [#find(Class,Object,FindOption...)] instead. + /// See [FindOption]. @Deprecated(since = "7.1", forRemoval = true) IdentifierLoadAccess byId(Class entityClass); - /** - * Create an {@link IdentifierLoadAccess} instance to retrieve an instance of the named - * entity type by its primary key. - * - * @param entityName the entity name of the entity type to be retrieved - * - * @return an instance of {@link IdentifierLoadAccess} for executing the lookup - * - * @throws HibernateException If the given name does not resolve to a mapped entity - * - * @deprecated This method will be removed. - * Use {@link #find(String, Object, FindOption...)} instead. - * See {@link FindOption}. - */ + /// Create an [IdentifierLoadAccess] instance to retrieve an instance of the named + /// entity type by its primary key. + /// + /// @param entityName the entity name of the entity type to be retrieved + /// + /// @return an instance of [IdentifierLoadAccess] for executing the lookup + /// + /// @throws HibernateException If the given name does not resolve to a mapped entity + /// + /// @deprecated This method will be removed. + /// Use [#find(String,Object,FindOption...)] instead. + /// See [FindOption]. @Deprecated(since = "7.1", forRemoval = true) IdentifierLoadAccess byId(String entityName); - /** - * Create a {@link MultiIdentifierLoadAccess} instance to retrieve multiple instances - * of the given entity type by their primary key values, using batching. - * - * @param entityClass the entity type to be retrieved - * - * @return an instance of {@link MultiIdentifierLoadAccess} for executing the lookup - * - * @throws HibernateException If the given class does not resolve as a mapped entity - * - * @see #findMultiple(Class, List, FindOption...) - * - * @deprecated Use {@link #findMultiple(Class, List, FindOption...)} instead. - */ + /// Create a [MultiIdentifierLoadAccess] instance to retrieve multiple instances + /// of the given entity type by their primary key values, using batching. + /// + /// @param entityClass the entity type to be retrieved + /// + /// @return an instance of [MultiIdentifierLoadAccess] for executing the lookup + /// + /// @throws HibernateException If the given class does not resolve as a mapped entity + /// + /// @see #findMultiple(Class, List, FindOption...) + /// + /// @deprecated Use [#findMultiple(Class,List,FindOption...)] instead. @Deprecated(since = "7.2", forRemoval = true) MultiIdentifierLoadAccess byMultipleIds(Class entityClass); - /** - * Create a {@link MultiIdentifierLoadAccess} instance to retrieve multiple instances - * of the named entity type by their primary key values, using batching. - * - * @param entityName the entity name of the entity type to be retrieved - * - * @return an instance of {@link MultiIdentifierLoadAccess} for executing the lookup - * - * @throws HibernateException If the given name does not resolve to a mapped entity - * - * @deprecated Use {@link #findMultiple(EntityGraph, List, FindOption...)} instead, - * with {@linkplain SessionFactory#createGraphForDynamicEntity(String)}. - */ + /// Create a [MultiIdentifierLoadAccess] instance to retrieve multiple instances + /// of the named entity type by their primary key values, using batching. + /// + /// @param entityName the entity name of the entity type to be retrieved + /// + /// @return an instance of [MultiIdentifierLoadAccess] for executing the lookup + /// + /// @throws HibernateException If the given name does not resolve to a mapped entity + /// + /// @deprecated Use [#findMultiple(EntityGraph,List,FindOption...)] instead, + /// with {@linkplain SessionFactory#createGraphForDynamicEntity(String)}. @Deprecated(since = "7.2", forRemoval = true) MultiIdentifierLoadAccess byMultipleIds(String entityName); - /** - * Create a {@link NaturalIdLoadAccess} instance to retrieve an instance of the given - * entity type by its {@linkplain org.hibernate.annotations.NaturalId natural id}, - * which may be a composite natural id. The entity must have at least one attribute - * annotated {@link org.hibernate.annotations.NaturalId}. - * - * @param entityClass the entity type to be retrieved - * - * @return an instance of {@link NaturalIdLoadAccess} for executing the lookup - * - * @throws HibernateException If the given class does not resolve as a mapped entity, - * or if the entity does not declare a natural id - */ + /// Create a [NaturalIdLoadAccess] instance to retrieve an instance of the given + /// entity type by its {@linkplain org.hibernate.annotations.NaturalId natural id}, + /// which may be a composite natural id. The entity must have at least one attribute + /// annotated [org.hibernate.annotations.NaturalId]. + /// + /// @param entityClass the entity type to be retrieved + /// + /// @return an instance of [NaturalIdLoadAccess] for executing the lookup + /// + /// @throws HibernateException If the given class does not resolve as a mapped entity, + /// or if the entity does not declare a natural id + /// + /// @deprecated (since 7.3) : Use {@linkplain #find} with [KeyType#NATURAL] instead. + @Deprecated NaturalIdLoadAccess byNaturalId(Class entityClass); - /** - * Create a {@link NaturalIdLoadAccess} instance to retrieve an instance of the named - * entity type by its {@linkplain org.hibernate.annotations.NaturalId natural id}, - * which may be a composite natural id. The entity must have at least one attribute - * annotated {@link org.hibernate.annotations.NaturalId}. - * - * @param entityName the entity name of the entity type to be retrieved - * - * @return an instance of {@link NaturalIdLoadAccess} for executing the lookup - * - * @throws HibernateException If the given name does not resolve to a mapped entity, - * or if the entity does not declare a natural id - */ + /// Create a [NaturalIdLoadAccess] instance to retrieve an instance of the named + /// entity type by its {@linkplain org.hibernate.annotations.NaturalId natural id}, + /// which may be a composite natural id. The entity must have at least one attribute + /// annotated [org.hibernate.annotations.NaturalId]. + /// + /// @param entityName the entity name of the entity type to be retrieved + /// + /// @return an instance of [NaturalIdLoadAccess] for executing the lookup + /// + /// @throws HibernateException If the given name does not resolve to a mapped entity, + /// or if the entity does not declare a natural id + /// + /// @deprecated (since 7.3) : Use {@linkplain #find} with [KeyType#NATURAL] instead. + @Deprecated NaturalIdLoadAccess byNaturalId(String entityName); - /** - * Create a {@link SimpleNaturalIdLoadAccess} instance to retrieve an instance of the - * given entity type by its {@linkplain org.hibernate.annotations.NaturalId natural id}, - * which must be a simple (non-composite) value. The entity must have exactly one - * attribute annotated {@link org.hibernate.annotations.NaturalId}. - * - * @param entityClass the entity type to be retrieved - * - * @return an instance of {@link SimpleNaturalIdLoadAccess} for executing the lookup - * - * @throws HibernateException If the given class does not resolve as a mapped entity, - * or if the entity does not declare a natural id - */ + /// Create a [SimpleNaturalIdLoadAccess] instance to retrieve an instance of the + /// given entity type by its {@linkplain org.hibernate.annotations.NaturalId natural id}, + /// which must be a simple (non-composite) value. The entity must have exactly one + /// attribute annotated [org.hibernate.annotations.NaturalId]. + /// + /// @param entityClass the entity type to be retrieved + /// + /// @return an instance of [SimpleNaturalIdLoadAccess] for executing the lookup + /// + /// @throws HibernateException If the given class does not resolve as a mapped entity, + /// or if the entity does not declare a natural id + /// + /// @deprecated (since 7.3) : Use {@linkplain #find} with [KeyType#NATURAL] instead. + @Deprecated SimpleNaturalIdLoadAccess bySimpleNaturalId(Class entityClass); - /** - * Create a {@link SimpleNaturalIdLoadAccess} instance to retrieve an instance of the - * named entity type by its {@linkplain org.hibernate.annotations.NaturalId natural id}, - * which must be a simple (non-composite) value. The entity must have exactly one - * attribute annotated {@link org.hibernate.annotations.NaturalId}. - * - * @param entityName the entity name of the entity type to be retrieved - * - * @return an instance of {@link SimpleNaturalIdLoadAccess} for executing the lookup - * - * @throws HibernateException If the given name does not resolve to a mapped entity, - * or if the entity does not declare a natural id - */ + /// Create a [SimpleNaturalIdLoadAccess] instance to retrieve an instance of the + /// named entity type by its {@linkplain org.hibernate.annotations.NaturalId natural id}, + /// which must be a simple (non-composite) value. The entity must have exactly one + /// attribute annotated [org.hibernate.annotations.NaturalId]. + /// + /// @param entityName the entity name of the entity type to be retrieved + /// + /// @return an instance of [SimpleNaturalIdLoadAccess] for executing the lookup + /// + /// @throws HibernateException If the given name does not resolve to a mapped entity, + /// or if the entity does not declare a natural id + /// + /// @deprecated (since 7.3) : Use {@linkplain #find} with [KeyType#NATURAL] instead. + @Deprecated SimpleNaturalIdLoadAccess bySimpleNaturalId(String entityName); - /** - * Create a {@link MultiIdentifierLoadAccess} instance to retrieve multiple instances - * of the given entity type by their by {@linkplain org.hibernate.annotations.NaturalId - * natural id} values, using batching. - * - * @param entityClass the entity type to be retrieved - * - * @return an instance of {@link NaturalIdMultiLoadAccess} for executing the lookup - * - * @throws HibernateException If the given class does not resolve as a mapped entity, - * or if the entity does not declare a natural id - */ + /// Create a [MultiIdentifierLoadAccess] instance to retrieve multiple instances + /// of the given entity type by their by {@linkplain org.hibernate.annotations.NaturalId + /// natural id} values, using batching. + /// + /// @param entityClass the entity type to be retrieved + /// + /// @return an instance of [NaturalIdMultiLoadAccess] for executing the lookup + /// + /// @throws HibernateException If the given class does not resolve as a mapped entity, + /// or if the entity does not declare a natural id + /// + /// @deprecated (since 7.3) : Use {@linkplain #findMultiple} with [KeyType#NATURAL] instead. + /// + @Deprecated NaturalIdMultiLoadAccess byMultipleNaturalId(Class entityClass); - /** - * Create a {@link MultiIdentifierLoadAccess} instance to retrieve multiple instances - * of the named entity type by their by {@linkplain org.hibernate.annotations.NaturalId - * natural id} values, using batching. - * - * @param entityName the entity name of the entity type to be retrieved - * - * @return an instance of {@link NaturalIdMultiLoadAccess} for executing the lookup - * - * @throws HibernateException If the given name does not resolve to a mapped entity, - * or if the entity does not declare a natural id - */ + /// Create a [MultiIdentifierLoadAccess] instance to retrieve multiple instances + /// of the named entity type by their by {@linkplain org.hibernate.annotations.NaturalId + /// natural id} values, using batching. + /// + /// @param entityName the entity name of the entity type to be retrieved + /// + /// @return an instance of [NaturalIdMultiLoadAccess] for executing the lookup + /// + /// @throws HibernateException If the given name does not resolve to a mapped entity, + /// or if the entity does not declare a natural id + /// + /// @deprecated (since 7.3) : Use {@linkplain #findMultiple} with [KeyType#NATURAL] instead. + @Deprecated NaturalIdMultiLoadAccess byMultipleNaturalId(String entityName); - /** - * Get the {@linkplain SessionStatistics statistics} for this session. - * - * @return the session statistics being collected for this session - */ + /// Get the [statistics][SessionStatistics] for this session. + /// + /// @return the session statistics being collected for this session SessionStatistics getStatistics(); - /** - * Is the specified entity or proxy read-only? - *

- * To get the default read-only/modifiable setting used for - * entities and proxies that are loaded into the session use - * {@link #isDefaultReadOnly()} - * - * @see #isDefaultReadOnly() - * - * @param entityOrProxy an entity or proxy - * @return {@code true} if the entity or proxy is read-only, - * {@code false} if the entity or proxy is modifiable. - */ + /// Is the specified entity or proxy read-only? + /// + /// To get the default read-only/modifiable setting used for + /// entities and proxies that are loaded into the session use + /// [#isDefaultReadOnly()] + /// + /// @see #isDefaultReadOnly() + /// + /// @param entityOrProxy an entity or proxy + /// @return `true` if the entity or proxy is read-only, + /// `false` if the entity or proxy is modifiable. boolean isReadOnly(Object entityOrProxy); - /** - * Set an unmodified persistent object to read-only mode, or a read-only - * object to modifiable mode. In read-only mode, no snapshot is maintained, - * the instance is never dirty-checked, and mutations to the fields of the - * entity are not made persistent. - *

- * If the entity or proxy already has the specified read-only/modifiable - * setting, then this method does nothing. - *

- * To set the default read-only/modifiable setting used for all entities - * and proxies that are loaded into the session use - * {@link #setDefaultReadOnly(boolean)}. - *

- * To override the default read-only mode of the current session for - * all entities and proxies returned by a given {@code Query}, use - * {@link Query#setReadOnly(boolean)}. - *

- * Every instance of an {@linkplain org.hibernate.annotations.Immutable - * immutable} entity is loaded in read-only mode. An immutable entity may - * not be set to modifiable. - * - * @see #setDefaultReadOnly(boolean) - * @see Query#setReadOnly(boolean) - * @see IdentifierLoadAccess#withReadOnly(boolean) - * @see org.hibernate.annotations.Immutable - * - * @param entityOrProxy an entity or proxy - * @param readOnly {@code true} if the entity or proxy should be made read-only; - * {@code false} if the entity or proxy should be made modifiable - * - * @throws IllegalStateException if an immutable entity is set to modifiable - */ + /// Set an unmodified persistent object to read-only mode, or a read-only + /// object to modifiable mode. In read-only mode, no snapshot is maintained, + /// the instance is never dirty-checked, and mutations to the fields of the + /// entity are not made persistent. + /// + /// If the entity or proxy already has the specified read-only/modifiable + /// setting, then this method does nothing. + /// + /// To set the default read-only/modifiable setting used for all entities + /// and proxies that are loaded into the session use + /// [#setDefaultReadOnly(boolean)]. + /// + /// To override the default read-only mode of the current session for + /// all entities and proxies returned by a given `Query`, use + /// [Query#setReadOnly(boolean)]. + /// + /// Every instance of an [immutable][org.hibernate.annotations.Immutable] + /// entity is loaded in read-only mode. An immutable entity may + /// not be set to modifiable. + /// + /// @see #setDefaultReadOnly(boolean) + /// @see Query#setReadOnly(boolean) + /// @see IdentifierLoadAccess#withReadOnly(boolean) + /// @see org.hibernate.annotations.Immutable + /// + /// @param entityOrProxy an entity or proxy + /// @param readOnly `true` if the entity or proxy should be made read-only; + /// `false` if the entity or proxy should be made modifiable + /// + /// @throws IllegalStateException if an immutable entity is set to modifiable void setReadOnly(Object entityOrProxy, boolean readOnly); - /** - * Is the {@link org.hibernate.annotations.FetchProfile fetch profile} - * with the given name enabled in this session? - * - * @param name the name of the profile - * @return True if fetch profile is enabled; false if not. - * - * @throws UnknownProfileException Indicates that the given name does not - * match any known fetch profile names - * - * @see org.hibernate.annotations.FetchProfile - */ + /// Is the [fetch profile][org.hibernate.annotations.FetchProfile] + /// with the given name enabled in this session? + /// + /// @param name the name of the profile + /// @return True if fetch profile is enabled; false if not. + /// + /// @throws UnknownProfileException Indicates that the given name does not + /// match any known fetch profile names + /// + /// @see org.hibernate.annotations.FetchProfile boolean isFetchProfileEnabled(String name) throws UnknownProfileException; - /** - * Enable the {@link org.hibernate.annotations.FetchProfile fetch profile} - * with the given name in this session. If the requested fetch profile is - * already enabled, the call has no effect. - * - * @param name the name of the fetch profile to be enabled - * - * @throws UnknownProfileException Indicates that the given name does not - * match any known fetch profile names - * - * @see org.hibernate.annotations.FetchProfile - */ + /// Enable the [fetch profile][org.hibernate.annotations.FetchProfile] + /// with the given name in this session. If the requested fetch profile is + /// already enabled, the call has no effect. + /// + /// @param name the name of the fetch profile to be enabled + /// + /// @throws UnknownProfileException Indicates that the given name does not + /// match any known fetch profile names + /// + /// @see org.hibernate.annotations.FetchProfile void enableFetchProfile(String name) throws UnknownProfileException; - /** - * Disable the {@link org.hibernate.annotations.FetchProfile fetch profile} - * with the given name in this session. If the requested fetch profile is - * not currently enabled, the call has no effect. - * - * @param name the name of the fetch profile to be disabled - * - * @throws UnknownProfileException Indicates that the given name does not - * match any known fetch profile names - * - * @see org.hibernate.annotations.FetchProfile - */ + /// Disable the [fetch profile][org.hibernate.annotations.FetchProfile] + /// with the given name in this session. If the requested fetch profile is + /// not currently enabled, the call has no effect. + /// + /// @param name the name of the fetch profile to be disabled + /// + /// @throws UnknownProfileException Indicates that the given name does not + /// match any known fetch profile names + /// + /// @see org.hibernate.annotations.FetchProfile void disableFetchProfile(String name) throws UnknownProfileException; - /** - * Obtain a {@linkplain LobHelper} for instances of {@link java.sql.Blob} - * and {@link java.sql.Clob}. - * - * @return an instance of {@link LobHelper} - * - * @deprecated Use {@link Hibernate#getLobHelper()} instead. - */ + /// Obtain a {@linkplain LobHelper} for instances of [java.sql.Blob] + /// and [java.sql.Clob]. + /// + /// @return an instance of [LobHelper] + /// + /// @deprecated Use [#getLobHelper()] instead. @Deprecated(since="7.0", forRemoval = true) LobHelper getLobHelper(); - /** - * Obtain the collection of all managed entities which belong to this - * persistence context. - * - * @since 7.0 - */ + /// Obtain the collection of all managed entities which belong to this + /// persistence context. + /// + /// @since 7.0 @Incubating Collection getManagedEntities(); - /** - * Obtain a collection of all managed instances of the entity type with the - * given entity name which belong to this persistence context. - * - * @since 7.0 - */ + /// Obtain a collection of all managed instances of the entity type with the + /// given entity name which belong to this persistence context. + /// + /// @since 7.0 @Incubating Collection getManagedEntities(String entityName); - /** - * Obtain a collection of all managed entities of the given type which belong - * to this persistence context. This operation is not polymorphic, and does - * not return instances of subtypes of the given entity type. - * - * @since 7.0 - */ + /// Obtain a collection of all managed entities of the given type which belong + /// to this persistence context. This operation is not polymorphic, and does + /// not return instances of subtypes of the given entity type. + /// + /// @since 7.0 @Incubating Collection getManagedEntities(Class entityType); - /** - * Obtain a collection of all managed entities of the given type which belong - * to this persistence context. This operation is not polymorphic, and does - * not return instances of subtypes of the given entity type. - * - * @since 7.0 - */ + /// Obtain a collection of all managed entities of the given type which belong + /// to this persistence context. This operation is not polymorphic, and does + /// not return instances of subtypes of the given entity type. + /// + /// @since 7.0 @Incubating Collection getManagedEntities(EntityType entityType); - /** - * Add one or more listeners to the Session - * - * @param listeners the listener(s) to add - */ + /// Add one or more listeners to the Session + /// + /// @param listeners the listener(s) to add void addEventListeners(SessionEventListener... listeners); - /** - * Set a hint. The hints understood by Hibernate are enumerated by - * {@link org.hibernate.jpa.AvailableHints}. - * - * @see org.hibernate.jpa.HibernateHints - * @see org.hibernate.jpa.SpecHints - * - * @apiNote Hints are a - * {@linkplain jakarta.persistence.EntityManager#setProperty - * JPA-standard way} to control provider-specific behavior of the - * {@code EntityManager}. Clients of the native API defined by - * Hibernate should make use of type-safe operations of this - * interface. For example, {@link #enableFetchProfile(String)} - * should be used in preference to the hint - * {@link org.hibernate.jpa.HibernateHints#HINT_FETCH_PROFILE}. - */ + /// Set a hint. The hints understood by Hibernate are enumerated by + /// [org.hibernate.jpa.AvailableHints]. + /// + /// @see org.hibernate.jpa.HibernateHints + /// @see org.hibernate.jpa.SpecHints + /// + /// @apiNote Hints are a [JPA-standard way][jakarta.persistence.EntityManager#setProperty] + /// to control provider-specific behavior of the + /// [EntityManager]. Clients of the native API defined by + /// Hibernate should make use of type-safe operations of this + /// interface. For example, [#enableFetchProfile(String)] + /// should be used in preference to the hint [org.hibernate.jpa.HibernateHints#HINT_FETCH_PROFILE]. @Override void setProperty(String propertyName, Object value); - /** - * Create a new mutable instance of {@link EntityGraph}, with only - * a root node, allowing programmatic definition of the graph from - * scratch. - * - * @param rootType The root entity of the graph - * - * @see #find(EntityGraph, Object, FindOption...) - * @see org.hibernate.query.SelectionQuery#setEntityGraph(EntityGraph, org.hibernate.graph.GraphSemantic) - * @see org.hibernate.graph.EntityGraphs#createGraph(jakarta.persistence.metamodel.EntityType) - */ + /// Create a new mutable instance of [EntityGraph], with only + /// a root node, allowing programmatic definition of the graph from + /// scratch. + /// + /// @param rootType The root entity of the graph + /// + /// @see #find(EntityGraph, Object, FindOption...) + /// @see org.hibernate.query.SelectionQuery#setEntityGraph(EntityGraph, org.hibernate.graph.GraphSemantic) + /// @see org.hibernate.graph.EntityGraphs#createGraph(jakarta.persistence.metamodel.EntityType) @Override RootGraph createEntityGraph(Class rootType); - /** - * Create a new mutable instance of {@link EntityGraph}, based on - * a predefined {@linkplain jakarta.persistence.NamedEntityGraph - * named entity graph}, allowing customization of the graph, or - * return {@code null} if there is no predefined graph with the - * given name. - * - * @param graphName The name of the predefined named entity graph - * - * @apiNote This method returns {@code RootGraph}, requiring an - * unchecked typecast before use. It's cleaner to obtain a graph using - * {@link #createEntityGraph(Class, String)} instead. - * - * @see #find(EntityGraph, Object, FindOption...) - * @see org.hibernate.query.SelectionQuery#setEntityGraph(EntityGraph, org.hibernate.graph.GraphSemantic) - * @see jakarta.persistence.EntityManagerFactory#addNamedEntityGraph(String, EntityGraph) - */ + /// Create a new mutable instance of [EntityGraph], based on + /// a predefined [named entity graph][jakarta.persistence.NamedEntityGraph], + /// allowing customization of the graph, or return `null` if there is no + /// predefined graph with the given name. + /// + /// @param graphName The name of the predefined named entity graph + /// + /// @apiNote This method returns `RootGraph`, requiring an + /// unchecked typecast before use. It's cleaner to obtain a graph using + /// [#createEntityGraph(Class,String)] instead. + /// + /// @see #find(EntityGraph, Object, FindOption...) + /// @see org.hibernate.query.SelectionQuery#setEntityGraph(EntityGraph, org.hibernate.graph.GraphSemantic) + /// @see jakarta.persistence.EntityManagerFactory#addNamedEntityGraph(String, EntityGraph) @Override RootGraph createEntityGraph(String graphName); - /** - * Obtain an immutable reference to a predefined - * {@linkplain jakarta.persistence.NamedEntityGraph named entity graph} - * or return {@code null} if there is no predefined graph with the given - * name. - * - * @param graphName The name of the predefined named entity graph - * - * @apiNote This method returns {@code RootGraph}, requiring an - * unchecked typecast before use. It's cleaner to obtain a graph using - * the static metamodel for the class which defines the graph, or by - * calling {@link SessionFactory#getNamedEntityGraphs(Class)} instead. - * - * @see #find(EntityGraph, Object, FindOption...) - * @see org.hibernate.query.SelectionQuery#setEntityGraph(EntityGraph, org.hibernate.graph.GraphSemantic) - * @see jakarta.persistence.EntityManagerFactory#addNamedEntityGraph(String, EntityGraph) - */ + /// Obtain an immutable reference to a predefined + /// [named entity graph][jakarta.persistence.NamedEntityGraph] + /// or return `null` if there is no predefined graph with the given + /// name. + /// + /// @param graphName The name of the predefined named entity graph + /// + /// @apiNote This method returns `RootGraph`, requiring an + /// unchecked typecast before use. It's cleaner to obtain a graph using + /// the static metamodel for the class which defines the graph, or by + /// calling [SessionFactory#getNamedEntityGraphs(Class)] instead. + /// + /// @see #find(EntityGraph, Object, FindOption...) + /// @see org.hibernate.query.SelectionQuery#setEntityGraph(EntityGraph, org.hibernate.graph.GraphSemantic) + /// @see jakarta.persistence.EntityManagerFactory#addNamedEntityGraph(String, EntityGraph) @Override RootGraph getEntityGraph(String graphName); - /** - * Retrieve all named {@link EntityGraph}s with the given root entity type. - * - * @see jakarta.persistence.EntityManagerFactory#getNamedEntityGraphs(Class) - * @see jakarta.persistence.EntityManagerFactory#addNamedEntityGraph(String, EntityGraph) - */ + /// Retrieve all named [EntityGraph]s with the given root entity type. + /// + /// @see jakarta.persistence.EntityManagerFactory#getNamedEntityGraphs(Class) + /// @see jakarta.persistence.EntityManagerFactory#addNamedEntityGraph(String, EntityGraph) @Override List> getEntityGraphs(Class entityClass); - // The following overrides should not be necessary, + // The following overrides should not be necessary // and are only needed to work around a bug in IntelliJ @Override @@ -1527,6 +1374,9 @@ public interface Session extends SharedSessionContract, EntityManager { @Override @Deprecated @SuppressWarnings("rawtypes") Query createQuery(String queryString); + @Override @Deprecated @SuppressWarnings("rawtypes") + NativeQuery createNativeQuery(String queryString); + @Override Query createNamedQuery(String name, Class resultClass); @@ -1536,19 +1386,15 @@ public interface Session extends SharedSessionContract, EntityManager { @Override Query createQuery(CriteriaQuery criteriaQuery); - /** - * Create a {@link Query} for the given JPA {@link CriteriaDelete}. - * - * @deprecated use {@link #createMutationQuery(CriteriaDelete)} - */ + /// Create a [Query] for the given JPA [CriteriaDelete]. + /// + /// @deprecated use [#createMutationQuery(CriteriaDelete)] @Override @Deprecated(since = "6.0") @SuppressWarnings("rawtypes") Query createQuery(CriteriaDelete deleteQuery); - /** - * Create a {@link Query} for the given JPA {@link CriteriaUpdate}. - * - * @deprecated use {@link #createMutationQuery(CriteriaUpdate)} - */ + /// Create a [Query] for the given JPA [CriteriaUpdate]. + /// + /// @deprecated use [#createMutationQuery(CriteriaUpdate)] @Override @Deprecated(since = "6.0") @SuppressWarnings("rawtypes") Query createQuery(CriteriaUpdate updateQuery); diff --git a/hibernate-core/src/main/java/org/hibernate/SessionCheckMode.java b/hibernate-core/src/main/java/org/hibernate/SessionCheckMode.java index 4e9b21337cdd..ec0ba99b3522 100644 --- a/hibernate-core/src/main/java/org/hibernate/SessionCheckMode.java +++ b/hibernate-core/src/main/java/org/hibernate/SessionCheckMode.java @@ -13,16 +13,16 @@ /** * Indicates whether the persistence context should be checked for entities * matching the identifiers to be loaded -

    - *
  • Entities which are in a managed state are not re-loaded from the database. - * those identifiers are removed from the SQL restriction sent to the database. - *
  • Entities which are in a removed state are {@linkplain RemovalsMode#REPLACE excluded} + *
  • Entities which are in a managed state are not reloaded from the database. + * Those identifiers are removed from the SQL restriction sent to the database. + *
  • Entities which are in a removed state are {@linkplain RemovalsMode#REPLACE replaced with null} * from the result by default, but can be {@linkplain RemovalsMode#INCLUDE included} if desired. *
*

- * The default is {@link #DISABLED} + * The default is {@link #DISABLED}. * - * @see org.hibernate.Session#findMultiple(Class, List , FindOption...) - * @see org.hibernate.Session#findMultiple(EntityGraph, List , FindOption...) + * @see org.hibernate.Session#findMultiple(Class, List, FindOption...) + * @see org.hibernate.Session#findMultiple(EntityGraph, List, FindOption...) * * @since 7.2 */ diff --git a/hibernate-core/src/main/java/org/hibernate/SessionFactory.java b/hibernate-core/src/main/java/org/hibernate/SessionFactory.java index c03e63bd2ad7..2991a8b956ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/SessionFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/SessionFactory.java @@ -13,7 +13,6 @@ import jakarta.persistence.TypedQueryReference; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.engine.spi.FilterDefinition; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.graph.GraphParser; import org.hibernate.graph.InvalidGraphException; import org.hibernate.graph.RootGraph; @@ -46,7 +45,8 @@ * Typically, a program has a single {@link SessionFactory} instance, and must * obtain a new {@link Session} instance from the factory each time it services * a client request. It is then also responsible for {@linkplain Session#close() - * destroying} the session at the end of the client request. + * destroying} the session at the end of the client request. An instance of + * {@code Session} must never be shared between multiple threads. *

* The {@link #inSession} and {@link #inTransaction} methods provide a convenient * way to obtain a session, with or without starting a transaction, and have it @@ -108,7 +108,7 @@ * SingularAttribute<Book,String>}. * *

- * Use of these statically-typed metamodel references is the preferred way of + * Use of these statically typed metamodel references is the preferred way of * working with the {@linkplain jakarta.persistence.criteria.CriteriaBuilder * criteria query API}, and with {@linkplain EntityGraph}s. *

@@ -266,7 +266,7 @@ default void inSession(Consumer action) { * @since 6.3 */ default void inStatelessSession(Consumer action) { - try ( StatelessSession session = openStatelessSession() ) { + try ( var session = openStatelessSession() ) { action.accept( session ); } } @@ -308,7 +308,7 @@ default void inStatelessTransaction(Consumer action) { * @see #fromTransaction(Function) */ default R fromSession(Function action) { - try ( Session session = openSession() ) { + try ( var session = openSession() ) { return action.apply( session ); } } @@ -331,7 +331,7 @@ default R fromSession(Function action) { * @since 6.3 */ default R fromStatelessSession(Function action) { - try ( StatelessSession session = openStatelessSession() ) { + try ( var session = openStatelessSession() ) { return action.apply( session ); } } @@ -522,9 +522,7 @@ default RootGraph createEntityGraph(Class entityType) { * * @since 7.0 */ - default RootGraph parseEntityGraph(Class rootEntityClass, CharSequence graphText) { - return GraphParser.parse( rootEntityClass, graphText.toString(), unwrap( SessionFactoryImplementor.class ) ); - } + RootGraph parseEntityGraph(Class rootEntityClass, CharSequence graphText); /** * Creates a {@link RootGraph} for the given {@code rootEntityName} and parses the graph @@ -543,9 +541,8 @@ default RootGraph parseEntityGraph(Class rootEntityClass, CharSequence * * @since 7.0 */ - default RootGraph parseEntityGraph(String rootEntityName, CharSequence graphText) { - return GraphParser.parse( rootEntityName, graphText.toString(), unwrap( SessionFactoryImplementor.class ) ); - } + @Incubating + RootGraph parseEntityGraph(String rootEntityName, CharSequence graphText); /** * Creates a {@link RootGraph} based on the passed string representation. Here, the @@ -560,9 +557,8 @@ default RootGraph parseEntityGraph(String rootEntityName, CharSequence gr * * @since 7.0 */ - default RootGraph parseEntityGraph(CharSequence graphText) { - return GraphParser.parse( graphText.toString(), unwrap( SessionFactoryImplementor.class ) ); - } + @Incubating + RootGraph parseEntityGraph(CharSequence graphText); /** * Obtain the set of names of all {@linkplain org.hibernate.annotations.FilterDef diff --git a/hibernate-core/src/main/java/org/hibernate/SimpleNaturalIdLoadAccess.java b/hibernate-core/src/main/java/org/hibernate/SimpleNaturalIdLoadAccess.java index 13130fd7c18e..bdcb8a61de8c 100644 --- a/hibernate-core/src/main/java/org/hibernate/SimpleNaturalIdLoadAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/SimpleNaturalIdLoadAccess.java @@ -29,7 +29,10 @@ * @see Session#bySimpleNaturalId(Class) * @see org.hibernate.annotations.NaturalId * @see NaturalIdLoadAccess + * + * @deprecated (since 7.3) Use {@linkplain Session#findByNaturalId} instead. */ +@Deprecated public interface SimpleNaturalIdLoadAccess { /** diff --git a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java index 17a968aea794..e60fa236161d 100644 --- a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java +++ b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java @@ -97,7 +97,9 @@ public interface StatelessSession extends SharedSessionContract { Object insert(Object entity); /** - * Insert multiple records. + * Insert multiple records in the same order as the entity + * instances representing the new records occur in the given + * list. * * @param entities a list of transient instances to be inserted * @@ -130,7 +132,9 @@ public interface StatelessSession extends SharedSessionContract { void update(Object entity); /** - * Update multiple records. + * Update multiple records in the same order as the entity + * instances representing the records occur in the given + * list. * * @param entities a list of detached instances to be updated * @@ -161,7 +165,9 @@ public interface StatelessSession extends SharedSessionContract { void delete(Object entity); /** - * Delete multiple records. + * Delete multiple records in the same order as the entity + * instances representing the records occur in the given + * list. * * @param entities a list of detached instances to be deleted * @@ -206,9 +212,11 @@ public interface StatelessSession extends SharedSessionContract { void upsert(Object entity); /** - * Perform an upsert, that is, to insert the record if it does - * not exist, or update the record if it already exists, for - * each given record. + * Upsert multiple records, that is, for a given record, + * insert the record if it does not exist or update the + * record if it already exists, in the same order as the + * entity instances representing the records occur in + * the given list. * * @param entities a list of detached instances and new * instances with assigned identifiers diff --git a/hibernate-core/src/main/java/org/hibernate/Timeouts.java b/hibernate-core/src/main/java/org/hibernate/Timeouts.java index ddeae2940271..c96315975244 100644 --- a/hibernate-core/src/main/java/org/hibernate/Timeouts.java +++ b/hibernate-core/src/main/java/org/hibernate/Timeouts.java @@ -146,4 +146,17 @@ static int fromHint(Object factoryHint) { } return Integer.parseInt( factoryHint.toString() ); } + + static Timeout fromHintTimeout(Object factoryHint) { + if ( factoryHint == null ) { + return WAIT_FOREVER; + } + if ( factoryHint instanceof Timeout timeout ) { + return timeout; + } + if ( factoryHint instanceof Integer number ) { + return Timeout.milliseconds( number ); + } + return Timeout.milliseconds( Integer.parseInt( factoryHint.toString() ) ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/IdGeneratorType.java b/hibernate-core/src/main/java/org/hibernate/annotations/IdGeneratorType.java index 318fdfe1509d..d3b5e0833efb 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/IdGeneratorType.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/IdGeneratorType.java @@ -22,7 +22,7 @@ * For example, if we have a custom identifier generator: *

  * public class CustomSequenceGenerator implements BeforeExecutionGenerator {
- *     public CustomSequenceGenerator(CustomSequence config, Member annotatedMember,
+ *     public CustomSequenceGenerator(CustomSequence config,
  *                                    GeneratorCreationContext context) {
  *         ...
  *     }
diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/NamedEntityGraph.java b/hibernate-core/src/main/java/org/hibernate/annotations/NamedEntityGraph.java
index aa5aed0ac291..e3b397ff475d 100644
--- a/hibernate-core/src/main/java/org/hibernate/annotations/NamedEntityGraph.java
+++ b/hibernate-core/src/main/java/org/hibernate/annotations/NamedEntityGraph.java
@@ -45,7 +45,7 @@
 	 * Entity graph names must be unique within the persistence unit.
 	 * 

* When applied to a root entity class, the name is optional and - * defaults to the entity-name of that entity. + * defaults to the JPA entity name of that entity. */ String name() default ""; diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/NaturalId.java b/hibernate-core/src/main/java/org/hibernate/annotations/NaturalId.java index 4eca9fc13bab..0d5325aa9a91 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/NaturalId.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/NaturalId.java @@ -4,6 +4,7 @@ */ package org.hibernate.annotations; + import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -11,80 +12,107 @@ import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; -/** - * Specifies that a field or property of an entity class is part of - * the natural id of the entity. This annotation is very useful when - * the primary key of an entity class is a surrogate key, that is, - * a {@linkplain jakarta.persistence.GeneratedValue system-generated} - * synthetic identifier, with no domain-model semantics. There should - * always be some other field or combination of fields which uniquely - * identifies an instance of the entity from the point of view of the - * user of the system. This is the natural id of the entity. - *

- * A natural id may be a single field or property of the entity: - *

- * @Entity
- * @Cache @NaturalIdCache
- * class Person {
- *
- *     //synthetic id
- *     @GeneratedValue @Id
- *     Long id;
- *
- *     @NotNull
- *     String name;
- *
- *     //simple natural id
- *     @NotNull @NaturalId
- *     String ssn;
- *
- *     ...
- * }
- * 
- *

- * or it may be a composite value: - *

- * @Entity
- * @Cache @NaturalIdCache
- * class Vehicle {
- *
- *     //synthetic id
- *     @GeneratedValue @Id
- *     Long id;
- *
- *     //composite natural id
- *
- *     @Enumerated
- *     @NotNull @NaturalId
- *     Region region;
- *
- *     @NotNull @NaturalId
- *     String registration;
- *
- *     ...
- * }
- * 
- *

- * Unlike the {@linkplain jakarta.persistence.Id primary identifier} - * of an entity, a natural id may be {@linkplain #mutable}. - *

- * On the other hand, a field or property which forms part of a natural - * id may never be null, and so it's a good idea to use {@code @NaturalId} - * in conjunction with the Bean Validation {@code @NotNull} annotation - * or {@link jakarta.persistence.Basic#optional @Basic(optional=false)}. - *

- * The {@link org.hibernate.Session} interface offers several methods - * that allow an entity instance to be retrieved by its - * {@linkplain org.hibernate.Session#bySimpleNaturalId(Class) simple} - * or {@linkplain org.hibernate.Session#byNaturalId(Class) composite} - * natural id value. If the entity is also marked for {@linkplain - * NaturalIdCache natural id caching}, then these methods may be able - * to avoid a database round trip. - * - * @author Nicolás Lichtmaier - * - * @see NaturalIdCache - */ +/// Specifies that a field or property of an entity class is part of +/// the natural id of the entity. This annotation is very useful when +/// the primary key of an entity class is a surrogate key, that is, +/// a {@linkplain jakarta.persistence.GeneratedValue system-generated} +/// synthetic identifier, with no domain-model semantics. There should +/// always be some other field or combination of fields that uniquely +/// identifies an instance of the entity from the point of view of the +/// user of the system. This is the _natural id_ of the entity. +/// +/// A natural id may be a single (basic or embedded) attribute of the entity: +/// ````java +/// @Entity +/// class Person { +/// +/// //synthetic id +/// @GeneratedValue @Id +/// Long id; +/// +/// @NotNull +/// String name; +/// +/// //simple natural id +/// @NotNull @NaturalId +/// String ssn; +/// +/// ... +/// } +/// ``` +/// +/// or it may be a non-aggregated composite value: +/// ```java +/// @Entity +/// class Vehicle { +/// +/// //synthetic id +/// @GeneratedValue @Id +/// Long id; +/// +/// //composite natural id +/// +/// @Enumerated +/// @NotNull +/// @NaturalId +/// Region region; +/// +/// @NotNull +/// @NaturalId +/// String registration; +/// +/// ... +/// } +/// ``` +/// +/// Unlike the {@linkplain jakarta.persistence.Id primary identifier} +/// of an entity, a natural id may be {@linkplain #mutable}. +/// +/// On the other hand, a field or property which forms part of a natural +/// id may never be null, and so it's a good idea to use `@NaturalId` +/// in conjunction with the Bean Validation `@NotNull` annotation +/// or [@Basic(optional=false)][jakarta.persistence.Basic#optional()]. +/// +/// The [org.hibernate.Session] interface offers several methods +/// that allow retrieval of one or more entity references by natural-id +/// allow an entity instance to be retrieved by its natural-id: +/// * [org.hibernate.Session#find(Class, Object, jakarta.persistence.FindOption...)] +/// allows loading a single entity instance by natural-id. +/// * [org.hibernate.Session#findMultiple(Class, java.util.List, jakarta.persistence.FindOption...)] +/// allows loading multiple entity instances by natural-id. +/// +/// ``` +/// Person person = session.find(Person.class, ssn, KeyType.NATURAL); +/// ``` +/// ``` +/// Vehicle vehicle = +/// session.find(Vehicle.class, +/// Map.of(Vehicle_.REGION, region, +/// Vehicle_.REGISTRATION, registration), +/// KeyType.NATURAL); +/// ``` +/// ``` +/// List people = session.findMultiple(Person.class, ssns, KeyType.NATURAL); +/// ``` +/// +/// If the entity is also marked for [natural id caching][NaturalIdCache], +/// then these methods may be able to avoid a database round trip. +/// +/// @see org.hibernate.Session#find(Class, Object, jakarta.persistence.FindOption...) +/// @see org.hibernate.Session#findMultiple(Class, java.util.List, jakarta.persistence.FindOption...) +/// @see NaturalIdClass +/// @see NaturalIdCache +/// +/// @apiNote For non-aggregated composite natural-id cases, it is recommended to +/// leverage [@NaturalIdClass][NaturalIdClass] for loading. +/// ``` +/// Vehicle vehicle = +/// session.find(Vehicle.class, +/// VehicleKey(region, registration), +/// KeyType.NATURAL); +/// ``` +/// +/// @author Nicolás Lichtmaier @Target({METHOD, FIELD}) @Retention(RUNTIME) public @interface NaturalId { diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/NaturalIdClass.java b/hibernate-core/src/main/java/org/hibernate/annotations/NaturalIdClass.java new file mode 100644 index 000000000000..eafb1a61fc86 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/annotations/NaturalIdClass.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.Retention; + +/// Models a non-aggregated composite natural-id for the purpose of loading. +/// The non-aggregated form uses multiple [@NaturalId][NaturalId] as opposed +/// to the aggregated form which uses a single [@NaturalId][NaturalId] combined +/// with [@Embedded][jakarta.persistence.Embedded]. +/// Functions in a similar fashion as [@IdClass][jakarta.persistence.IdClass] for +/// non-aggregated composite identifiers. +/// +/// ```java +/// @Entity +/// @NaturalIdClass(OrderNaturalId.class) +/// class Order { +/// @Id +/// Integer id; +/// @NaturalId @ManyToOne +/// Customer customer; +/// @NaturalId +/// Integer orderNumber; +/// ... +/// } +/// +/// class OrderNaturalId { +/// Customer customer; +/// Integer orderNumber; +/// ... +/// } +/// ``` +/// +/// @see NaturalId +/// @see jakarta.persistence.IdClass +/// +/// @since 7.3 +/// +/// @author Steve Ebersole +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface NaturalIdClass { + /// The class to use for loading the associated entity by natural-id. + Class value(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Type.java b/hibernate-core/src/main/java/org/hibernate/annotations/Type.java index 9b5724aee625..bb216e05f9f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Type.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Type.java @@ -8,6 +8,7 @@ import java.lang.annotation.Target; import org.hibernate.usertype.UserType; +import org.hibernate.usertype.UserTypeCreationContext; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.FIELD; @@ -31,7 +32,7 @@ * BigDecimal amount; *

*

- * we may define an annotation type: + * we may define a custom annotation type: *

  * @Retention(RUNTIME)
  * @Target({METHOD,FIELD})
@@ -47,6 +48,27 @@
  * 

* which is much cleaner. *

+ * An implementation of {@link UserType} applied via a custom annotation + * may declare a constructor which accepts the annotation instance, + * allowing the annotation to be used to configure the type. + *

+ * @Retention(RUNTIME)
+ * @Target({METHOD,FIELD})
+ * @Type(MonetaryAmountUserType.class)
+ * public @interface MonetaryAmount {
+ *     public Currency currency();
+ * }
+ * 
+ *
+ * public class MonetaryAmountUserType implements UserType<Amount> {
+ *     private final Currency currency;
+ *     public MonetaryAmountUserType(MonetaryAmount annotation) {
+ *         currency = annotation.currency();
+ *     }
+ *     ...
+ * }
+ * 
+ *

* The use of a {@code UserType} is usually mutually exclusive with the * compositional approach of {@link JavaType} and {@link JdbcType}. * @@ -64,9 +86,14 @@ /** * Parameters to be injected into the custom type after it is - * instantiated. The {@link UserType} implementation must implement + * instantiated. The {@link UserType} implementation may implement * {@link org.hibernate.usertype.ParameterizedType} to receive the - * parameters. + * parameters, or it may obtain them via + * {@link UserTypeCreationContext#getParameters()}. + * + * @apiNote A better approach is to declare a custom annotation type, + * as described {@linkplain Type above}, and specify parameters in a + * type safe way as members of the custom annotation. */ Parameter[] parameters() default {}; } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/ValueGenerationType.java b/hibernate-core/src/main/java/org/hibernate/annotations/ValueGenerationType.java index 802833f1006f..36cb3e2c37cf 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/ValueGenerationType.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/ValueGenerationType.java @@ -26,8 +26,7 @@ *

  * public class SKUGeneration
  *         implements BeforeExecutionGenerator {
- *     public SKUGeneration(SKU sku, Member annotatedMember,
- *                          GeneratorCreationContext context) {
+ *     public SKUGeneration(SKU sku, GeneratorCreationContext context) {
  *         ...
  *     }
  *     ...
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/BootLogging.java b/hibernate-core/src/main/java/org/hibernate/boot/BootLogging.java
index d827eeeafec8..c97d1331a293 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/BootLogging.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/BootLogging.java
@@ -45,7 +45,6 @@ public interface BootLogging extends BasicLogger {
 	@Message(id = 160101, value = "Duplicate generator name: '%s'")
 	void duplicateGeneratorName(String name);
 
-
 	@LogMessage(level = DEBUG)
 	@Message(id = 160111, value = "Package not found or no package-info.java: %s")
 	void packageNotFound(String packageName);
@@ -84,7 +83,6 @@ public interface BootLogging extends BasicLogger {
 					@ManyToOne and @OneToOne associations mapped with @NotFound are forced to EAGER fetching""")
 	void ignoreNotFoundWithFetchTypeLazy(String entity, String association);
 
-	// --- New typed TRACE/DEBUG messages for boot internals ---
 	@LogMessage(level = TRACE)
 	@Message(id = 160140, value = "Binding formula: %s")
 	void bindingFormula(String formula);
@@ -444,9 +442,17 @@ public interface BootLogging extends BasicLogger {
 
 	@LogMessage(level = TRACE)
 	@Message(id = 160243, value = "Checking auto-apply AttributeConverter [%s] (domain-type=%s) for match against %s : %s.%s (type=%s)")
-	void checkingAutoApplyAttributeConverter(String converterClass, String domainTypeSignature, String siteDescriptor, String declaringType, String memberName, String memberTypeName);
+	void checkingAutoApplyAttributeConverter(String converterClass, String domainType, String siteDescriptor, String declaringType, String memberName, String memberTypeName);
 
 	@LogMessage(level = DEBUG)
 	@Message(id = 160244, value = "Skipping HBM processing of entity hierarchy [%s], as at least one entity [%s] has been processed")
 	void skippingHbmProcessingOfEntityHierarchy(String rootEntityName, String processedEntity);
+
+	@LogMessage(level = WARN)
+	@Message(id = 160245, value = "Association '%s' is 'mappedBy' another entity and should not specify a '@MapKeyColumn' (use '@MapKey' instead)")
+	void mappedByShouldNotSpecifyMapKeyColumn(String associationPath);
+
+	@LogMessage(level = WARN)
+	@Message(id = 160246, value = "Association '%s' is 'mappedBy' another entity and should not specify an '@OrderColumn' (use '@OrderBy' instead)")
+	void mappedByShouldNotSpecifyOrderColumn(String associationPath);
 }
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/MetadataSources.java b/hibernate-core/src/main/java/org/hibernate/boot/MetadataSources.java
index ef46107b6dda..bf6d02c9cd53 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/MetadataSources.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/MetadataSources.java
@@ -105,9 +105,7 @@ public MetadataSources(ServiceRegistry serviceRegistry, XmlMappingBinderAccess x
 		// service registry really should be either BootstrapServiceRegistry or StandardServiceRegistry type...
 		if ( !isExpectedServiceRegistryType( serviceRegistry ) ) {
 			if ( BOOT_LOGGER.isDebugEnabled() ) {
-				BOOT_LOGGER.unexpectedServiceRegistryType(
-						serviceRegistry.getClass().getName()
-				);
+				BOOT_LOGGER.unexpectedServiceRegistryType( serviceRegistry.getClass().getName() );
 			}
 		}
 		this.serviceRegistry = serviceRegistry;
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/ArchiveHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/ArchiveHelper.java
index 89b2b6cc0ebc..de96e3aad1ef 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/ArchiveHelper.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/ArchiveHelper.java
@@ -14,6 +14,8 @@
 
 import org.hibernate.boot.archive.spi.ArchiveException;
 
+import static org.hibernate.boot.BootLogging.BOOT_LOGGER;
+
 
 /**
  * Helper for dealing with archives
@@ -89,7 +91,7 @@ else if ( "zip".equals( protocol )
 					"Unable to determine JAR Url from " + url + ". Cause: " + e.getMessage()
 			);
 		}
-		org.hibernate.boot.BootLogging.BOOT_LOGGER.jarUrlFromUrlEntry( String.valueOf(url), String.valueOf(jarUrl) );
+		BOOT_LOGGER.jarUrlFromUrlEntry( String.valueOf(url), String.valueOf(jarUrl) );
 		return jarUrl;
 	}
 
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/BeanValidationEventListener.java b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/BeanValidationEventListener.java
index 04bf58a0e805..79b845e300a3 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/BeanValidationEventListener.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/BeanValidationEventListener.java
@@ -13,6 +13,7 @@
 import org.hibernate.boot.internal.ClassLoaderAccessImpl;
 import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
 import org.hibernate.engine.spi.SessionFactoryImplementor;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
 import org.hibernate.event.spi.PreCollectionUpdateEvent;
 import org.hibernate.event.spi.PreCollectionUpdateEventListener;
 import org.hibernate.event.spi.PreDeleteEvent;
@@ -51,6 +52,8 @@ public class BeanValidationEventListener
 	private final Validator validator;
 	private final GroupsPerOperation groupsPerOperation;
 
+	private SessionFactoryImplementor sessionFactory;
+
 	public BeanValidationEventListener(
 			ValidatorFactory factory, Map settings, ClassLoaderService classLoaderService) {
 		traversableResolver = new HibernateTraversableResolver();
@@ -58,14 +61,17 @@ public BeanValidationEventListener(
 				factory.usingContext()
 						.traversableResolver( traversableResolver )
 						.getValidator();
-		groupsPerOperation = GroupsPerOperation.from( settings, new ClassLoaderAccessImpl( classLoaderService ) );
+		groupsPerOperation =
+				GroupsPerOperation.from( settings,
+						new ClassLoaderAccessImpl( classLoaderService ) );
 	}
 
 	@Override
 	public void sessionFactoryCreated(SessionFactory factory) {
-		var implementor = factory.unwrap( SessionFactoryImplementor.class );
-		implementor.getMappingMetamodel()
-				.forEachEntityDescriptor( entityPersister -> traversableResolver.addPersister( entityPersister, implementor ) );
+		sessionFactory = factory.unwrap( SessionFactoryImplementor.class );
+		sessionFactory.getMappingMetamodel()
+				.forEachEntityDescriptor( entityPersister ->
+						traversableResolver.addPersister( entityPersister, sessionFactory ) );
 	}
 
 	public boolean onPreInsert(PreInsertEvent event) {
@@ -110,53 +116,69 @@ public void onPreUpdateCollection(PreCollectionUpdateEvent event) {
 		final Object entity = castNonNull( event.getCollection().getOwner() );
 		validate(
 				entity,
-				event.getSession().getEntityPersister( event.getAffectedOwnerEntityName(), entity ),
+				getEntityPersister( event.getSession(), event.getAffectedOwnerEntityName(), entity ),
 				GroupsPerOperation.Operation.UPDATE
 		);
 	}
 
-	private  void validate(
-			T object,
-			EntityPersister persister,
-			GroupsPerOperation.Operation operation) {
+	private EntityPersister getEntityPersister(
+			SharedSessionContractImplementor session, String entityName, Object entity) {
+		if ( session != null ) {
+			return session.getEntityPersister( entityName, entity );
+		}
+		else {
+			final var metamodel = sessionFactory.getMappingMetamodel();
+			return entityName == null
+					? metamodel.getEntityDescriptor( entity.getClass().getName() )
+					: metamodel.getEntityDescriptor( entityName )
+							.getSubclassEntityPersister( entity, sessionFactory );
+		}
+	}
+
+	private  void validate(T object, EntityPersister persister, GroupsPerOperation.Operation operation) {
 		if ( object != null
 				&& persister.getRepresentationStrategy().getMode() == RepresentationMode.POJO ) {
-			final Class[] groups = groupsPerOperation.get( operation );
+			final var groups = groupsPerOperation.get( operation );
 			if ( groups.length > 0 ) {
 				final var constraintViolations = validator.validate( object, groups );
 				if ( !constraintViolations.isEmpty() ) {
-					final Set> propagatedViolations = setOfSize( constraintViolations.size() );
+					final Set> propagatedViolations =
+							setOfSize( constraintViolations.size() );
 					final Set classNames = new HashSet<>();
 					for ( var violation : constraintViolations ) {
 						BEAN_VALIDATION_LOGGER.trace( violation );
 						propagatedViolations.add( violation );
 						classNames.add( violation.getLeafBean().getClass().getName() );
 					}
-					final var builder =
-							new StringBuilder()
-									.append( "Validation failed for classes " )
-									.append( classNames )
-									.append( " during " )
-									.append( operation.getName() )
-									.append( " time for groups " )
-									.append( toString( groups ) )
-									.append( "\nList of constraint violations:[\n" );
-					for ( var violation : constraintViolations ) {
-						builder.append( "\t" ).append( violation.toString() ).append( "\n" );
-					}
-					builder.append( "]" );
-					throw new ConstraintViolationException( builder.toString(), propagatedViolations );
+					throw new ConstraintViolationException(
+							message( operation, classNames, groups, constraintViolations ),
+							propagatedViolations );
 				}
 			}
 		}
 	}
 
-	private String toString(Class[] groups) {
-		final var string = new StringBuilder( "[" );
+	private  String message(
+			GroupsPerOperation.Operation operation,
+			Set classNames,
+			Class[] groups,
+			Set> constraintViolations) {
+		final var builder = new StringBuilder();
+		builder.append( "Validation failed for classes " )
+				.append( classNames )
+				.append( " during " )
+				.append( operation.getName() )
+				.append( " time for groups [" );
 		for ( var group : groups ) {
-			string.append( group.getName() ).append( ", " );
+			builder.append( group.getName() ).append( ", " );
+		}
+		builder.append( "]\nList of constraint violations:[\n" );
+		for ( var violation : constraintViolations ) {
+			builder.append( "\t" )
+					.append( violation.toString() )
+					.append( "\n" );
 		}
-		string.append( "]" );
-		return string.toString();
+		builder.append( "]" );
+		return builder.toString();
 	}
 }
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java
index 674181d97bee..bf42c928f7c5 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java
@@ -106,7 +106,6 @@ else if ( validationModes.contains( ValidationMode.DDL ) ) {
 					// There is a Jakarta Validation provider, but it failed to bootstrap the factory for some reason,
 					// we should fail and let the user deal with it:
 					throw exception;
-
 				}
 			}
 		}
@@ -148,10 +147,10 @@ private static void setupListener(
 			ValidatorFactory validatorFactory,
 			SessionFactoryServiceRegistry serviceRegistry,
 			SessionFactoryImplementor sessionFactory) {
-		final var classLoaderService = serviceRegistry.requireService( ClassLoaderService.class );
-		final var cfgService = serviceRegistry.requireService( ConfigurationService.class );
 		final var listener =
-				new BeanValidationEventListener( validatorFactory, cfgService.getSettings(), classLoaderService );
+				new BeanValidationEventListener( validatorFactory,
+						serviceRegistry.requireService( ConfigurationService.class ).getSettings(),
+						serviceRegistry.requireService( ClassLoaderService.class ) );
 		final var listenerRegistry = sessionFactory.getEventListenerRegistry();
 		listenerRegistry.addDuplicationStrategy( DuplicationStrategyImpl.INSTANCE );
 		listenerRegistry.appendListeners( EventType.PRE_INSERT, listener );
@@ -183,7 +182,8 @@ private static void applyRelationalConstraints(ValidatorFactory factory, Activat
 					context.getMetadata().getEntityBindings(),
 					serviceRegistry.requireService( ConfigurationService.class ).getSettings(),
 					serviceRegistry.requireService( JdbcServices.class ).getDialect(),
-					new ClassLoaderAccessImpl( null, serviceRegistry.getService( ClassLoaderService.class ) )
+					new ClassLoaderAccessImpl( null,
+							serviceRegistry.getService( ClassLoaderService.class ) )
 			);
 		}
 	}
@@ -418,17 +418,16 @@ private static boolean isNotNullDescriptor(ConstraintDescriptor descriptor) {
 
 	private static void markNotNull(Property property) {
 		// single table inheritance should not be forced to null due to shared state
-		if ( !( property.getPersistentClass() instanceof SingleTableSubclass ) ) {
-			// composite should not add not-null on all columns
-			if ( !property.isComposite() ) {
-				property.setOptional( false );
-				for ( var selectable : property.getSelectables() ) {
-					if ( selectable instanceof Column column ) {
-						column.setNullable( false );
-					}
-					else {
-						BEAN_VALIDATION_LOGGER.notNullOnFormulaPortion( property.getName() );
-					}
+		if ( !( property.getPersistentClass() instanceof SingleTableSubclass )
+				// composite should not add not-null on all columns
+				&& !property.isComposite() ) {
+			property.setOptional( false );
+			for ( var selectable : property.getSelectables() ) {
+				if ( selectable instanceof Column column ) {
+					column.setNullable( false );
+				}
+				else {
+					BEAN_VALIDATION_LOGGER.notNullOnFormulaPortion( property.getName() );
 				}
 			}
 		}
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/spi/LoadedConfig.java b/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/spi/LoadedConfig.java
index be806d5b7dbb..11b36bd57e78 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/spi/LoadedConfig.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/spi/LoadedConfig.java
@@ -100,12 +100,11 @@ public static LoadedConfig consume(JaxbCfgHibernateConfiguration jaxbCfg) {
 					final String eventTypeName = listenerGroup.getType().value();
 					final var eventType = EventType.resolveEventTypeByName( eventTypeName );
 					for ( var listener : listenerGroup.getListener() ) {
-					if ( listener.getType() != null ) {
-						BOOT_LOGGER.listenerDefinedAlsoDefinedEventType(
-								listener.getClazz()
-						);
-					}
-						cfg.addEventListener( eventType, listener.getClazz() );
+						final String listenerClassName = listener.getClazz();
+						if ( listener.getType() != null ) {
+							BOOT_LOGGER.listenerDefinedAlsoDefinedEventType( listenerClassName );
+						}
+						cfg.addEventListener( eventType, listenerClassName );
 					}
 				}
 			}
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/BootstrapContextImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/BootstrapContextImpl.java
index e51dab58de82..53987bdddc40 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/internal/BootstrapContextImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/BootstrapContextImpl.java
@@ -20,7 +20,6 @@
 import org.hibernate.boot.registry.selector.spi.StrategySelector;
 import org.hibernate.boot.spi.BootstrapContext;
 import org.hibernate.boot.spi.ClassLoaderAccess;
-import org.hibernate.boot.spi.ClassmateContext;
 import org.hibernate.boot.spi.MetadataBuildingOptions;
 import org.hibernate.cfg.PersistenceSettings;
 import org.hibernate.engine.config.spi.ConfigurationService;
@@ -67,8 +66,6 @@ public class BootstrapContextImpl implements BootstrapContext {
 
 	private boolean isJpaBootstrap;
 
-	private final ClassmateContext classmateContext;
-
 	private ScanOptions scanOptions;
 	private ScanEnvironment scanEnvironment;
 	private Object scannerSetting;
@@ -89,7 +86,6 @@ public BootstrapContextImpl(
 		this.serviceRegistry = serviceRegistry;
 		this.metadataBuildingOptions = metadataBuildingOptions;
 
-		classmateContext = new ClassmateContext();
 		classLoaderService = serviceRegistry.requireService( ClassLoaderService.class );
 		classLoaderAccess = new ClassLoaderAccessImpl( classLoaderService );
 
@@ -191,11 +187,6 @@ public ClassLoaderAccess getClassLoaderAccess() {
 		return classLoaderAccess;
 	}
 
-	@Override
-	public ClassmateContext getClassmateContext() {
-		return classmateContext;
-	}
-
 	@Override
 	public ArchiveDescriptorFactory getArchiveDescriptorFactory() {
 		return archiveDescriptorFactory;
@@ -258,7 +249,6 @@ public  BasicType resolveAdHocBasicType(String key) {
 
 	@Override
 	public void release() {
-		classmateContext.release();
 		classLoaderAccess.release();
 
 		scanOptions = null;
@@ -318,7 +308,7 @@ void injectJpaTempClassLoader(ClassLoader classLoader) {
 
 	void injectScanOptions(ScanOptions scanOptions) {
 		if ( scanOptions != this.scanOptions ) {
-			BOOT_LOGGER.injectingScanOptions(scanOptions, this.scanOptions);
+			BOOT_LOGGER.injectingScanOptions( scanOptions, this.scanOptions );
 		}
 		this.scanOptions = scanOptions;
 	}
@@ -332,7 +322,7 @@ void injectScanEnvironment(ScanEnvironment scanEnvironment) {
 
 	void injectScanner(Scanner scanner) {
 		if ( scanner != this.scannerSetting ) {
-			BOOT_LOGGER.injectingScanner( scanner, scannerSetting );
+			BOOT_LOGGER.injectingScanner( scanner, this.scannerSetting );
 		}
 		this.scannerSetting = scanner;
 	}
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java
index 186fbb1da563..b4cf4430e469 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java
@@ -55,7 +55,6 @@
 import org.hibernate.boot.query.NamedProcedureCallDefinition;
 import org.hibernate.boot.query.NamedResultSetMappingDescriptor;
 import org.hibernate.boot.spi.BootstrapContext;
-import org.hibernate.boot.spi.ClassmateContext;
 import org.hibernate.boot.spi.InFlightMetadataCollector;
 import org.hibernate.boot.spi.MetadataBuildingContext;
 import org.hibernate.boot.spi.MetadataBuildingOptions;
@@ -543,20 +542,16 @@ public AttributeConverterManager getAttributeConverterManager() {
 		return attributeConverterManager;
 	}
 
-	private ClassmateContext getClassmateContext() {
-		return getBootstrapContext().getClassmateContext();
-	}
-
 	@Override
 	public void addAttributeConverter(Class> converterClass) {
 		attributeConverterManager.addConverter(
-				ConverterDescriptors.of( converterClass, null, false, getClassmateContext() ) );
+				ConverterDescriptors.of( converterClass, null, false ) );
 	}
 
 	@Override
 	public void addOverridableConverter(Class> converterClass) {
 		attributeConverterManager.addConverter(
-				ConverterDescriptors.of( converterClass, null, true, getClassmateContext() ) );
+				ConverterDescriptors.of( converterClass, null, true ) );
 	}
 
 	@Override
@@ -566,7 +561,7 @@ public void addAttributeConverter(ConverterDescriptor descriptor) {
 
 	@Override
 	public void addRegisteredConversion(RegisteredConversion conversion) {
-		attributeConverterManager.addRegistration( conversion, bootstrapContext );
+		attributeConverterManager.addRegistration( conversion );
 	}
 
 	@Override
@@ -900,14 +895,15 @@ public Table addTable(
 			String name,
 			String subselectFragment,
 			boolean isAbstract,
-			MetadataBuildingContext buildingContext) {
+			MetadataBuildingContext buildingContext,
+			boolean isExplicit) {
 		final Namespace namespace = getDatabase().locateNamespace(
 				getDatabase().toIdentifier( catalogName ),
 				getDatabase().toIdentifier( schemaName )
 		);
 		// annotation binding depends on the "table name" for @Subselect bindings
 		// being set into the generated table (mainly to avoid later NPE), but for now we need to keep that :(
-		final Identifier logicalName = name != null ? getDatabase().toIdentifier( name ) : null;
+		final Identifier logicalName = name != null ? getDatabase().toIdentifier( name, isExplicit ) : null;
 		if ( subselectFragment != null ) {
 			return new Table( buildingContext.getCurrentContributorName(),
 					namespace, logicalName, subselectFragment, isAbstract );
@@ -1767,7 +1763,7 @@ private void processValueResolvers(MetadataBuildingContext buildingContext) {
 
 	private void processSecondPasses(ArrayList secondPasses) {
 		if ( secondPasses != null ) {
-			for ( SecondPass secondPass : secondPasses ) {
+			for ( var secondPass : secondPasses ) {
 				secondPass.doSecondPass( getEntityBindingMap() );
 			}
 			secondPasses.clear();
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuilderImpl.java
index 26f030267935..9e263e9a9d30 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuilderImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuilderImpl.java
@@ -321,7 +321,7 @@ public TypeConfiguration getTypeConfiguration() {
 	@Override
 	public void contributeAttributeConverter(Class> converterClass) {
 		bootstrapContext.addAttributeConverterDescriptor(
-				ConverterDescriptors.of( converterClass, bootstrapContext.getClassmateContext() )
+				ConverterDescriptors.of( converterClass )
 		);
 	}
 
@@ -384,7 +384,7 @@ public MetadataBuilder applyAttributeConverter(ConverterDescriptor descript
 	@Override
 	public  MetadataBuilder applyAttributeConverter(Class> attributeConverterClass) {
 		bootstrapContext.addAttributeConverterDescriptor(
-				ConverterDescriptors.of( attributeConverterClass, bootstrapContext.getClassmateContext() )
+				ConverterDescriptors.of( attributeConverterClass )
 		);
 		return this;
 	}
@@ -392,8 +392,7 @@ public  MetadataBuilder applyAttributeConverter(Class MetadataBuilder applyAttributeConverter(Class> attributeConverterClass, boolean autoApply) {
 		bootstrapContext.addAttributeConverterDescriptor(
-				ConverterDescriptors.of( attributeConverterClass, autoApply, false,
-						bootstrapContext.getClassmateContext() )
+				ConverterDescriptors.of( attributeConverterClass, autoApply, false )
 		);
 		return this;
 	}
@@ -401,7 +400,7 @@ public  MetadataBuilder applyAttributeConverter(Class MetadataBuilder applyAttributeConverter(AttributeConverter attributeConverter) {
 		bootstrapContext.addAttributeConverterDescriptor(
-				ConverterDescriptors.of( attributeConverter, bootstrapContext.getClassmateContext() )
+				ConverterDescriptors.of( attributeConverter )
 		);
 		return this;
 	}
@@ -409,7 +408,7 @@ public  MetadataBuilder applyAttributeConverter(AttributeConverter att
 	@Override
 	public MetadataBuilder applyAttributeConverter(AttributeConverter attributeConverter, boolean autoApply) {
 		bootstrapContext.addAttributeConverterDescriptor(
-				ConverterDescriptors.of( attributeConverter, autoApply, bootstrapContext.getClassmateContext() )
+				ConverterDescriptors.of( attributeConverter, autoApply )
 		);
 		return this;
 	}
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java
index 22389a3b558a..ec02695b70aa 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java
@@ -501,13 +501,13 @@ private  void appendListeners(
 			EventType eventType) {
 		final var eventListenerGroup = eventListenerRegistry.getEventListenerGroup( eventType );
 		for ( String listenerImpl : splitAtCommas( listeners ) ) {
-			@SuppressWarnings("unchecked")
-			T listener = (T) instantiate( listenerImpl, classLoaderService );
-			if ( !eventType.baseListenerInterface().isInstance( listener ) ) {
+			final var listener = instantiate( listenerImpl, classLoaderService );
+			final var baseListenerInterface = eventType.baseListenerInterface();
+			if ( !baseListenerInterface.isInstance( listener ) ) {
 				throw new HibernateException( "Event listener '" + listenerImpl
-						+ "' must implement '" + eventType.baseListenerInterface().getName() + "'");
+						+ "' must implement '" + baseListenerInterface.getName() + "'");
 			}
-			eventListenerGroup.appendListener( listener );
+			eventListenerGroup.appendListener( baseListenerInterface.cast( listener ) );
 		}
 	}
 
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedProcedureCallDefinitionImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedProcedureCallDefinitionImpl.java
index 8cc3c7e380c6..a0eb40699f28 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedProcedureCallDefinitionImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedProcedureCallDefinitionImpl.java
@@ -20,7 +20,6 @@
 import org.hibernate.internal.util.StringHelper;
 import org.hibernate.internal.util.collections.CollectionHelper;
 import org.hibernate.procedure.internal.NamedCallableQueryMementoImpl;
-import org.hibernate.procedure.internal.Util;
 import org.hibernate.procedure.spi.NamedCallableQueryMemento;
 import org.hibernate.procedure.spi.ParameterStrategy;
 import org.hibernate.query.results.ResultSetMapping;
@@ -29,6 +28,8 @@
 import jakarta.persistence.ParameterMode;
 import jakarta.persistence.StoredProcedureParameter;
 
+import static org.hibernate.procedure.internal.Util.resolveResultSetMappingClasses;
+import static org.hibernate.procedure.internal.Util.resolveResultSetMappingNames;
 import static org.hibernate.procedure.spi.NamedCallableQueryMemento.ParameterMemento;
 
 /**
@@ -96,7 +97,7 @@ public NamedCallableQueryMemento resolve(SessionFactoryImplementor sessionFactor
 		final ResultSetMapping resultSetMapping = buildResultSetMapping( registeredName, sessionFactory );
 
 		if ( specifiesResultClasses ) {
-			Util.resolveResultSetMappingClasses(
+			resolveResultSetMappingClasses(
 					resultClasses,
 					resultSetMapping,
 					collectedQuerySpaces::add,
@@ -104,7 +105,7 @@ public NamedCallableQueryMemento resolve(SessionFactoryImplementor sessionFactor
 			);
 		}
 		else if ( specifiesResultSetMappings ) {
-			Util.resolveResultSetMappingNames(
+			resolveResultSetMappingNames(
 					resultSetMappings,
 					resultSetMapping,
 					collectedQuerySpaces::add,
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java
index 7a54f65f4167..0cb24580392b 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java
@@ -79,7 +79,6 @@
 import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder;
 import org.hibernate.type.format.FormatMapper;
 import org.hibernate.type.format.FormatMapperCreationContext;
-import org.hibernate.type.format.jaxb.JaxbXmlFormatMapper;
 
 import jakarta.persistence.criteria.Nulls;
 
@@ -101,11 +100,14 @@
 import static org.hibernate.jpa.internal.util.CacheModeHelper.interpretCacheMode;
 import static org.hibernate.jpa.internal.util.ConfigurationHelper.getFlushMode;
 import static org.hibernate.stat.Statistics.DEFAULT_QUERY_STATISTICS_MAX_SIZE;
+import static org.hibernate.type.format.jackson.JacksonIntegration.getJsonJackson3FormatMapperOrNull;
 import static org.hibernate.type.format.jackson.JacksonIntegration.getJsonJacksonFormatMapperOrNull;
 import static org.hibernate.type.format.jackson.JacksonIntegration.getOsonJacksonFormatMapperOrNull;
+import static org.hibernate.type.format.jackson.JacksonIntegration.getXMLJackson3FormatMapperOrNull;
 import static org.hibernate.type.format.jackson.JacksonIntegration.getXMLJacksonFormatMapperOrNull;
-import static org.hibernate.type.format.jackson.JacksonIntegration.isJacksonOsonExtensionAvailable;
 import static org.hibernate.type.format.jakartajson.JakartaJsonIntegration.getJakartaJsonBFormatMapperOrNull;
+import static org.hibernate.type.format.jaxb.JaxbIntegration.getJaxbLegacyXmlFormatMapperOrNull;
+import static org.hibernate.type.format.jaxb.JaxbIntegration.getJaxbXmlFormatMapperOrNull;
 
 /**
  * In-flight state of {@link SessionFactoryOptions} during {@link org.hibernate.boot.SessionFactoryBuilder}
@@ -852,13 +854,24 @@ private static FormatMapper jsonFormatMapper(Object setting, boolean osonExtensi
 				selector,
 				() -> {
 					// Prefer the OSON Jackson FormatMapper by default if available
-					final FormatMapper jsonJacksonFormatMapper =
-							osonExtensionEnabled && isJacksonOsonExtensionAvailable()
-									? getOsonJacksonFormatMapperOrNull( creationContext )
-									: getJsonJacksonFormatMapperOrNull( creationContext );
-					return jsonJacksonFormatMapper != null
-							? jsonJacksonFormatMapper
-							: getJakartaJsonBFormatMapperOrNull();
+					final var jacksonOsonFormatMapper = osonExtensionEnabled
+							? getOsonJacksonFormatMapperOrNull( creationContext )
+							: null;
+					if ( jacksonOsonFormatMapper != null ) {
+						return jacksonOsonFormatMapper;
+					}
+
+					final var jacksonFormatMapper = getJsonJacksonFormatMapperOrNull( creationContext );
+					if ( jacksonFormatMapper != null ) {
+						return jacksonFormatMapper;
+					}
+
+					final var jackson3FormatMapper = getJsonJackson3FormatMapperOrNull( creationContext );
+					if ( jackson3FormatMapper != null ) {
+						return jackson3FormatMapper;
+					}
+
+					return getJakartaJsonBFormatMapperOrNull();
 				},
 				creationContext
 		);
@@ -869,10 +882,19 @@ private static FormatMapper xmlFormatMapper(Object setting, StrategySelector sel
 				setting,
 				selector,
 				() -> {
-					final FormatMapper jacksonFormatMapper = getXMLJacksonFormatMapperOrNull( creationContext );
-					return jacksonFormatMapper != null
-							? jacksonFormatMapper
-							: new JaxbXmlFormatMapper( legacyFormat );
+					final var jacksonFormatMapper = getXMLJacksonFormatMapperOrNull( creationContext );
+					if ( jacksonFormatMapper != null ) {
+						return jacksonFormatMapper;
+					}
+
+					final var jackson3FormatMapper = getXMLJackson3FormatMapperOrNull( creationContext );
+					if ( jackson3FormatMapper != null ) {
+						return jackson3FormatMapper;
+					}
+
+					return legacyFormat
+							? getJaxbLegacyXmlFormatMapperOrNull()
+							: getJaxbXmlFormatMapperOrNull();
 				},
 				creationContext
 		);
@@ -881,9 +903,8 @@ private static FormatMapper xmlFormatMapper(Object setting, StrategySelector sel
 	private static FormatMapper formatMapper(Object setting, StrategySelector selector, Callable defaultResolver, FormatMapperCreationContext creationContext) {
 		return selector.resolveStrategy( FormatMapper.class, setting, defaultResolver, strategyClass -> {
 			try {
-				final Constructor creationContextConstructor =
-						strategyClass.getDeclaredConstructor( FormatMapperCreationContext.class );
-				return creationContextConstructor.newInstance( creationContext );
+				return strategyClass.getDeclaredConstructor( FormatMapperCreationContext.class )
+						.newInstance( creationContext );
 			}
 			catch (NoSuchMethodException e) {
 				// Ignore
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/hbm/transform/HbmXmlTransformer.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/hbm/transform/HbmXmlTransformer.java
index 559b94e6a50e..94bc743ca848 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/hbm/transform/HbmXmlTransformer.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/hbm/transform/HbmXmlTransformer.java
@@ -713,7 +713,7 @@ private void transferImports() {
 		if ( !hbmImports.isEmpty() ) {
 			final var ormRoot = mappingXmlBinding.getRoot();
 			for ( var hbmImport : hbmImports ) {
-				final JaxbHqlImportImpl ormImport = new JaxbHqlImportImpl();
+				final var ormImport = new JaxbHqlImportImpl();
 				ormRoot.getHqlImports().add( ormImport );
 				ormImport.setClazz( hbmImport.getClazz() );
 				ormImport.setRename( hbmImport.getRename() );
@@ -890,7 +890,8 @@ private static JaxbNamedHqlQueryImpl transformNamedQuery(JaxbHbmNamedQueryType h
 				query.setQuery( qryString );
 			}
 			else {
-				@SuppressWarnings("unchecked") final var element = (JAXBElement) content;
+				@SuppressWarnings("unchecked")
+				final var element = (JAXBElement) content;
 				final var hbmQueryParam = element.getValue();
 				final var queryParam = new JaxbQueryParamTypeImpl();
 				query.getQueryParam().add( queryParam );
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/NamedGraphCreator.java b/hibernate-core/src/main/java/org/hibernate/boot/model/NamedGraphCreator.java
index 026af3d8f896..098fbbe844e2 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/NamedGraphCreator.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/NamedGraphCreator.java
@@ -4,17 +4,16 @@
  */
 package org.hibernate.boot.model;
 
+import org.hibernate.graph.spi.GraphParserEntityClassResolver;
+import org.hibernate.graph.spi.GraphParserEntityNameResolver;
 import org.hibernate.graph.spi.RootGraphImplementor;
-import org.hibernate.metamodel.model.domain.EntityDomainType;
-
-import java.util.function.Function;
 
 /**
  * @author Steve Ebersole
  */
 @FunctionalInterface
 public interface NamedGraphCreator {
-	 RootGraphImplementor createEntityGraph(
-			Function, EntityDomainType> entityDomainClassResolver,
-			Function> entityDomainNameResolver);
+	RootGraphImplementor createEntityGraph(
+			GraphParserEntityClassResolver entityDomainClassResolver,
+			GraphParserEntityNameResolver entityDomainNameResolver);
 }
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java
index 227c78dc08d4..00b0bc74b4d8 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java
@@ -108,7 +108,7 @@ public BasicValue.Resolution resolve(
 			MetadataBuildingContext context,
 			JdbcTypeIndicators indicators) {
 		if ( isEmpty( localConfigParameters ) ) {
-			// we can use the re-usable resolution...
+			// we can use the reusable resolution...
 			if ( reusableResolution == null ) {
 				reusableResolution = createResolution( this.name, emptyMap(), indicators, context );
 			}
@@ -144,16 +144,15 @@ private static  BasicValue.Resolution createResolution(
 			MetadataBuildingContext context) {
 		final var bootstrapContext = context.getBootstrapContext();
 		final var typeConfiguration = bootstrapContext.getTypeConfiguration();
-		final var instanceProducer = bootstrapContext.getCustomTypeProducer();
+
 		final boolean isKnownType =
 				Type.class.isAssignableFrom( typeImplementorClass )
 				|| UserType.class.isAssignableFrom( typeImplementorClass );
-
 		// support for AttributeConverter would be nice too
 		if ( isKnownType ) {
 			final T typeInstance =
 					instantiateType( bootstrapContext.getServiceRegistry(), context.getBuildingOptions(),
-							name, typeImplementorClass, instanceProducer );
+							name, typeImplementorClass, bootstrapContext.getCustomTypeProducer() );
 
 			if ( typeInstance instanceof TypeConfigurationAware configurationAware ) {
 				configurationAware.setTypeConfiguration( typeConfiguration );
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AbstractConverterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AbstractConverterDescriptor.java
index 29da7fa7bccc..fefd22d1953f 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AbstractConverterDescriptor.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AbstractConverterDescriptor.java
@@ -4,7 +4,6 @@
  */
 package org.hibernate.boot.model.convert.internal;
 
-import org.hibernate.boot.spi.ClassmateContext;
 import org.hibernate.boot.model.convert.spi.AutoApplicableConverterDescriptor;
 import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
 import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext;
@@ -12,11 +11,13 @@
 import org.hibernate.type.descriptor.converter.spi.JpaAttributeConverter;
 import org.hibernate.resource.beans.spi.ManagedBean;
 
-import com.fasterxml.classmate.ResolvedType;
 import jakarta.persistence.AttributeConverter;
 import jakarta.persistence.Converter;
 
-import static org.hibernate.boot.model.convert.internal.ConverterHelper.resolveConverterClassParamTypes;
+import java.lang.reflect.Type;
+
+import static org.hibernate.internal.util.GenericsHelper.erasedType;
+import static org.hibernate.internal.util.GenericsHelper.typeArguments;
 
 /**
  * @author Steve Ebersole
@@ -24,19 +25,18 @@
 abstract class AbstractConverterDescriptor implements ConverterDescriptor {
 	private final Class> converterClass;
 
-	private final ResolvedType domainType;
-	private final ResolvedType jdbcType;
+	private final Type domainType;
+	private final Type jdbcType;
 
 	private final AutoApplicableConverterDescriptor autoApplicableDescriptor;
 
 	AbstractConverterDescriptor(
 			Class> converterClass,
-			Boolean forceAutoApply,
-			ClassmateContext classmateContext) {
+			Boolean forceAutoApply) {
 		this.converterClass = converterClass;
-		final var converterParamTypes = resolveConverterClassParamTypes( converterClass, classmateContext );
-		domainType = converterParamTypes.get( 0 );
-		jdbcType = converterParamTypes.get( 1 );
+		final var converterParamTypes = typeArguments( AttributeConverter.class, converterClass );
+		domainType = converterParamTypes[0];
+		jdbcType = converterParamTypes[1];
 		autoApplicableDescriptor = resolveAutoApplicableDescriptor( converterClass, forceAutoApply );
 	}
 
@@ -55,7 +55,7 @@ private static boolean isAutoApply(Class> con
 		}
 		else {
 			// otherwise, look at the converter's @Converter annotation
-			final Converter annotation = converterClass.getAnnotation( Converter.class );
+			final var annotation = converterClass.getAnnotation( Converter.class );
 			return annotation != null && annotation.autoApply();
 		}
 	}
@@ -66,12 +66,12 @@ public Class> getAttributeConverterClass() {
 	}
 
 	@Override
-	public ResolvedType getDomainValueResolvedType() {
+	public Type getDomainValueResolvedType() {
 		return domainType;
 	}
 
 	@Override
-	public ResolvedType getRelationalValueResolvedType() {
+	public Type getRelationalValueResolvedType() {
 		return jdbcType;
 	}
 
@@ -93,12 +93,12 @@ public JpaAttributeConverter createJpaAttributeConverter(JpaAttributeConver
 
 	@SuppressWarnings("unchecked")
 	private Class getRelationalClass() {
-		return (Class) getRelationalValueResolvedType().getErasedType();
+		return (Class) erasedType( getRelationalValueResolvedType() );
 	}
 
 	@SuppressWarnings("unchecked")
 	private Class getDomainClass() {
-		return (Class) getDomainValueResolvedType().getErasedType();
+		return (Class) erasedType( getDomainValueResolvedType() );
 	}
 
 	protected abstract ManagedBean>
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AttributeConverterManager.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AttributeConverterManager.java
index 7b83063a49b2..9764e36bfcc8 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AttributeConverterManager.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AttributeConverterManager.java
@@ -4,6 +4,7 @@
  */
 package org.hibernate.boot.model.convert.internal;
 
+import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -12,13 +13,13 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 
+import jakarta.persistence.AttributeConverter;
 import org.hibernate.AnnotationException;
 import org.hibernate.HibernateException;
 import org.hibernate.boot.model.convert.spi.AutoApplicableConverterDescriptor;
 import org.hibernate.boot.model.convert.spi.ConverterAutoApplyHandler;
 import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
 import org.hibernate.boot.model.convert.spi.RegisteredConversion;
-import org.hibernate.boot.spi.BootstrapContext;
 import org.hibernate.boot.spi.MetadataBuildingContext;
 import org.hibernate.models.spi.MemberDetails;
 
@@ -26,8 +27,8 @@
 
 import static java.util.Collections.emptyList;
 import static org.hibernate.boot.BootLogging.BOOT_LOGGER;
-import static org.hibernate.boot.model.convert.internal.ConverterHelper.resolveAttributeType;
-import static org.hibernate.boot.model.convert.internal.ConverterHelper.resolveConverterClassParamTypes;
+import static org.hibernate.internal.util.GenericsHelper.typeArguments;
+import static org.hibernate.internal.util.GenericsHelper.actualMemberType;
 import static org.hibernate.internal.util.StringHelper.join;
 
 /**
@@ -39,7 +40,7 @@
 public class AttributeConverterManager implements ConverterAutoApplyHandler {
 
 	private Map, ConverterDescriptor> attributeConverterDescriptorsByClass;
-	private Map, RegisteredConversion> registeredConversionsByDomainType;
+	private Map registeredConversionsByDomainType;
 
 	public RegisteredConversion findRegisteredConversion(Class domainType) {
 		return registeredConversionsByDomainType == null ? null : registeredConversionsByDomainType.get( domainType );
@@ -52,7 +53,7 @@ public void addConverter(ConverterDescriptor descriptor) {
 		}
 
 		if ( registeredConversionsByDomainType != null ) {
-			final Class domainType = descriptor.getDomainValueResolvedType().getErasedType();
+			final var domainType = descriptor.getDomainValueResolvedType();
 			final var registeredConversion = registeredConversionsByDomainType.get( domainType );
 			if ( registeredConversion != null ) {
 				// we can skip registering the converter, the RegisteredConversion will always take precedence
@@ -79,12 +80,12 @@ public void addConverter(ConverterDescriptor descriptor) {
 		}
 	}
 
-	public void addRegistration(RegisteredConversion conversion, BootstrapContext context) {
+	public void addRegistration(RegisteredConversion conversion) {
 		if ( registeredConversionsByDomainType == null ) {
 			registeredConversionsByDomainType = new ConcurrentHashMap<>();
 		}
 
-		final Class domainType = getDomainType( conversion, context );
+		final var domainType = getDomainType( conversion );
 		checkNotOverriding( conversion, domainType );
 		// See if we have a matching entry in attributeConverterDescriptorsByClass.
 		// If so, remove it. The conversion being registered will always take precedence.
@@ -98,7 +99,7 @@ public void addRegistration(RegisteredConversion conversion, BootstrapContext co
 		registeredConversionsByDomainType.put( domainType, conversion );
 	}
 
-	private void checkNotOverriding(RegisteredConversion conversion, Class domainType) {
+	private void checkNotOverriding(RegisteredConversion conversion, Type domainType) {
 		// make sure we are not overriding a previous conversion registration
 		final var existingRegistration = registeredConversionsByDomainType.get( domainType );
 		if ( existingRegistration != null ) {
@@ -113,11 +114,10 @@ private void checkNotOverriding(RegisteredConversion conversion, Class domain
 		}
 	}
 
-	private static Class getDomainType(RegisteredConversion conversion, BootstrapContext context) {
+	private static Type getDomainType(RegisteredConversion conversion) {
 		// the registration did not define an explicit domain-type, so inspect the converter
 		return conversion.getExplicitDomainType().equals( void.class )
-				? resolveConverterClassParamTypes( conversion.getConverterType(), context.getClassmateContext() )
-						.get( 0 ).getErasedType()
+				? typeArguments( AttributeConverter.class, conversion.getConverterType() )[0]
 				: conversion.getExplicitDomainType();
 	}
 
@@ -149,21 +149,19 @@ public ConverterDescriptor findAutoApplyConverterForAttribute(
 				attributeMember,
 				ConversionSite.ATTRIBUTE,
 				(autoApplyDescriptor) ->
-						autoApplyDescriptor.getAutoAppliedConverterDescriptorForAttribute( attributeMember, context ),
-				context
+						autoApplyDescriptor.getAutoAppliedConverterDescriptorForAttribute( attributeMember, context )
 		);
 	}
 
 	private ConverterDescriptor locateMatchingConverter(
 			MemberDetails memberDetails,
 			ConversionSite conversionSite,
-			Function> matcher,
-			MetadataBuildingContext context) {
+			Function> matcher) {
 		if ( registeredConversionsByDomainType != null ) {
 			// we had registered conversions - see if any of them match and, if so, use that conversion
-			final var resolveAttributeType = resolveAttributeType( memberDetails, context );
+			final var resolveAttributeType = actualMemberType( memberDetails );
 			final var registrationForDomainType =
-					registeredConversionsByDomainType.get( resolveAttributeType.getErasedType() );
+					registeredConversionsByDomainType.get( resolveAttributeType );
 			if ( registrationForDomainType != null ) {
 				return registrationForDomainType.isAutoApply()
 						? registrationForDomainType.getConverterDescriptor()
@@ -217,7 +215,7 @@ private List> getMatches(
 			if ( BOOT_LOGGER.isTraceEnabled() ) {
 				BOOT_LOGGER.checkingAutoApplyAttributeConverter(
 						descriptor.getAttributeConverterClass().getName(),
-						descriptor.getDomainValueResolvedType().getSignature(),
+						descriptor.getDomainValueResolvedType().getTypeName(),
 						conversionSite.getSiteDescriptor(),
 						memberDetails.getDeclaringType().getName(),
 						memberDetails.getName(),
@@ -240,8 +238,7 @@ public ConverterDescriptor findAutoApplyConverterForCollectionElement(
 				attributeMember,
 				ConversionSite.COLLECTION_ELEMENT,
 				(autoApplyDescriptor) ->
-						autoApplyDescriptor.getAutoAppliedConverterDescriptorForCollectionElement( attributeMember, context ),
-				context
+						autoApplyDescriptor.getAutoAppliedConverterDescriptorForCollectionElement( attributeMember, context )
 		);
 	}
 
@@ -253,8 +250,7 @@ public ConverterDescriptor findAutoApplyConverterForMapKey(
 				attributeMember,
 				ConversionSite.MAP_KEY,
 				(autoApplyDescriptor) ->
-						autoApplyDescriptor.getAutoAppliedConverterDescriptorForMapKey( attributeMember, context ),
-				context
+						autoApplyDescriptor.getAutoAppliedConverterDescriptorForMapKey( attributeMember, context )
 		);
 	}
 
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorStandardImpl.java
index eecde3d91663..eea404d5fa1d 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorStandardImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorStandardImpl.java
@@ -4,8 +4,8 @@
  */
 package org.hibernate.boot.model.convert.internal;
 
+import java.lang.reflect.Type;
 import java.util.Collection;
-import java.util.List;
 import java.util.Map;
 
 import org.hibernate.HibernateException;
@@ -14,12 +14,11 @@
 import org.hibernate.boot.spi.MetadataBuildingContext;
 import org.hibernate.models.spi.MemberDetails;
 
-import com.fasterxml.classmate.ResolvedType;
-import com.fasterxml.classmate.members.ResolvedMember;
-
-import static org.hibernate.boot.model.convert.internal.ConverterHelper.resolveAttributeType;
-import static org.hibernate.boot.model.convert.internal.ConverterHelper.resolveMember;
-import static org.hibernate.boot.model.convert.internal.ConverterHelper.typesMatch;
+import static org.hibernate.internal.util.GenericsHelper.erasedType;
+import static org.hibernate.internal.util.GenericsHelper.typeArguments;
+import static org.hibernate.internal.util.GenericsHelper.actualMemberType;
+import static org.hibernate.internal.util.GenericAssignability.isAssignableFrom;
+import static org.hibernate.internal.util.type.PrimitiveWrappers.canonicalize;
 
 /**
  * Standard implementation of AutoApplicableConverterDescriptor
@@ -42,44 +41,52 @@ public boolean isAutoApplicable() {
 	public ConverterDescriptor getAutoAppliedConverterDescriptorForAttribute(
 			MemberDetails memberDetails,
 			MetadataBuildingContext context) {
-		final ResolvedType attributeType = resolveAttributeType( memberDetails, context );
-
-		return typesMatch( linkedConverterDescriptor.getDomainValueResolvedType(), attributeType )
+		// TODO: arrays, etc
+		final var attributeType = actualMemberType( memberDetails );
+		return isAssignableFrom( linkedConverterDescriptor.getDomainValueResolvedType(),
+						canonicalizePrimitive( attributeType ) )
 				? linkedConverterDescriptor
 				: null;
 	}
 
+	private static Type canonicalizePrimitive(Type attributeType) {
+		return attributeType instanceof Class cl
+				? canonicalize( cl )
+				: attributeType;
+	}
+
 	@Override
 	public ConverterDescriptor getAutoAppliedConverterDescriptorForCollectionElement(
 			MemberDetails memberDetails,
 			MetadataBuildingContext context) {
-		final ResolvedMember collectionMember = resolveMember( memberDetails, context );
-
-		final ResolvedType elementType;
-		final ResolvedType type = collectionMember.getType();
-		final Class erasedType = type.getErasedType();
+		final var collectionMemberType = actualMemberType( memberDetails );
+		final var erasedType = erasedType( collectionMemberType );
+		final Type elementType;
 		if ( Map.class.isAssignableFrom( erasedType ) ) {
-			final List typeArguments = type.typeParametersFor(Map.class);
-			if ( typeArguments.size() < 2 ) {
+			final var typeArguments =
+					typeArguments( Map.class, collectionMemberType );
+			if ( typeArguments.length < 2 ) {
 				return null;
 			}
-			elementType = typeArguments.get( 1 );
+			elementType = typeArguments[1];
 		}
 		else if ( Collection.class.isAssignableFrom( erasedType ) ) {
-			final List typeArguments = type.typeParametersFor(Collection.class);
-			if ( typeArguments.isEmpty() ) {
+			final var typeArguments =
+					typeArguments( Collection.class, collectionMemberType );
+			if ( typeArguments.length == 0 ) {
 				return null;
 			}
-			elementType = typeArguments.get( 0 );
+			elementType = typeArguments[0];
 		}
 		else if ( erasedType.isArray() ) {
-			elementType = type.getArrayElementType();
+			elementType = erasedType.componentType();
 		}
 		else {
 			throw new HibernateException( "Attribute was neither a Collection nor a Map : " + erasedType);
 		}
 
-		return typesMatch( linkedConverterDescriptor.getDomainValueResolvedType(), elementType )
+		return isAssignableFrom( linkedConverterDescriptor.getDomainValueResolvedType(),
+						canonicalizePrimitive( elementType ) )
 				? linkedConverterDescriptor
 				: null;
 	}
@@ -89,21 +96,22 @@ public ConverterDescriptor getAutoAppliedConverterDescriptorForMapKey(
 			MemberDetails memberDetails,
 			MetadataBuildingContext context) {
 
-		final ResolvedMember collectionMember = resolveMember( memberDetails, context );
-		final ResolvedType keyType;
-		final ResolvedType type = collectionMember.getType();
-		if ( Map.class.isAssignableFrom( type.getErasedType() ) ) {
-			final List typeArguments = type.typeParametersFor(Map.class);
-			if ( typeArguments.isEmpty() ) {
+		final var collectionMemberType = actualMemberType( memberDetails );
+		final Type keyType;
+		if ( Map.class.isAssignableFrom( erasedType( collectionMemberType ) ) ) {
+			final var typeArguments =
+					typeArguments( Map.class, collectionMemberType );
+			if ( typeArguments.length == 0 ) {
 				return null;
 			}
-			keyType = typeArguments.get(0);
+			keyType = typeArguments[0];
 		}
 		else {
-			throw new HibernateException( "Attribute was not a Map : " + type.getErasedType() );
+			throw new HibernateException( "Attribute was not a Map : " + collectionMemberType );
 		}
 
-		return typesMatch( linkedConverterDescriptor.getDomainValueResolvedType(), keyType )
+		return isAssignableFrom( linkedConverterDescriptor.getDomainValueResolvedType(),
+						canonicalizePrimitive( keyType ) )
 				? linkedConverterDescriptor
 				: null;
 	}
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ClassBasedConverterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ClassBasedConverterDescriptor.java
index 2e9a633b9494..0cc304a9495a 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ClassBasedConverterDescriptor.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ClassBasedConverterDescriptor.java
@@ -4,7 +4,6 @@
  */
 package org.hibernate.boot.model.convert.internal;
 
-import org.hibernate.boot.spi.ClassmateContext;
 import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext;
 import org.hibernate.resource.beans.spi.ManagedBean;
 
@@ -23,9 +22,8 @@ class ClassBasedConverterDescriptor extends AbstractConverterDescriptor> converterClass,
 			Boolean forceAutoApply,
-			ClassmateContext classmateContext,
 			boolean overrideable) {
-		super( converterClass, forceAutoApply, classmateContext );
+		super( converterClass, forceAutoApply );
 		this.overrideable = overrideable;
 	}
 
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ConverterDescriptorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ConverterDescriptorImpl.java
index 3bbfc8d3c8cd..8d221d3d3dcd 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ConverterDescriptorImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ConverterDescriptorImpl.java
@@ -4,7 +4,6 @@
  */
 package org.hibernate.boot.model.convert.internal;
 
-import com.fasterxml.classmate.ResolvedType;
 import jakarta.persistence.AttributeConverter;
 import org.hibernate.boot.model.convert.spi.AutoApplicableConverterDescriptor;
 import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
@@ -12,17 +11,19 @@
 import org.hibernate.type.descriptor.converter.internal.AttributeConverterBean;
 import org.hibernate.type.descriptor.converter.spi.JpaAttributeConverter;
 
+import java.lang.reflect.Type;
+
 
 class ConverterDescriptorImpl implements ConverterDescriptor {
 	private final Class> converterType;
-	private final ResolvedType domainTypeToMatch;
-	private final ResolvedType relationalType;
+	private final Type domainTypeToMatch;
+	private final Type relationalType;
 	private final AutoApplicableConverterDescriptor autoApplyDescriptor;
 
 	ConverterDescriptorImpl(
 			Class> converterType,
-			ResolvedType domainTypeToMatch,
-			ResolvedType relationalType,
+			Type domainTypeToMatch,
+			Type relationalType,
 			boolean autoApply) {
 		this.converterType = converterType;
 		this.domainTypeToMatch = domainTypeToMatch;
@@ -38,12 +39,12 @@ public Class> getAttributeConverterClass() {
 	}
 
 	@Override
-	public ResolvedType getDomainValueResolvedType() {
+	public Type getDomainValueResolvedType() {
 		return domainTypeToMatch;
 	}
 
 	@Override
-	public ResolvedType getRelationalValueResolvedType() {
+	public Type getRelationalValueResolvedType() {
 		return relationalType;
 	}
 
@@ -59,8 +60,8 @@ public JpaAttributeConverter createJpaAttributeConverter(JpaAttributeConve
 		return new AttributeConverterBean<>(
 				converterBean,
 				javaTypeRegistry.resolveDescriptor( converterBean.getBeanClass() ),
-				javaTypeRegistry.getDescriptor( domainTypeToMatch.getErasedType() ),
-				javaTypeRegistry.getDescriptor( relationalType.getErasedType() )
+				javaTypeRegistry.getDescriptor( domainTypeToMatch ),
+				javaTypeRegistry.getDescriptor( relationalType)
 		);
 	}
 }
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ConverterDescriptors.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ConverterDescriptors.java
index 047635bd09d5..c6172194d18a 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ConverterDescriptors.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ConverterDescriptors.java
@@ -4,10 +4,10 @@
  */
 package org.hibernate.boot.model.convert.internal;
 
-import com.fasterxml.classmate.ResolvedType;
 import jakarta.persistence.AttributeConverter;
 import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
-import org.hibernate.boot.spi.ClassmateContext;
+
+import java.lang.reflect.Type;
 
 /**
  * Factory for {@link org.hibernate.boot.model.convert.spi.ConverterDescriptor}.
@@ -17,39 +17,35 @@
 public class ConverterDescriptors {
 
 	public static  ConverterDescriptor of(
-			AttributeConverter converterInstance, ClassmateContext classmateContext) {
-		return new InstanceBasedConverterDescriptor<>( converterInstance, null, classmateContext );
+			AttributeConverter converterInstance) {
+		return new InstanceBasedConverterDescriptor<>( converterInstance, null );
 	}
 
 	public static  ConverterDescriptor of(
-			AttributeConverter converterInstance, boolean autoApply, ClassmateContext classmateContext) {
-		return new InstanceBasedConverterDescriptor<>( converterInstance, autoApply, classmateContext );
+			AttributeConverter converterInstance, boolean autoApply) {
+		return new InstanceBasedConverterDescriptor<>( converterInstance, autoApply );
 	}
 
 	public static  ConverterDescriptor of(
 			Class> converterClass,
-			Boolean autoApply, boolean overrideable, ClassmateContext classmateContext) {
+			Boolean autoApply, boolean overrideable) {
 		@SuppressWarnings("unchecked") // work around weird fussiness in wildcard capture
-		final Class> converterType =
-				(Class>) converterClass;
-		return new ClassBasedConverterDescriptor<>( converterType, autoApply, classmateContext, overrideable );
+		final var converterType = (Class>) converterClass;
+		return new ClassBasedConverterDescriptor<>( converterType, autoApply, overrideable );
 	}
 
 	public static  ConverterDescriptor of(
-			Class> converterClass,
-			ClassmateContext classmateContext) {
+			Class> converterClass) {
 		@SuppressWarnings("unchecked") // work around weird fussiness in wildcard capture
-		final Class> converterType =
-				(Class>) converterClass;
-		return new ClassBasedConverterDescriptor<>( converterType, null, classmateContext, false );
+		final var converterType = (Class>) converterClass;
+		return new ClassBasedConverterDescriptor<>( converterType, null, false );
 	}
 
 	public static  ConverterDescriptor of(
 			Class> converterType,
-			ResolvedType domainTypeToMatch, ResolvedType relationalType, boolean autoApply) {
+			Type domainTypeToMatch, Type relationalType, boolean autoApply) {
 		@SuppressWarnings("unchecked") // work around weird fussiness in wildcard capture
-		final Class> converterClass =
-				(Class>) converterType;
+		final var converterClass = (Class>) converterType;
 		return new ConverterDescriptorImpl<>( converterClass, domainTypeToMatch, relationalType, autoApply );
 	}
 }
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ConverterHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ConverterHelper.java
deleted file mode 100644
index d56ce6bfca52..000000000000
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ConverterHelper.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * SPDX-License-Identifier: Apache-2.0
- * Copyright Red Hat Inc. and Hibernate Authors
- */
-package org.hibernate.boot.model.convert.internal;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Member;
-import java.lang.reflect.Method;
-import java.lang.reflect.ParameterizedType;
-import java.util.List;
-
-import org.hibernate.AnnotationException;
-import org.hibernate.HibernateException;
-import org.hibernate.boot.spi.ClassmateContext;
-import org.hibernate.boot.spi.MetadataBuildingContext;
-import org.hibernate.internal.util.GenericsHelper;
-import org.hibernate.internal.util.type.PrimitiveWrapperHelper;
-import org.hibernate.models.spi.MemberDetails;
-
-import com.fasterxml.classmate.ResolvedType;
-import com.fasterxml.classmate.members.ResolvedField;
-import com.fasterxml.classmate.members.ResolvedMember;
-import com.fasterxml.classmate.members.ResolvedMethod;
-import jakarta.persistence.AttributeConverter;
-
-/**
- * Helpers related to handling converters
- */
-public class ConverterHelper {
-	public static ParameterizedType extractAttributeConverterParameterizedType(Class> base) {
-		return GenericsHelper.extractParameterizedType( base, AttributeConverter.class );
-	}
-
-	public static ResolvedType resolveAttributeType(MemberDetails memberDetails, MetadataBuildingContext context) {
-		return resolveMember( memberDetails, context ).getType();
-	}
-
-	public static ResolvedMember resolveMember(MemberDetails memberDetails, MetadataBuildingContext buildingContext) {
-		final var classmateContext = buildingContext.getBootstrapContext().getClassmateContext();
-		final var declaringClassType =
-				classmateContext.getTypeResolver()
-						.resolve( memberDetails.getDeclaringType().toJavaClass() );
-		final var declaringClassWithMembers =
-				classmateContext.getMemberResolver()
-						.resolve( declaringClassType, null, null );
-
-		final var member = memberDetails.toJavaMember();
-		if ( member instanceof Method ) {
-			for ( ResolvedMethod resolvedMember : declaringClassWithMembers.getMemberMethods() ) {
-				if ( resolvedMember.getName().equals( member.getName() ) ) {
-					return resolvedMember;
-				}
-			}
-		}
-		else if ( member instanceof Field ) {
-			for ( ResolvedField resolvedMember : declaringClassWithMembers.getMemberFields() ) {
-				if ( resolvedMember.getName().equals( member.getName() ) ) {
-					return resolvedMember;
-				}
-			}
-		}
-		else {
-			throw new HibernateException( "Unexpected java.lang.reflect.Member type from org.hibernate.models.spi.MemberDetails : " + member );
-		}
-
-		throw new HibernateException(
-				"Could not locate resolved type information for attribute [" + member.getName() + "] from Classmate"
-		);
-	}
-
-	public static List resolveConverterClassParamTypes(
-			Class> converterClass,
-			ClassmateContext context) {
-		final var converterType = context.getTypeResolver().resolve( converterClass );
-		final var converterParamTypes = converterType.typeParametersFor( AttributeConverter.class );
-		if ( converterParamTypes == null ) {
-			throw new AnnotationException(
-					"Could not extract type argument from attribute converter class '"
-							+ converterClass.getName() + "'"
-			);
-		}
-		else if ( converterParamTypes.size() != 2 ) {
-			throw new AnnotationException(
-					"Unexpected type argument for attribute converter class '"
-							+ converterClass.getName()
-							+ "' (expected 2 type arguments, but found " + converterParamTypes.size() + ")"
-			);
-		}
-		return converterParamTypes;
-	}
-
-	/**
-	 * Determine whether 2 types match.  Intended for determining whether to auto applying a converter
-	 *
-	 * @param converterDefinedType The type defined via the converter's parameterized type signature.
-	 * 		E.g. {@code O} in {@code implements AttributeConverter}
-	 * @param checkType The type from the domain model (basic attribute type, Map key type, Collection element type)
-	 *
-	 * @return {@code true} if they match, otherwise {@code false}.
-	 */
-	public static boolean typesMatch(ResolvedType converterDefinedType, ResolvedType checkType) {
-		Class erasedCheckType = checkType.getErasedType();
-		if ( erasedCheckType.isPrimitive() ) {
-			erasedCheckType = PrimitiveWrapperHelper.getDescriptorByPrimitiveType( erasedCheckType ).getWrapperClass();
-		}
-		else if ( erasedCheckType.isArray() ) {
-			// converterDefinedType have type parameters if it extends super generic class
-			// but checkType doesn't have any type parameters
-			// comparing erased type is enough
-			// see https://hibernate.atlassian.net/browse/HHH-18012
-			return converterDefinedType.getErasedType() == erasedCheckType;
-		}
-
-		return converterDefinedType.getErasedType().isAssignableFrom( erasedCheckType )
-			&& checkTypeParametersMatch( converterDefinedType, checkType );
-	}
-
-	private static boolean checkTypeParametersMatch(ResolvedType converterDefinedType, ResolvedType checkType) {
-		final var converterTypeParameters = converterDefinedType.getTypeParameters();
-		// if the converter did not define any nested type parameters,
-		// then the checks already done above are enough for a match
-		if ( converterTypeParameters.isEmpty() ) {
-			return true;
-		}
-		else {
-			// However, here the converter *did* define nested type parameters,
-			// so we'd have a converter defined using something like, for example,
-			// List for its domain type, and so we need to check those
-			// nested types as well
-			final var checkTypeParameters = checkType.getTypeParameters();
-			if ( checkTypeParameters.isEmpty() ) {
-				// the domain type did not define nested type params.  a List would not auto-match a List()
-				return false;
-			}
-			else if ( converterTypeParameters.size() != checkTypeParameters.size() ) {
-				// they had different number of type params somehow.
-				return false;
-			}
-			else {
-				for ( int i = 0; i < converterTypeParameters.size(); i++ ) {
-					if ( !typesMatch( converterTypeParameters.get( i ), checkTypeParameters.get( i ) ) ) {
-						return false;
-					}
-				}
-				return true;
-			}
-		}
-	}
-}
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/InstanceBasedConverterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/InstanceBasedConverterDescriptor.java
index 2849e697c2aa..d5f8ad831b48 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/InstanceBasedConverterDescriptor.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/InstanceBasedConverterDescriptor.java
@@ -4,7 +4,6 @@
  */
 package org.hibernate.boot.model.convert.internal;
 
-import org.hibernate.boot.spi.ClassmateContext;
 import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext;
 import org.hibernate.internal.util.ReflectHelper;
 import org.hibernate.resource.beans.spi.ManagedBean;
@@ -23,9 +22,8 @@ class InstanceBasedConverterDescriptor extends AbstractConverterDescriptor<
 
 	InstanceBasedConverterDescriptor(
 			AttributeConverter converterInstance,
-			Boolean forceAutoApply,
-			ClassmateContext classmateContext) {
-		super( ReflectHelper.getClass( converterInstance ), forceAutoApply, classmateContext );
+			Boolean forceAutoApply) {
+		super( ReflectHelper.getClass( converterInstance ), forceAutoApply );
 		this.converterInstance = converterInstance;
 	}
 
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/ConverterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/ConverterDescriptor.java
index bd9f806a7508..2c24fd2ecc8a 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/ConverterDescriptor.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/ConverterDescriptor.java
@@ -6,9 +6,10 @@
 
 import org.hibernate.type.descriptor.converter.spi.JpaAttributeConverter;
 
-import com.fasterxml.classmate.ResolvedType;
 import jakarta.persistence.AttributeConverter;
 
+import java.lang.reflect.Type;
+
 /**
  * Boot-time descriptor of a JPA {@linkplain AttributeConverter converter}.
  *
@@ -31,12 +32,12 @@ public interface ConverterDescriptor {
 	/**
 	 * The resolved Classmate type descriptor for the conversion's domain type
 	 */
-	ResolvedType getDomainValueResolvedType();
+	Type getDomainValueResolvedType();
 
 	/**
 	 * The resolved Classmate type descriptor for the conversion's relational type
 	 */
-	ResolvedType getRelationalValueResolvedType();
+	Type getRelationalValueResolvedType();
 
 	/**
 	 * Get the auto-apply checker for this converter.
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/RegisteredConversion.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/RegisteredConversion.java
index e2a0ef353f78..31c05d60a46e 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/RegisteredConversion.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/RegisteredConversion.java
@@ -7,13 +7,10 @@
 import java.util.Objects;
 
 import org.hibernate.boot.model.convert.internal.ConverterDescriptors;
-import org.hibernate.boot.spi.ClassmateContext;
-import org.hibernate.boot.spi.MetadataBuildingContext;
 
-import com.fasterxml.classmate.ResolvedType;
 import jakarta.persistence.AttributeConverter;
 
-import static org.hibernate.boot.model.convert.internal.ConverterHelper.resolveConverterClassParamTypes;
+import static org.hibernate.internal.util.GenericsHelper.typeArguments;
 
 /**
  * A registered conversion.
@@ -35,10 +32,9 @@ public record RegisteredConversion(
 	public RegisteredConversion(
 			Class explicitDomainType,
 			Class> converterType,
-			boolean autoApply,
-			MetadataBuildingContext context) {
+			boolean autoApply) {
 		this( explicitDomainType, converterType, autoApply,
-				determineConverterDescriptor( explicitDomainType, converterType, autoApply, context ) );
+				determineConverterDescriptor( explicitDomainType, converterType, autoApply ) );
 	}
 
 	@Override
@@ -62,15 +58,14 @@ public int hashCode() {
 	private static ConverterDescriptor determineConverterDescriptor(
 			Class explicitDomainType,
 			Class> converterType,
-			boolean autoApply,
-			MetadataBuildingContext context) {
-		final ClassmateContext classmateContext = context.getBootstrapContext().getClassmateContext();
-		final var resolvedParamTypes = resolveConverterClassParamTypes( converterType, classmateContext );
-		final ResolvedType relationalType = resolvedParamTypes.get( 1 );
-		final ResolvedType domainTypeToMatch =
+			boolean autoApply) {
+		final var resolvedParamTypes =
+				typeArguments( AttributeConverter.class, converterType );
+		final var relationalType = resolvedParamTypes[1];
+		final var domainTypeToMatch =
 				void.class.equals( explicitDomainType )
-						? resolvedParamTypes.get( 0 )
-						: classmateContext.getTypeResolver().resolve( explicitDomainType );
+						? resolvedParamTypes[0]
+						: explicitDomainType;
 		return ConverterDescriptors.of( converterType, domainTypeToMatch, relationalType, autoApply );
 	}
 
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AbstractPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AbstractPropertyHolder.java
index 473e2775e364..9b51796dae69 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AbstractPropertyHolder.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AbstractPropertyHolder.java
@@ -9,6 +9,7 @@
 import java.util.List;
 import java.util.Map;
 
+import jakarta.persistence.AttributeConverter;
 import org.hibernate.AnnotationException;
 import org.hibernate.AssertionFailure;
 import org.hibernate.annotations.ColumnTransformer;
@@ -111,11 +112,11 @@ public ConverterDescriptor resolveAttributeConverterDescriptor(MemberDetail
 	}
 
 	protected IllegalStateException buildExceptionFromInstantiationError(AttributeConversionInfo info, Exception e) {
-		if ( void.class.equals( info.getConverterClass() ) ) {
+		if ( AttributeConverter.class.equals( info.getConverterClass() ) ) {
 			// the user forgot to set @Convert.converter
 			// we already know it's not a @Convert.disableConversion
 			return new IllegalStateException(
-					"Unable to instantiate AttributeConverter: you left @Convert.converter to its default value void.",
+					"Unable to instantiate AttributeConverter because the 'converter' member of '@Convert' was not specified",
 					e
 			);
 
@@ -133,9 +134,7 @@ protected IllegalStateException buildExceptionFromInstantiationError(AttributeCo
 
 	protected ConverterDescriptor makeAttributeConverterDescriptor(AttributeConversionInfo conversion) {
 		try {
-			return ConverterDescriptors.of( conversion.getConverterClass(),
-					null, false,
-					context.getBootstrapContext().getClassmateContext() );
+			return ConverterDescriptors.of( conversion.getConverterClass(), null, false );
 		}
 		catch (Exception e) {
 			throw new AnnotationException( "Unable to create AttributeConverter instance", e );
@@ -283,7 +282,7 @@ private Column[] getExactOverriddenColumn(String propertyName) {
 	 */
 	@Override
 	public JoinColumn[] getOverriddenJoinColumn(String propertyName) {
-		final JoinColumn[] result = getExactOverriddenJoinColumn( propertyName );
+		final var result = getExactOverriddenJoinColumn( propertyName );
 		if ( result == null && propertyName.contains( ".{element}." ) ) {
 			//support for non map collections where no prefix is needed
 			//TODO cache the underlying regexp
@@ -316,13 +315,15 @@ private JoinColumn[] getExactOverriddenJoinColumn(String propertyName) {
 
 	@Override
 	public ForeignKey getOverriddenForeignKey(String propertyName) {
-		final ForeignKey result = getExactOverriddenForeignKey( propertyName );
+		final var result = getExactOverriddenForeignKey( propertyName );
 		if ( result == null && propertyName.contains( ".{element}." ) ) {
 			//support for non map collections where no prefix is needed
 			//TODO cache the underlying regexp
 			return getExactOverriddenForeignKey( propertyName.replace( ".{element}.", "." ) );
 		}
-		return result;
+		else {
+			return result;
+		}
 	}
 
 	private ForeignKey getExactOverriddenForeignKey(String propertyName) {
@@ -585,7 +586,7 @@ private static Map buildColumnTransformerOverride(Ann
 	private static Map buildJoinColumnOverride(AnnotationTarget element, String path, MetadataBuildingContext context) {
 		final Map columnOverride = new HashMap<>();
 		if ( element != null ) {
-			for ( var override : buildAssociationOverrides( element, path, context ) ) {
+			for ( var override : buildAssociationOverrides( element, context ) ) {
 				columnOverride.put( qualify( path, override.name() ), override.joinColumns() );
 			}
 		}
@@ -595,21 +596,21 @@ private static Map buildJoinColumnOverride(AnnotationTarge
 	private static Map buildForeignKeyOverride(AnnotationTarget element, String path, MetadataBuildingContext context) {
 		final Map foreignKeyOverride = new HashMap<>();
 		if ( element != null ) {
-			for ( var override : buildAssociationOverrides( element, path, context ) ) {
+			for ( var override : buildAssociationOverrides( element, context ) ) {
 				foreignKeyOverride.put( qualify( path, override.name() ), override.foreignKey() );
 			}
 		}
 		return foreignKeyOverride;
 	}
 
-	private static AssociationOverride[] buildAssociationOverrides(AnnotationTarget element, String path, MetadataBuildingContext context) {
+	private static AssociationOverride[] buildAssociationOverrides(AnnotationTarget element, MetadataBuildingContext context) {
 		return element.getRepeatedAnnotationUsages( AssociationOverride.class, context.getBootstrapContext().getModelsContext() );
 	}
 
 	private static Map buildJoinTableOverride(AnnotationTarget element, String path, MetadataBuildingContext context) {
 		final Map result = new HashMap<>();
 		if ( element != null ) {
-			for ( var override : buildAssociationOverrides( element, path, context ) ) {
+			for ( var override : buildAssociationOverrides( element, context ) ) {
 				if ( isEmpty( override.joinColumns() ) ) {
 					result.put( qualify( path, override.name() ), override.joinTable() );
 				}
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java
index 5ff06ffcb345..f137eaffb0fb 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java
@@ -235,9 +235,7 @@ public AnnotatedColumn() {
 
 	public void bind() {
 		if ( isNotEmpty( formulaString ) ) {
-if ( BOOT_LOGGER.isTraceEnabled() ) {
-				BOOT_LOGGER.bindingFormula( formulaString );
-			}
+			BOOT_LOGGER.bindingFormula( formulaString );
 			formula = new Formula();
 			formula.setFormula( formulaString );
 		}
@@ -269,7 +267,7 @@ public void bind() {
 			if ( generatedAs != null ) {
 				mappingColumn.setGeneratedAs( generatedAs );
 			}
-if ( BOOT_LOGGER.isDebugEnabled() && logicalColumnName != null ) {
+			if ( logicalColumnName != null ) {
 				BOOT_LOGGER.bindingColumn( logicalColumnName );
 			}
 		}
@@ -353,9 +351,10 @@ public boolean isNameDeferred() {
 	 * @return {@code true} if a name could be inferred
 	 */
 	boolean inferColumnNameIfPossible(String columnName, String propertyName, boolean applyNamingStrategy) {
-		if ( !isEmpty( columnName ) || !isEmpty( propertyName ) ) {
+		if ( isNotEmpty( columnName ) || isNotEmpty( propertyName ) ) {
 			final String logicalColumnName = resolveLogicalColumnName( columnName, propertyName );
-			mappingColumn.setName( processColumnName( logicalColumnName, applyNamingStrategy ) );
+			mappingColumn.setName(
+					processColumnName( logicalColumnName, applyNamingStrategy, isNotEmpty( columnName ) ) );
 			return true;
 		}
 		else {
@@ -403,11 +402,12 @@ private String applyEmbeddedColumnNaming(String inferredColumnName, ComponentPro
 		return result;
 	}
 
-	protected String processColumnName(String columnName, boolean applyNamingStrategy) {
+	protected String processColumnName(String columnName, boolean applyNamingStrategy, boolean isExplicit) {
 		if ( applyNamingStrategy ) {
 			final var database = getDatabase();
 			return getPhysicalNamingStrategy()
-					.toPhysicalColumnName( database.toIdentifier( columnName ), database.getJdbcEnvironment() )
+					.toPhysicalColumnName( database.toIdentifier( columnName, isExplicit ),
+							database.getJdbcEnvironment() )
 					.render( database.getDialect() );
 		}
 		else {
@@ -770,7 +770,7 @@ private static jakarta.persistence.Column[] overrideColumns(
 						+ " columns (every column must have exactly one '@AttributeOverride')" );
 			}
 			if ( BOOT_LOGGER.isTraceEnabled() ) {
-	BOOT_LOGGER.columnMappingOverridden( inferredData.getPropertyName() );
+				BOOT_LOGGER.columnMappingOverridden( inferredData.getPropertyName() );
 			}
 			return isEmpty( overriddenCols ) ? null : overriddenCols;
 		}
@@ -930,7 +930,7 @@ void applyColumnDefault(PropertyData inferredData, int length) {
 			}
 		}
 		else {
-BOOT_LOGGER.couldNotPerformColumnDefaultLookup();
+			BOOT_LOGGER.couldNotPerformColumnDefaultLookup();
 		}
 	}
 
@@ -951,7 +951,7 @@ void applyGeneratedAs(PropertyData inferredData, int length) {
 			}
 		}
 		else {
-BOOT_LOGGER.couldNotPerformGeneratedColumnLookup();
+			BOOT_LOGGER.couldNotPerformGeneratedColumnLookup();
 		}
 }
 
@@ -994,9 +994,9 @@ void applyCheckConstraint(PropertyData inferredData, int length) {
 			}
 		}
 		else {
-BOOT_LOGGER.couldNotPerformCheckLookup();
+			BOOT_LOGGER.couldNotPerformCheckLookup();
 		}
-}
+	}
 
 	//must only be called after all setters are defined and before binding
 	private void extractDataFromPropertyData(
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumn.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumn.java
index e5c57d18d96e..716cc05fdea9 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumn.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumn.java
@@ -73,7 +73,7 @@ static AnnotatedJoinColumn buildJoinColumn(
 			PropertyHolder propertyHolder,
 			PropertyData inferredData) {
 		final String path = qualify( propertyHolder.getPath(), inferredData.getPropertyName() );
-		final JoinColumn[] overrides = propertyHolder.getOverriddenJoinColumn( path );
+		final var overrides = propertyHolder.getOverriddenJoinColumn( path );
 		if ( overrides != null ) {
 			//TODO: relax this restriction
 			throw new AnnotationException( "Property '" + path
@@ -334,7 +334,7 @@ public void linkValueUsingDefaultColumnNaming(
 		setLogicalColumnName( columnName );
 		setImplicit( true );
 		setReferencedColumn( logicalReferencedColumn );
-		final Column mappingColumn = getMappingColumn();
+		final var mappingColumn = getMappingColumn();
 		initMappingColumn(
 				columnName,
 				null,
@@ -363,7 +363,7 @@ private String defaultColumnName(int columnIndex, PersistentClass referencedEnti
 			// the name of the @MapsId annotation, but
 			// it's better than just having two different
 			// column names which disagree
-			final Column column = parent.resolveMapsId().getValue().getColumns().get( columnIndex );
+			final var column = parent.resolveMapsId().getValue().getColumns().get( columnIndex );
 //			return column.getQuotedName();
 			if ( column.isExplicit() ) {
 				throw new AnnotationException( "Association '" + parent.getPropertyName()
@@ -429,7 +429,7 @@ protected void addColumnBinding(SimpleValue value) {
 	 * @param column the referenced column.
 	 */
 	public void overrideFromReferencedColumnIfNecessary(Column column) {
-		final Column mappingColumn = getMappingColumn();
+		final var mappingColumn = getMappingColumn();
 		if ( mappingColumn != null ) {
 			// columnDefinition can also be specified using @JoinColumn, hence we have to check
 			// whether it is set or not
@@ -458,7 +458,8 @@ public void overrideFromReferencedColumnIfNecessary(Column column) {
 	@Override
 	boolean inferColumnNameIfPossible(String columnName, String propertyName, boolean applyNamingStrategy) {
 		if ( isNotEmpty( columnName ) ) {
-			getMappingColumn().setName( processColumnName( columnName, applyNamingStrategy ) );
+			getMappingColumn().setName(
+					processColumnName( columnName, applyNamingStrategy, isNotEmpty( columnName ) ) );
 			return true;
 		}
 		else {
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumns.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumns.java
index db72632a4cd2..cd2f6859df8d 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumns.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumns.java
@@ -40,6 +40,7 @@
 import static org.hibernate.boot.model.internal.AnnotatedJoinColumn.buildExplicitJoinTableJoinColumn;
 import static org.hibernate.boot.model.internal.AnnotatedJoinColumn.buildImplicitJoinTableJoinColumn;
 import static org.hibernate.boot.model.internal.AnnotatedJoinColumn.buildJoinColumn;
+import static org.hibernate.boot.model.internal.AnnotatedJoinColumn.buildJoinFormula;
 import static org.hibernate.boot.model.internal.BinderHelper.findReferencedColumnOwner;
 import static org.hibernate.boot.model.internal.BinderHelper.getRelativePath;
 import static org.hibernate.boot.model.internal.ForeignKeyType.EXPLICIT_PRIMARY_KEY_REFERENCE;
@@ -99,7 +100,7 @@ public static AnnotatedJoinColumns buildJoinColumnsOrFormulas(
 			final var column = columnOrFormula.column();
 			final String annotationString = formula.value();
 			if ( isNotBlank( annotationString ) ) {
-				AnnotatedJoinColumn.buildJoinFormula( formula, parent );
+				buildJoinFormula( formula, parent );
 			}
 			else {
 				buildJoinColumn( column, mappedBy, parent, propertyHolder, inferredData );
@@ -134,7 +135,7 @@ static AnnotatedJoinColumns buildJoinColumnsWithFormula(
 		joinColumns.setJoins( secondaryTables );
 		joinColumns.setPropertyHolder( propertyHolder );
 		joinColumns.setPropertyName( getRelativePath( propertyHolder, inferredData.getPropertyName() ) );
-		AnnotatedJoinColumn.buildJoinFormula( joinFormula, joinColumns );
+		buildJoinFormula( joinFormula, joinColumns );
 		handlePropertyRef( inferredData.getAttributeMember(), joinColumns );
 		return joinColumns;
 	}
@@ -168,15 +169,15 @@ public static AnnotatedJoinColumns buildJoinColumnsWithDefaultColumnSuffix(
 		assert mappedBy == null || !mappedBy.isBlank();
 		final String propertyName = inferredData.getPropertyName();
 		final String path = qualify( propertyHolder.getPath(), propertyName );
-		final JoinColumn[] overrides = propertyHolder.getOverriddenJoinColumn( path );
-		final JoinColumn[] actualColumns = overrides == null ? joinColumns : overrides;
+		final var overriddenJoinColumns = propertyHolder.getOverriddenJoinColumn( path );
+		final var actualJoinColumns = overriddenJoinColumns == null ? joinColumns : overriddenJoinColumns;
 		final var parent = new AnnotatedJoinColumns();
 		parent.setBuildingContext( context );
 		parent.setJoins( joins );
 		parent.setPropertyHolder( propertyHolder );
 		parent.setPropertyName( getRelativePath( propertyHolder, propertyName ) );
 		parent.setMappedBy( mappedBy );
-		if ( isEmpty( actualColumns ) ) {
+		if ( isEmpty( actualJoinColumns ) ) {
 			buildJoinColumn(
 					null,
 					mappedBy,
@@ -188,7 +189,7 @@ public static AnnotatedJoinColumns buildJoinColumnsWithDefaultColumnSuffix(
 		}
 		else {
 			parent.setMappedBy( mappedBy );
-			for ( var actualColumn : actualColumns ) {
+			for ( var actualColumn : actualJoinColumns ) {
 				buildJoinColumn(
 						actualColumn,
 						mappedBy,
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationBinder.java
index bb89b313fea6..8d85f0f95d84 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationBinder.java
@@ -430,8 +430,7 @@ private static void handleConverterRegistration(ConverterRegistration registrati
 				.addRegisteredConversion( new RegisteredConversion(
 						registration.domainType(),
 						registration.converter(),
-						registration.autoApply(),
-						context
+						registration.autoApply()
 				) );
 	}
 
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationHelper.java
index b6cf53a618be..10f300375a5f 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationHelper.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationHelper.java
@@ -41,7 +41,7 @@ public static HashMap extractParameterMap(Parameter[] parameters
 
 	public static JdbcMapping resolveUserType(Class> userTypeClass, MetadataBuildingContext context) {
 		final var bootstrapContext = context.getBootstrapContext();
-		final UserType userType =
+		final var userType =
 				context.getBuildingOptions().isAllowExtensionsInCdi()
 						? bootstrapContext.getManagedBeanRegistry().getBean( userTypeClass ).getBeanInstance()
 						: FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( userTypeClass );
@@ -74,7 +74,7 @@ public static  JdbcMapping resolveAttributeConverter(
 
 	public static BasicType resolveBasicType(Class type, MetadataBuildingContext context) {
 		final var typeConfiguration = context.getBootstrapContext().getTypeConfiguration();
-		final JavaType jtd = typeConfiguration.getJavaTypeRegistry().findDescriptor( type );
+		final var jtd = typeConfiguration.getJavaTypeRegistry().findDescriptor( type );
 		if ( jtd != null ) {
 			final JdbcType jdbcType = jtd.getRecommendedJdbcType(
 					new JdbcTypeIndicators() {
@@ -132,7 +132,7 @@ private static JavaType getJavaType(
 			Class> javaTypeClass,
 			MetadataBuildingContext context,
 			TypeConfiguration typeConfiguration) {
-		final JavaType registeredJtd =
+		final var registeredJtd =
 				typeConfiguration.getJavaTypeRegistry()
 						.findDescriptor( javaTypeClass );
 		if ( registeredJtd != null ) {
@@ -143,8 +143,7 @@ else if ( !context.getBuildingOptions().isAllowExtensionsInCdi() ) {
 		}
 		else {
 			return context.getBootstrapContext().getManagedBeanRegistry()
-					.getBean( javaTypeClass )
-					.getBeanInstance();
+					.getBean( javaTypeClass ).getBeanInstance();
 		}
 	}
 }
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java
index 6658fc70de8e..adbd6ef9f39e 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java
@@ -102,7 +102,8 @@ public enum Kind {
 	// in-flight info
 
 	private Class> explicitCustomType;
-	private Map explicitLocalTypeParams;
+	private Map explicitLocalCustomTypeParams;
+	private Annotation explicitCustomTypeAnnotation;
 
 	private Function explicitJdbcTypeAccess;
 	private Function> explicitJavaTypeAccess;
@@ -338,8 +339,9 @@ private boolean applyCustomType(MemberDetails memberDetails, TypeDetails typeDet
 		final var modelContext = getSourceModelContext();
 		final var userTypeImpl = kind.mappingAccess.customType( memberDetails, modelContext );
 		if ( userTypeImpl != null ) {
-			applyExplicitType( userTypeImpl,
-					kind.mappingAccess.customTypeParameters( memberDetails, modelContext ) );
+			this.explicitCustomType = userTypeImpl;
+			this.explicitLocalCustomTypeParams = kind.mappingAccess.customTypeParameters( memberDetails, modelContext );
+			this.explicitCustomTypeAnnotation = kind.mappingAccess.customTypeAnnotation( memberDetails, modelContext );
 			// An explicit custom UserType has top precedence when we get to BasicValue resolution.
 			return true;
 		}
@@ -349,7 +351,8 @@ private boolean applyCustomType(MemberDetails memberDetails, TypeDetails typeDet
 			final var registeredUserTypeImpl =
 					getMetadataCollector().findRegisteredUserType( basicClass );
 			if ( registeredUserTypeImpl != null ) {
-				applyExplicitType( registeredUserTypeImpl, emptyMap() );
+				this.explicitCustomType = registeredUserTypeImpl;
+				this.explicitLocalCustomTypeParams = emptyMap();
 				return true;
 			}
 		}
@@ -384,11 +387,6 @@ private void prepareValue(MemberDetails value, TypeDetails typeDetails, @Nullabl
 		}
 	}
 
-	private void applyExplicitType(Class> userTypeImpl, Map parameters) {
-		explicitCustomType = userTypeImpl;
-		explicitLocalTypeParams = parameters;
-	}
-
 	private void prepareCollectionId(MemberDetails attribute) {
 		final var collectionId = attribute.getDirectAnnotationUsage( CollectionId.class );
 		if ( collectionId == null ) {
@@ -831,7 +829,7 @@ private void prepareAnyDiscriminator(MemberDetails memberDetails) {
 			return originalResolution != null
 					? originalResolution
 					: typeConfiguration.getJavaTypeRegistry()
-							.getDescriptor( implicitJavaTypeAccess.apply( typeConfiguration ) )
+							.resolveDescriptor( implicitJavaTypeAccess.apply( typeConfiguration ) )
 							.getRecommendedJdbcType( typeConfiguration.getCurrentBaseSqlTypeIndicators() );
 		};
 	}
@@ -1188,14 +1186,17 @@ else if ( aggregateComponent != null ) {
 			firstColumn.linkWithAggregateValue( basicValue, aggregateComponent );
 		}
 		else {
-			for ( AnnotatedColumn column : columns.getColumns() ) {
+			for ( var column : columns.getColumns() ) {
 				column.linkWithValue( basicValue );
 			}
 		}
 	}
 
 	public void fillSimpleValue() {
-		basicValue.setExplicitTypeParams( explicitLocalTypeParams );
+
+		basicValue.setMemberDetails( memberDetails );
+
+		basicValue.setExplicitTypeParams( explicitLocalCustomTypeParams );
 
 		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 		// todo (6.0) : we are dropping support for @Type and @TypeDef from annotations
@@ -1211,6 +1212,10 @@ public void fillSimpleValue() {
 			basicValue.setTypeParameters( createDynamicParameterizedTypeParameters() );
 		}
 
+		if ( explicitCustomType != null ) {
+			basicValue.setTypeAnnotation( explicitCustomTypeAnnotation );
+		}
+
 		if ( converterDescriptor != null ) {
 			basicValue.setJpaAttributeConverterDescriptor( converterDescriptor );
 		}
@@ -1282,8 +1287,8 @@ private Map createDynamicParameterizedTypeParameters() {
 			parameters.put( DynamicParameterizedType.ACCESS_TYPE, accessType.getType() );
 		}
 
-		if ( explicitLocalTypeParams != null ) {
-			parameters.putAll( explicitLocalTypeParams );
+		if ( explicitLocalCustomTypeParams != null ) {
+			parameters.putAll( explicitLocalCustomTypeParams );
 		}
 
 		return parameters;
@@ -1295,6 +1300,7 @@ private Map createDynamicParameterizedTypeParameters() {
 	private interface BasicMappingAccess {
 		Class> customType(MemberDetails attribute, ModelsContext context);
 		Map customTypeParameters(MemberDetails attribute, ModelsContext context);
+		Annotation customTypeAnnotation(MemberDetails attribute, ModelsContext context);
 	}
 
 	private static class ValueMappingAccess implements BasicMappingAccess {
@@ -1311,6 +1317,12 @@ public Map customTypeParameters(MemberDetails attribute, ModelsCo
 			final var customType = attribute.locateAnnotationUsage( Type.class, context );
 			return customType == null ? null : extractParameterMap( customType.parameters() );
 		}
+
+		@Override
+		public Annotation customTypeAnnotation(MemberDetails attribute, ModelsContext context) {
+			final var annotations = attribute.getMetaAnnotated( Type.class, context );
+			return annotations == null || annotations.isEmpty() ? null : annotations.get( 0 );
+		}
 	}
 
 	private static class AnyDiscriminatorMappingAccess implements BasicMappingAccess {
@@ -1325,6 +1337,11 @@ public Class> customType(MemberDetails attribute, ModelsCo
 		public Map customTypeParameters(MemberDetails attribute, ModelsContext context) {
 			return emptyMap();
 		}
+
+		@Override
+		public Annotation customTypeAnnotation(MemberDetails attribute, ModelsContext context) {
+			return null;
+		}
 	}
 
 	private static class AnyKeyMappingAccess implements BasicMappingAccess {
@@ -1339,6 +1356,11 @@ public Class> customType(MemberDetails attribute, ModelsCo
 		public Map customTypeParameters(MemberDetails attribute, ModelsContext context) {
 			return emptyMap();
 		}
+
+		@Override
+		public Annotation customTypeAnnotation(MemberDetails attribute, ModelsContext context) {
+			return null;
+		}
 	}
 
 	private static class MapKeyMappingAccess implements BasicMappingAccess {
@@ -1357,6 +1379,12 @@ public Map customTypeParameters(MemberDetails attribute, ModelsCo
 			return customType == null ? null : extractParameterMap( customType.parameters() );
 
 		}
+
+		@Override
+		public Annotation customTypeAnnotation(MemberDetails attribute, ModelsContext context) {
+			final var annotations = attribute.getMetaAnnotated( MapKeyType.class, context );
+			return annotations == null || annotations.isEmpty() ? null : annotations.get( 0 );
+		}
 	}
 
 	private static class CollectionIdMappingAccess implements BasicMappingAccess {
@@ -1373,7 +1401,12 @@ public Class> customType(MemberDetails attribute, ModelsCo
 		public Map customTypeParameters(MemberDetails attribute, ModelsContext context) {
 			final var customType = attribute.locateAnnotationUsage( CollectionIdType.class, context );
 			return customType == null ? null : extractParameterMap( customType.parameters() );
+		}
 
+		@Override
+		public Annotation customTypeAnnotation(MemberDetails attribute, ModelsContext context) {
+			final var annotations = attribute.getMetaAnnotated( CollectionIdType.class, context );
+			return annotations == null || annotations.isEmpty() ? null : annotations.get( 0 );
 		}
 	}
 
@@ -1389,6 +1422,11 @@ public Class> customType(MemberDetails attribute, ModelsCo
 		public Map customTypeParameters(MemberDetails attribute, ModelsContext context) {
 			return emptyMap();
 		}
+
+		@Override
+		public Annotation customTypeAnnotation(MemberDetails attribute, ModelsContext context) {
+			return null;
+		}
 	}
 
 	private static AnnotatedJoinColumns convertToJoinColumns(AnnotatedColumns columns, MetadataBuildingContext context) {
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java
index 2ec9461d3656..f907c737335f 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java
@@ -12,7 +12,7 @@
 import org.hibernate.AssertionFailure;
 import org.hibernate.PropertyNotFoundException;
 import org.hibernate.boot.spi.MetadataBuildingContext;
-import org.hibernate.boot.spi.SecondPass;
+import org.hibernate.mapping.BasicValue;
 import org.hibernate.mapping.Collection;
 import org.hibernate.mapping.Component;
 import org.hibernate.mapping.IndexedCollection;
@@ -29,6 +29,8 @@
 
 import jakarta.persistence.Convert;
 import jakarta.persistence.JoinTable;
+import org.hibernate.models.spi.TypeDetails;
+import org.hibernate.type.internal.ParameterizedTypeImpl;
 
 import static org.hibernate.internal.util.StringHelper.isEmpty;
 import static org.hibernate.internal.util.collections.CollectionHelper.mapOfSize;
@@ -312,29 +314,25 @@ static void prepareActualProperty(
 			if ( !allowCollections ) {
 				throw new AssertionFailure( "Collections are not allowed as identifier properties" );
 			}
-			// The owner is a MappedSuperclass which is not a PersistentClass, so set it to null
-//						collection.setOwner( null );
+			// The owner is a MappedSuperclass, not a PersistentClass,
+			// so set it to null collection.setOwner( null );
 			collection.setRole( memberDetails.getDeclaringType().getName() + "." + property.getName() );
 			// To copy the element and key values, we need to defer setting the type name until the CollectionBinder ran
 			final var originalValue = property.getValue();
-			context.getMetadataCollector().addSecondPass(
-					new SecondPass() {
-						@Override
-						public void doSecondPass(Map persistentClasses) {
-							final var initializedCollection = (Collection) originalValue;
-							final var element = initializedCollection.getElement().copy();
-							setTypeName( element, memberDetails.getElementType().getName() );
-							if ( initializedCollection instanceof IndexedCollection indexedCollection ) {
-								final var index = indexedCollection.getIndex().copy();
-								if ( memberDetails.getMapKeyType() != null ) {
-									setTypeName( index, memberDetails.getMapKeyType().getName() );
-								}
-								( (IndexedCollection) collection ).setIndex( index );
-							}
-							collection.setElement( element );
-						}
+			context.getMetadataCollector().addSecondPass( persistentClasses -> {
+				final var initializedCollection = (Collection) originalValue;
+				final var element = initializedCollection.getElement().copy();
+				setTypeName( element, memberDetails.getElementType().getName() );
+				if ( initializedCollection instanceof IndexedCollection indexedCollection ) {
+					final var index = indexedCollection.getIndex().copy();
+					final var mapKeyType = memberDetails.getMapKeyType();
+					if ( mapKeyType != null ) {
+						setTypeName( index, mapKeyType.getName() );
 					}
-			);
+					( (IndexedCollection) collection ).setIndex( index );
+				}
+				collection.setElement( element );
+			} );
 		}
 		else {
 			setTypeName( value, memberDetails.getType().getName() );
@@ -419,6 +417,34 @@ else if ( value instanceof SimpleValue simpleValue ) {
 		}
 	}
 
+	static void setType(Value value, TypeDetails type) {
+		final var typeName = type.getName();
+		if ( value instanceof ToOne toOne ) {
+			toOne.setReferencedEntityName( typeName );
+			toOne.setTypeName( typeName );
+		}
+		else if ( value instanceof Component component ) {
+			// Avoid setting type name for generic components
+			if ( !component.isGeneric() ) {
+				component.setComponentClassName( typeName );
+			}
+			if ( component.getTypeName() != null ) {
+				component.setTypeName( typeName );
+			}
+		}
+		else if ( value instanceof SimpleValue simpleValue ) {
+			if ( value instanceof BasicValue basicValue ) {
+				basicValue.setImplicitJavaTypeAccess( typeConfiguration ->
+						type.getTypeKind() == TypeDetails.Kind.PARAMETERIZED_TYPE
+						? ParameterizedTypeImpl.from( type.asParameterizedType() )
+						: type.determineRawClass().toJavaClass() );
+			}
+			else {
+				simpleValue.setTypeName( typeName );
+			}
+		}
+	}
+
 	private void addPropertyToJoin(Property property, MemberDetails memberDetails, ClassDetails declaringClass, Join join) {
 		if ( declaringClass != null ) {
 			final var inheritanceState = inheritanceStatePerClass.get( declaringClass );
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java
index f60ab5e44969..646426742685 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java
@@ -1062,9 +1062,7 @@ private void bind() {
 		}
 		collection = createCollection( propertyHolder.getPersistentClass() );
 		final String role = qualify( propertyHolder.getPath(), propertyName );
-		if ( BOOT_LOGGER.isTraceEnabled() ) {
-			BOOT_LOGGER.bindingCollectionRole( role );
-		}
+		BOOT_LOGGER.bindingCollectionRole( role );
 		collection.setRole( role );
 		collection.setMappedByProperty( mappedBy );
 
@@ -1145,16 +1143,14 @@ private void detectMappedByProblem(boolean isMappedBy) {
 			}
 			if ( oneToMany ) {
 				if ( property.hasDirectAnnotationUsage( MapKeyColumn.class ) ) {
-					BOOT_LOGGER.warn( "Association '"
-									+ qualify( propertyHolder.getPath(), propertyName )
-									+ "' is 'mappedBy' another entity and should not specify a '@MapKeyColumn'"
-									+ " (use '@MapKey' instead)" );
+					BOOT_LOGGER.mappedByShouldNotSpecifyMapKeyColumn(
+							qualify( propertyHolder.getPath(), propertyName )
+					);
 				}
 				if ( property.hasDirectAnnotationUsage( OrderColumn.class ) ) {
-					BOOT_LOGGER.warn( "Association '"
-									+ qualify( propertyHolder.getPath(), propertyName )
-									+ "' is 'mappedBy' another entity and should not specify an '@OrderColumn'"
-									+ " (use '@OrderBy' instead)" );
+					BOOT_LOGGER.mappedByShouldNotSpecifyOrderColumn(
+							qualify( propertyHolder.getPath(), propertyName )
+					);
 				}
 			}
 			else {
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ComponentPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ComponentPropertyHolder.java
index c59f88942003..1991f1b1b6bb 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ComponentPropertyHolder.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ComponentPropertyHolder.java
@@ -12,7 +12,6 @@
 import org.hibernate.annotations.EmbeddedTable;
 import org.hibernate.boot.model.naming.Identifier;
 import org.hibernate.boot.models.AnnotationPlacementException;
-import org.hibernate.boot.spi.InFlightMetadataCollector;
 import org.hibernate.boot.spi.MetadataBuildingContext;
 import org.hibernate.boot.spi.PropertyData;
 import org.hibernate.mapping.AggregateColumn;
@@ -118,40 +117,30 @@ public static void applyExplicitTableName(
 			PropertyHolder container,
 			MetadataBuildingContext buildingContext) {
 		Table tableToUse = container.getTable();
-		boolean wasExplicit = false;
-		if ( container instanceof ComponentPropertyHolder componentPropertyHolder ) {
-			wasExplicit = componentPropertyHolder.getComponent().wasTableExplicitlyDefined();
-		}
+		boolean wasExplicit =
+				container instanceof ComponentPropertyHolder componentPropertyHolder
+						&& componentPropertyHolder.getComponent().wasTableExplicitlyDefined();
 
-		if ( propertyData.getAttributeMember() != null ) {
-			final EmbeddedTable embeddedTableAnn = propertyData.getAttributeMember()
-					.getDirectAnnotationUsage( EmbeddedTable.class );
+		final var attributeMember = propertyData.getAttributeMember();
+		if ( attributeMember != null ) {
+			final var embeddedTableAnn = attributeMember.getDirectAnnotationUsage( EmbeddedTable.class );
 			// we only allow this when done for an embedded on an entity or mapped-superclass
 			if ( container instanceof ClassPropertyHolder ) {
 				if ( embeddedTableAnn != null ) {
-					final Identifier tableNameIdentifier = buildingContext.getObjectNameNormalizer().normalizeIdentifierQuoting( embeddedTableAnn.value() );
-					final InFlightMetadataCollector.EntityTableXref entityTableXref = buildingContext
-							.getMetadataCollector()
-							.getEntityTableXref( container.getEntityName() );
-					tableToUse =  entityTableXref.resolveTable( tableNameIdentifier );
+					tableToUse = resolveEmbeddedTable( container, buildingContext, embeddedTableAnn );
 					wasExplicit = true;
 				}
 			}
-			else {
-				if ( embeddedTableAnn != null ) {
-					// not allowed
-					throw new AnnotationPlacementException( "@EmbeddedTable only supported for use on entity or mapped-superclass" );
-				}
+			else if ( embeddedTableAnn != null ) {
+				// not allowed
+				throw new AnnotationPlacementException(
+						"@EmbeddedTable only supported for use on entity or mapped-superclass" );
 			}
 		}
-		if ( propertyData.getAttributeMember() != null && container instanceof ClassPropertyHolder ) {
-			final EmbeddedTable embeddedTableAnn = propertyData.getAttributeMember().getDirectAnnotationUsage( EmbeddedTable.class );
+		if ( attributeMember != null && container instanceof ClassPropertyHolder ) {
+			final var embeddedTableAnn = attributeMember.getDirectAnnotationUsage( EmbeddedTable.class );
 			if ( embeddedTableAnn != null ) {
-				final Identifier tableNameIdentifier = buildingContext.getObjectNameNormalizer().normalizeIdentifierQuoting( embeddedTableAnn.value() );
-				final InFlightMetadataCollector.EntityTableXref entityTableXref = buildingContext
-						.getMetadataCollector()
-						.getEntityTableXref( container.getEntityName() );
-				tableToUse =  entityTableXref.resolveTable( tableNameIdentifier );
+				tableToUse = resolveEmbeddedTable( container, buildingContext, embeddedTableAnn );
 				wasExplicit = true;
 			}
 		}
@@ -159,6 +148,17 @@ public static void applyExplicitTableName(
 		component.setTable( tableToUse, wasExplicit );
 	}
 
+	private static Table resolveEmbeddedTable(
+			PropertyHolder container, MetadataBuildingContext buildingContext, EmbeddedTable embeddedTableAnn) {
+		final Identifier tableNameIdentifier =
+				buildingContext.getObjectNameNormalizer()
+						.normalizeIdentifierQuoting( embeddedTableAnn.value() );
+		final var entityTableXref =
+				buildingContext.getMetadataCollector()
+						.getEntityTableXref( container.getEntityName() );
+		return entityTableXref.resolveTable( tableNameIdentifier );
+	}
+
 	/**
 	 * Access to the underlying component
 	 */
@@ -235,21 +235,19 @@ protected String normalizeCompositePathForLogging(String attributeName) {
 	public void startingProperty(MemberDetails propertyMemberDetails) {
 		if ( propertyMemberDetails != null ) {
 			// again: the property coming in here *should* be the property on the embeddable (Address#city in the example),
-			// so we just ignore it if there is already an existing conversion info for that path since they would have
+			// so we just ignore it if there is already an existing ConversionInfo for that path since they would have
 			// precedence
 
 			// technically we should only do this for properties of "basic type"
 
 			final String attributeName = propertyMemberDetails.resolveAttributeName();
 			final String path = embeddedAttributeName + '.' + attributeName;
-			if ( attributeConversionInfoMap.containsKey( path ) ) {
-				return;
+			if ( !attributeConversionInfoMap.containsKey( path ) ) {
+				propertyMemberDetails.forEachAnnotationUsage( Convert.class, getSourceModelContext(), (usage) -> {
+					final var info = new AttributeConversionInfo( usage, propertyMemberDetails );
+					attributeConversionInfoMap.put( attributeName, info );
+				} );
 			}
-
-			propertyMemberDetails.forEachAnnotationUsage( Convert.class, getSourceModelContext(), (usage) -> {
-				final AttributeConversionInfo info = new AttributeConversionInfo( usage, propertyMemberDetails );
-				attributeConversionInfoMap.put( attributeName, info );
-			} );
 		}
 	}
 
@@ -403,7 +401,7 @@ public void setParentProperty(String parentProperty) {
 	@Override
 	public Column[] getOverriddenColumn(String propertyName) {
 		//FIXME this is yukky
-		Column[] result = super.getOverriddenColumn( propertyName );
+		var result = super.getOverriddenColumn( propertyName );
 		if ( result == null ) {
 			final String userPropertyName = extractUserPropertyName( "id", propertyName );
 			if ( userPropertyName != null ) {
@@ -434,6 +432,7 @@ private String extractUserPropertyName(String redundantString, String propertyNa
 
 	@Override
 	public String toString() {
-		return getClass().getSimpleName() + "(" + parent.normalizeCompositePathForLogging( embeddedAttributeName ) + ")";
+		return getClass().getSimpleName()
+			+ "(" + parent.normalizeCompositePathForLogging( embeddedAttributeName ) + ")";
 	}
 }
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java
index e553f89e1c2b..b0e9332eaeb8 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java
@@ -29,7 +29,40 @@
 import org.hibernate.AnnotationException;
 import org.hibernate.AssertionFailure;
 import org.hibernate.MappingException;
-import org.hibernate.annotations.*;
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
+import org.hibernate.annotations.CacheLayout;
+import org.hibernate.annotations.Check;
+import org.hibernate.annotations.Checks;
+import org.hibernate.annotations.ConcreteProxy;
+import org.hibernate.annotations.DiscriminatorFormula;
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
+import org.hibernate.annotations.Filter;
+import org.hibernate.annotations.Filters;
+import org.hibernate.annotations.HQLSelect;
+import org.hibernate.annotations.Immutable;
+import org.hibernate.annotations.Mutability;
+import org.hibernate.annotations.NaturalIdCache;
+import org.hibernate.annotations.NaturalIdClass;
+import org.hibernate.annotations.OnDelete;
+import org.hibernate.annotations.OptimisticLockType;
+import org.hibernate.annotations.OptimisticLocking;
+import org.hibernate.annotations.QueryCacheLayout;
+import org.hibernate.annotations.RowId;
+import org.hibernate.annotations.SQLDelete;
+import org.hibernate.annotations.SQLDeleteAll;
+import org.hibernate.annotations.SQLInsert;
+import org.hibernate.annotations.SQLRestriction;
+import org.hibernate.annotations.SQLSelect;
+import org.hibernate.annotations.SQLUpdate;
+import org.hibernate.annotations.SecondaryRow;
+import org.hibernate.annotations.SecondaryRows;
+import org.hibernate.annotations.SoftDelete;
+import org.hibernate.annotations.Subselect;
+import org.hibernate.annotations.Synchronize;
+import org.hibernate.annotations.TypeBinderType;
+import org.hibernate.annotations.View;
 import org.hibernate.binder.TypeBinder;
 import org.hibernate.boot.model.NamedEntityGraphDefinition;
 import org.hibernate.boot.model.internal.InheritanceState.ElementsToProcess;
@@ -48,6 +81,7 @@
 import org.hibernate.boot.spi.InFlightMetadataCollector;
 import org.hibernate.boot.spi.MetadataBuildingContext;
 import org.hibernate.boot.spi.PropertyData;
+import org.hibernate.boot.spi.SecondPass;
 import org.hibernate.cfg.AvailableSettings;
 import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
 import org.hibernate.internal.util.StringHelper;
@@ -59,6 +93,7 @@
 import org.hibernate.mapping.Join;
 import org.hibernate.mapping.JoinedSubclass;
 import org.hibernate.mapping.PersistentClass;
+import org.hibernate.mapping.Property;
 import org.hibernate.mapping.RootClass;
 import org.hibernate.mapping.SimpleValue;
 import org.hibernate.mapping.SingleTableSubclass;
@@ -71,6 +106,7 @@
 import org.hibernate.models.internal.ClassTypeDetailsImpl;
 import org.hibernate.models.spi.AnnotationTarget;
 import org.hibernate.models.spi.ClassDetails;
+import org.hibernate.models.spi.MemberDetails;
 import org.hibernate.models.spi.ModelsContext;
 import org.hibernate.models.spi.TypeDetails;
 import org.hibernate.spi.NavigablePath;
@@ -86,6 +122,7 @@
 
 import static jakarta.persistence.InheritanceType.SINGLE_TABLE;
 import static java.util.Collections.addAll;
+import static org.hibernate.boot.BootLogging.BOOT_LOGGER;
 import static org.hibernate.boot.model.internal.AnnotatedClassType.MAPPED_SUPERCLASS;
 import static org.hibernate.boot.model.internal.AnnotatedDiscriminatorColumn.DEFAULT_DISCRIMINATOR_COLUMN_NAME;
 import static org.hibernate.boot.model.internal.AnnotatedDiscriminatorColumn.buildDiscriminatorColumn;
@@ -97,6 +134,7 @@
 import static org.hibernate.boot.model.internal.BinderHelper.hasToOneAnnotation;
 import static org.hibernate.boot.model.internal.BinderHelper.toAliasEntityMap;
 import static org.hibernate.boot.model.internal.BinderHelper.toAliasTableMap;
+import static org.hibernate.boot.model.internal.ClassPropertyHolder.setType;
 import static org.hibernate.boot.model.internal.DialectOverridesAnnotationHelper.getOverridableAnnotation;
 import static org.hibernate.boot.model.internal.DialectOverridesAnnotationHelper.getOverrideAnnotation;
 import static org.hibernate.boot.model.internal.EmbeddableBinder.fillEmbeddable;
@@ -112,7 +150,6 @@
 import static org.hibernate.boot.spi.AccessType.getAccessStrategy;
 import static org.hibernate.engine.OptimisticLockStyle.fromLockType;
 import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle;
-import static org.hibernate.boot.BootLogging.BOOT_LOGGER;
 import static org.hibernate.internal.util.ReflectHelper.getDefaultSupplier;
 import static org.hibernate.internal.util.StringHelper.isBlank;
 import static org.hibernate.internal.util.StringHelper.isNotBlank;
@@ -122,6 +159,7 @@
 import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty;
 import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty;
 import static org.hibernate.jpa.event.internal.CallbackDefinitionResolver.resolveLifecycleCallbacks;
+import static org.hibernate.models.spi.TypeDetailsHelper.resolveRelativeType;
 import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.EMBEDDED;
 
 
@@ -159,6 +197,7 @@ public class EntityBinder {
 	private String cacheRegion;
 	private boolean cacheLazyProperty;
 	private String naturalIdCacheRegion;
+	private ClassDetails naturalIdClass;
 	private CacheLayout queryCacheLayout;
 
 	private ModelsContext modelsContext() {
@@ -1088,6 +1127,7 @@ private void processIdPropertiesIfNotAlready(
 				missingIdProperties.remove( propertyName );
 			}
 		}
+		addGenericProperties( persistentClass, inheritanceState, inheritanceStates );
 
 		if ( !missingIdProperties.isEmpty() ) {
 			throw new AnnotationException( "Entity '" + persistentClass.getEntityName()
@@ -1101,6 +1141,77 @@ else if ( !missingEntityProperties.isEmpty() ) {
 		}
 	}
 
+	private void addGenericProperties(
+			PersistentClass persistentClass,
+			InheritanceState inheritanceState,
+			Map inheritanceStates) {
+		if ( persistentClass.isAbstract() == null || !persistentClass.isAbstract() ) {
+			var superclass = persistentClass.getSuperPersistentClass();
+			while ( superclass != null ) {
+				for ( Property declaredProperty : superclass.getDeclaredProperties() ) {
+					if ( declaredProperty.isGeneric() ) {
+						final var memberDetails = getMemberDetails( inheritanceState, inheritanceStates, declaredProperty, superclass );
+						final var typeDetails = resolveRelativeType( memberDetails.getType(), inheritanceState.getClassDetails() );
+						final var returnedClassName = typeDetails.getName();
+						final var actualProperty = declaredProperty.copy();
+						actualProperty.setGeneric( false );
+						actualProperty.setGenericSpecialization( true );
+						actualProperty.setReturnedClassName( returnedClassName );
+						final var value = actualProperty.getValue().copy();
+						setType( value, typeDetails );
+						actualProperty.setValue( value );
+						persistentClass.addProperty( actualProperty );
+						if ( value instanceof BasicValue basicValue ) {
+							final InFlightMetadataCollector metadataCollector = context.getMetadataCollector();
+							final BasicValue originalBasicValue = (BasicValue) declaredProperty.getValue();
+							metadataCollector.addSecondPass( new SecondPass() {
+								@Override
+								public void doSecondPass(Map persistentClasses)
+										throws MappingException {
+									basicValue.setExplicitTypeParams( originalBasicValue.getExplicitTypeParams() );
+									basicValue.setTypeParameters( originalBasicValue.getTypeParameters() );
+									basicValue.setJpaAttributeConverterDescriptor( originalBasicValue.getJpaAttributeConverterDescriptor() );
+									// Don't copy over the implicit java type access, since we figure that out in ClassPropertyHolder#setType
+//									basicValue.setImplicitJavaTypeAccess( originalBasicValue.getImplicitJavaTypeAccess() );
+									basicValue.setExplicitJavaTypeAccess( originalBasicValue.getExplicitJavaTypeAccess() );
+									basicValue.setExplicitJdbcTypeAccess( originalBasicValue.getExplicitJdbcTypeAccess() );
+									basicValue.setExplicitMutabilityPlanAccess( originalBasicValue.getExplicitMutabilityPlanAccess() );
+									basicValue.setEnumerationStyle( originalBasicValue.getEnumeratedType() );
+									basicValue.setTimeZoneStorageType( originalBasicValue.getTimeZoneStorageType() );
+									basicValue.setTemporalPrecision( originalBasicValue.getTemporalPrecision() );
+									if ( originalBasicValue.isLob() ) {
+										basicValue.makeLob();
+									}
+									if ( originalBasicValue.isNationalized() ) {
+										basicValue.makeNationalized();
+									}
+								}
+							} );
+							metadataCollector.registerValueMappingResolver( basicValue::resolve );
+						}
+					}
+				}
+
+				superclass = superclass.getSuperPersistentClass();
+			}
+		}
+	}
+
+	private static MemberDetails getMemberDetails(InheritanceState inheritanceState, Map inheritanceStates, Property declaredProperty, PersistentClass superclass) {
+		var superclassDetails = inheritanceState.getClassDetails().getSuperClass();
+		while ( !superclass.getClassName().equals( superclassDetails.getClassName()) ) {
+			superclassDetails = superclassDetails.getSuperClass();
+		}
+		final var superclassInheritanceState = inheritanceStates.get( superclassDetails );
+		final var elementsToProcess = superclassInheritanceState.getElementsToProcess();
+		for ( PropertyData element : elementsToProcess.getElements() ) {
+			if ( declaredProperty.getName().equals( element.getPropertyName() ) ) {
+				return element.getAttributeMember();
+			}
+		}
+		throw new IllegalArgumentException("Couldn't find PropertyData for [" + declaredProperty.getName() + "] in class: " + declaredProperty.getPersistentClass().getClassName() );
+	}
+
 	private static String getMissingPropertiesString(Set propertyNames) {
 		final var missingProperties = new StringBuilder();
 		for ( String propertyName : propertyNames ) {
@@ -1264,6 +1375,7 @@ private void bindEntity() {
 		bindSqlRestriction();
 		bindCache();
 		bindNaturalIdCache();
+		bindNaturalIdClass();
 		bindFiltersInHierarchy();
 
 		persistentClass.setAbstract( annotatedClass.isAbstract() );
@@ -1338,6 +1450,7 @@ private void bindRootEntity() {
 			rootClass.setLazyPropertiesCacheable( cacheLazyProperty );
 		}
 		rootClass.setNaturalIdCacheRegionName( naturalIdCacheRegion );
+		rootClass.setNaturalIdClass( naturalIdClass );
 	}
 
 	private void bindCustomSql() {
@@ -1613,6 +1726,20 @@ private SQLRestriction extractSQLRestriction(ClassDetails classDetails) {
 		return null;
 	}
 
+	private void bindNaturalIdClass() {
+		final var ann = annotatedClass.getAnnotationUsage( NaturalIdClass.class, modelsContext() );
+		if ( ann != null ) {
+			if ( ann.value() == void.class ) {
+				throw new IllegalStateException( "NaturalIdClass#value must not be void.class" );
+			}
+			naturalIdClass = context
+					.getBootstrapContext()
+					.getModelsContext()
+					.getClassDetailsRegistry()
+					.resolveClassDetails( ann.value().getName() );
+		}
+	}
+
 	private void bindNaturalIdCache() {
 		final var naturalIdCache = annotatedClass.getAnnotationUsage( NaturalIdCache.class, modelsContext() );
 		if ( naturalIdCache != null ) {
@@ -1760,7 +1887,8 @@ public MetadataBuildingContext getBuildingContext() {
 
 		@Override
 		public Identifier handleExplicitName(String explicitName, MetadataBuildingContext buildingContext) {
-			return jdbcEnvironment( buildingContext ).getIdentifierHelper().toIdentifier( explicitName );
+			return jdbcEnvironment( buildingContext ).getIdentifierHelper()
+					.toIdentifier( explicitName, false, true );
 		}
 
 		@Override
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorAnnotationHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorAnnotationHelper.java
index d0e51cb34de8..4fe88d275b01 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorAnnotationHelper.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorAnnotationHelper.java
@@ -7,6 +7,8 @@
 import jakarta.persistence.SequenceGenerator;
 import jakarta.persistence.TableGenerator;
 import org.checkerframework.checker.nullness.qual.Nullable;
+import org.hibernate.AnnotationException;
+import org.hibernate.AssertionFailure;
 import org.hibernate.annotations.GenericGenerator;
 import org.hibernate.annotations.IdGeneratorType;
 import org.hibernate.boot.model.IdentifierGeneratorDefinition;
@@ -33,6 +35,7 @@
 import org.hibernate.models.spi.ModelsContext;
 
 import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
@@ -278,29 +281,11 @@ public static  void prepareForUse(
 			Consumer configBaseline,
 			BiConsumer configExtractor,
 			GeneratorCreationContext creationContext) {
-		if ( generator instanceof AnnotationBasedGenerator ) {
-			@SuppressWarnings("unchecked")
-			final var generation = (AnnotationBasedGenerator) generator;
-			generation.initialize( annotation, idMember.toJavaMember(), creationContext );
+		if ( generator instanceof AnnotationBasedGenerator annotationBasedGenerator ) {
+			initializeGenerator( annotationBasedGenerator, annotation, creationContext );
 		}
 		if ( generator instanceof Configurable configurable ) {
-			final var properties = new Properties();
-			if ( configBaseline != null ) {
-				configBaseline.accept( properties );
-			}
-			collectBaselineProperties(
-					creationContext.getProperty() != null
-							? creationContext.getProperty().getValue()
-							: creationContext.getPersistentClass().getIdentifierProperty().getValue(),
-					creationContext.getDatabase().getDialect(),
-					creationContext.getRootClass(),
-					properties::setProperty,
-					creationContext.getServiceRegistry().requireService( ConfigurationService.class )
-			);
-			if ( configExtractor != null ) {
-				configExtractor.accept( annotation, properties );
-			}
-			configurable.configure( creationContext, properties );
+			configureGenerator( annotation, configBaseline, configExtractor, creationContext, configurable );
 		}
 		if ( generator instanceof ExportableProducer exportableProducer ) {
 			exportableProducer.registerExportables( creationContext.getDatabase() );
@@ -310,6 +295,61 @@ public static  void prepareForUse(
 		}
 	}
 
+	private static  void configureGenerator(
+			A annotation,
+			Consumer configBaseline,
+			BiConsumer configExtractor,
+			GeneratorCreationContext creationContext,
+			Configurable configurable) {
+		final var properties = new Properties();
+		if ( configBaseline != null ) {
+			configBaseline.accept( properties );
+		}
+		collectBaselineProperties(
+				creationContext.getProperty() != null
+						? creationContext.getProperty().getValue()
+						: creationContext.getPersistentClass().getIdentifierProperty().getValue(),
+				creationContext.getDatabase().getDialect(),
+				creationContext.getRootClass(),
+				properties::setProperty,
+				creationContext.getServiceRegistry().requireService( ConfigurationService.class )
+		);
+		if ( configExtractor != null ) {
+			configExtractor.accept( annotation, properties );
+		}
+		configurable.configure( creationContext, properties );
+	}
+
+	public static  void initializeGenerator(
+			AnnotationBasedGenerator generator,
+			Annotation annotation,
+			GeneratorCreationContext creationContext) {
+		generator.initialize( castAnnotationType( annotation, generator), creationContext );
+	}
+
+	private static  A castAnnotationType(
+			Annotation typeAnnotation,
+			AnnotationBasedGenerator annotationBased) {
+		final var annotationType = annotationBased.getClass();
+		for ( var iface: annotationType.getGenericInterfaces() ) {
+			if ( iface instanceof ParameterizedType parameterizedType
+					&& parameterizedType.getRawType() == AnnotationBasedGenerator.class ) {
+				final var typeArguments = parameterizedType.getActualTypeArguments();
+				if ( typeArguments.length > 0
+						&& typeArguments[0] instanceof Class annotationClass ) {
+					if ( !annotationClass.isInstance( typeAnnotation ) ) {
+						throw new AnnotationException( String.format( "Annotation '%s' is not assignable to '%s'",
+								annotationType.getName(), iface.getTypeName() ) );
+					}
+					@SuppressWarnings("unchecked") // safe, we just checked it
+					final var castAnnotation = (A) typeAnnotation;
+					return castAnnotation;
+				}
+			}
+		}
+		throw new AssertionFailure( "Could not find implementing interface" );
+	}
+
 	public static void handleUuidStrategy(
 			SimpleValue idValue,
 			MemberDetails idMember,
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java
index 201abc49b146..6ec1a56eef3f 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java
@@ -59,6 +59,7 @@
 import static org.hibernate.boot.model.internal.AnnotationHelper.extractParameterMap;
 import static org.hibernate.boot.model.internal.BinderHelper.getPath;
 import static org.hibernate.boot.model.internal.BinderHelper.isGlobalGeneratorNameGlobal;
+import static org.hibernate.boot.model.internal.GeneratorAnnotationHelper.initializeGenerator;
 import static org.hibernate.boot.model.internal.GeneratorParameters.collectParameters;
 import static org.hibernate.boot.model.internal.GeneratorParameters.interpretSequenceGenerator;
 import static org.hibernate.boot.model.internal.GeneratorParameters.interpretTableGenerator;
@@ -423,7 +424,7 @@ private static Generator instantiateAndInitializeGenerator(
 				memberDetails,
 				annotationType
 		);
-		callInitialize( annotation, memberDetails, creationContext, generator );
+		callInitialize( annotation, creationContext, generator );
 		callConfigure( creationContext, generator, emptyMap(), value );
 		return generator;
 	}
@@ -566,15 +567,22 @@ private static  G instantiateGenerator(
 		try {
 			try {
 				return generatorClass.getConstructor( annotationType, Member.class, GeneratorCreationContext.class )
+						// support for deprecated signature (eventually remove)
 						.newInstance( annotation, memberDetails.toJavaMember(), creationContext);
 			}
 			catch (NoSuchMethodException ignore) {
 				try {
-					return generatorClass.getConstructor( annotationType )
-							.newInstance( annotation );
+					return generatorClass.getConstructor( annotationType, GeneratorCreationContext.class )
+							.newInstance( annotation, creationContext );
 				}
-				catch (NoSuchMethodException i) {
-					return instantiateGeneratorViaDefaultConstructor( generatorClass );
+				catch (NoSuchMethodException ignoreAgain) {
+					try {
+						return generatorClass.getConstructor( annotationType )
+								.newInstance( annotation );
+					}
+					catch (NoSuchMethodException i) {
+						return instantiateGeneratorViaDefaultConstructor( generatorClass );
+					}
 				}
 			}
 		}
@@ -611,18 +619,12 @@ private static  G instantiateGeneratorViaDefaultConstructor
 		}
 	}
 
-	public static  void callInitialize(
+	private static  void callInitialize(
 			A annotation,
-			MemberDetails memberDetails,
 			GeneratorCreationContext creationContext,
 			Generator generator) {
-		if ( generator instanceof AnnotationBasedGenerator ) {
-			// This will cause a CCE in case the generation type doesn't match the annotation type; As this would be
-			// a programming error of the generation type developer and thus should show up during testing, we don't
-			// check this explicitly; If required, this could be done e.g. using ClassMate
-			@SuppressWarnings("unchecked")
-			final var generation = (AnnotationBasedGenerator) generator;
-			generation.initialize( annotation, memberDetails.toJavaMember(), creationContext );
+		if ( generator instanceof AnnotationBasedGenerator annotationBasedGenerator ) {
+			initializeGenerator( annotationBasedGenerator, annotation, creationContext );
 		}
 	}
 
@@ -646,7 +648,7 @@ private static void checkVersionGenerationAlways(MemberDetails property, Generat
 	 * call its {@link Configurable#configure(GeneratorCreationContext, Properties)
 	 * configure()} method.
 	 */
-	public static void callConfigure(
+	private static void callConfigure(
 			GeneratorCreationContext creationContext,
 			Generator generator,
 			Map configuration,
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/InheritanceState.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/InheritanceState.java
index 4ed35b871378..0150cca6510a 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/InheritanceState.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/InheritanceState.java
@@ -223,7 +223,7 @@ public Boolean hasIdClassOrEmbeddedId() {
 	 * guessing from @Id or @EmbeddedId presence if not specified.
 	 * Change EntityBinder by side effect
 	 */
-	private ElementsToProcess getElementsToProcess() {
+	ElementsToProcess getElementsToProcess() {
 		if ( elementsToProcess == null ) {
 			final var inheritanceState = inheritanceStatePerClass.get( classDetails );
 			assert !inheritanceState.isEmbeddableSuperclass();
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorJpa.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorJpa.java
index 5e51ff06fcbb..852df030cc65 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorJpa.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorJpa.java
@@ -6,18 +6,18 @@
 
 import jakarta.persistence.NamedAttributeNode;
 import jakarta.persistence.NamedEntityGraph;
-import jakarta.persistence.NamedSubgraph;
+import org.checkerframework.checker.nullness.qual.NonNull;
 import org.hibernate.AnnotationException;
 import org.hibernate.boot.model.NamedGraphCreator;
 import org.hibernate.graph.internal.RootGraphImpl;
 import org.hibernate.graph.spi.AttributeNodeImplementor;
+import org.hibernate.graph.spi.GraphParserEntityClassResolver;
+import org.hibernate.graph.spi.GraphParserEntityNameResolver;
 import org.hibernate.graph.spi.GraphImplementor;
 import org.hibernate.graph.spi.RootGraphImplementor;
 import org.hibernate.graph.spi.SubGraphImplementor;
 import org.hibernate.metamodel.model.domain.EntityDomainType;
 
-import java.util.function.Function;
-
 import static org.hibernate.internal.util.StringHelper.isNotEmpty;
 import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
 
@@ -37,34 +37,32 @@ class NamedGraphCreatorJpa implements NamedGraphCreator {
 	}
 
 	@Override
-	public  RootGraphImplementor createEntityGraph(
-			Function, EntityDomainType> entityDomainClassResolver,
-			Function> entityDomainNameResolver) {
-		//noinspection unchecked
-		final EntityDomainType rootEntityType =
-				(EntityDomainType) entityDomainNameResolver.apply( jpaEntityName );
-		final RootGraphImplementor entityGraph =
-				createRootGraph( name, rootEntityType, annotation.includeAllAttributes() );
+	public RootGraphImplementor createEntityGraph(
+			GraphParserEntityClassResolver entityDomainClassResolver,
+			GraphParserEntityNameResolver entityDomainNameResolver) {
+		return createGraph( (EntityDomainType)
+				entityDomainNameResolver.resolveEntityName( jpaEntityName ) );
+	}
 
-		if ( annotation.subclassSubgraphs() != null ) {
-			for ( NamedSubgraph subclassSubgraph : annotation.subclassSubgraphs() ) {
-				final Class subgraphType = subclassSubgraph.type();
-				final Class graphJavaType = entityGraph.getGraphedType().getJavaType();
+	private  @NonNull RootGraphImplementor createGraph(EntityDomainType rootEntityType) {
+		final var entityGraph =
+				createRootGraph( name, rootEntityType, annotation.includeAllAttributes() );
+		final var subclassSubgraphs = annotation.subclassSubgraphs();
+		if ( subclassSubgraphs != null ) {
+			for ( var subclassSubgraph : subclassSubgraphs ) {
+				final var subgraphType = subclassSubgraph.type();
+				final var graphJavaType = entityGraph.getGraphedType().getJavaType();
 				if ( !graphJavaType.isAssignableFrom( subgraphType ) ) {
 					throw new AnnotationException( "Named subgraph type '" + subgraphType.getName()
-												+ "' is not a subtype of the graph type '" + graphJavaType.getName() + "'" );
+								+ "' is not a subtype of the graph type '" + graphJavaType.getName() + "'" );
 				}
-				@SuppressWarnings("unchecked") // Safe, because we just checked
-				final Class subtype = (Class) subgraphType;
-				final GraphImplementor subgraph = entityGraph.addTreatedSubgraph( subtype );
-				applyNamedAttributeNodes( subclassSubgraph.attributeNodes(), annotation, subgraph );
+				applyNamedAttributeNodes( subclassSubgraph.attributeNodes(), annotation,
+						entityGraph.addTreatedSubgraph( subgraphType.asSubclass( graphJavaType ) ) );
 			}
 		}
-
 		if ( annotation.attributeNodes() != null ) {
 			applyNamedAttributeNodes( annotation.attributeNodes(), annotation, entityGraph );
 		}
-
 		return entityGraph;
 	}
 
@@ -72,7 +70,7 @@ private static  RootGraphImplementor createRootGraph(
 			String name,
 			EntityDomainType rootEntityType,
 			boolean includeAllAttributes) {
-		final RootGraphImpl entityGraph = new RootGraphImpl<>( name, rootEntityType );
+		final var entityGraph = new RootGraphImpl<>( name, rootEntityType );
 		if ( includeAllAttributes ) {
 			for ( var attribute : rootEntityType.getAttributes() ) {
 				entityGraph.addAttributeNodes( attribute );
@@ -85,7 +83,7 @@ private void applyNamedAttributeNodes(
 			NamedAttributeNode[] namedAttributeNodes,
 			NamedEntityGraph namedEntityGraph,
 			GraphImplementor graphNode) {
-		for ( NamedAttributeNode namedAttributeNode : namedAttributeNodes ) {
+		for ( var namedAttributeNode : namedAttributeNodes ) {
 			final var attributeNode =
 					(AttributeNodeImplementor)
 							graphNode.addAttributeNode( namedAttributeNode.value() );
@@ -105,7 +103,7 @@ private  void applyNamedSubgraphs(
 			String subgraphName,
 			AttributeNodeImplementor attributeNode,
 			boolean isKeySubGraph) {
-		for ( NamedSubgraph namedSubgraph : namedEntityGraph.subgraphs() ) {
+		for ( var namedSubgraph : namedEntityGraph.subgraphs() ) {
 			if ( subgraphName.equals( namedSubgraph.name() ) ) {
 				applyNamedAttributeNodes( namedSubgraph.attributeNodes(), namedEntityGraph,
 						createSubgraph( attributeNode, isKeySubGraph, namedSubgraph.type() ) );
@@ -128,27 +126,27 @@ private static SubGraphImplementor createSubgraph(
 
 	private static  SubGraphImplementor makeAttributeNodeValueSubgraph(
 			AttributeNodeImplementor attributeNode, Class subgraphType) {
-		final Class attributeValueType =
-				attributeNode.getAttributeDescriptor().getValueGraphType().getJavaType();
+		final var attributeValueType =
+				attributeNode.getAttributeDescriptor()
+						.getValueGraphType().getJavaType();
 		if ( !attributeValueType.isAssignableFrom( subgraphType ) ) {
 			throw new AnnotationException( "Named subgraph type '" + subgraphType.getName()
-										+ "' is not a subtype of the value type '" + attributeValueType.getName() + "'" );
+						+ "' is not a subtype of the value type '" + attributeValueType.getName() + "'" );
 		}
-		@SuppressWarnings("unchecked") // Safe, because we just checked
-		final Class castType = (Class) subgraphType;
-		return attributeNode.addValueSubgraph().addTreatedSubgraph( castType );
+		return attributeNode.addValueSubgraph().addTreatedSubgraph(
+				subgraphType.asSubclass( attributeNode.getValueSubgraph().getClassType() ) );
 	}
 
 	private static  SubGraphImplementor makeAttributeNodeKeySubgraph(
 			AttributeNodeImplementor attributeNode, Class subgraphType) {
-		final Class attributeKeyType =
-				attributeNode.getAttributeDescriptor().getKeyGraphType().getJavaType();
+		final var attributeKeyType =
+				attributeNode.getAttributeDescriptor()
+						.getKeyGraphType().getJavaType();
 		if ( !attributeKeyType.isAssignableFrom( subgraphType ) ) {
 			throw new AnnotationException( "Named subgraph type '" + subgraphType.getName()
-										+ "' is not a subtype of the key type '" + attributeKeyType.getName() + "'" );
+						+ "' is not a subtype of the key type '" + attributeKeyType.getName() + "'" );
 		}
-		@SuppressWarnings("unchecked") // Safe, because we just checked
-		final Class castType = (Class) subgraphType;
-		return attributeNode.addKeySubgraph().addTreatedSubgraph( castType );
+		return attributeNode.addKeySubgraph().addTreatedSubgraph(
+				subgraphType.asSubclass( attributeNode.getKeySubgraph().getClassType() ) );
 	}
 }
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorParsed.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorParsed.java
index a264f3521fd2..01b29907259b 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorParsed.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorParsed.java
@@ -4,22 +4,20 @@
  */
 package org.hibernate.boot.model.internal;
 
-import org.antlr.v4.runtime.CharStreams;
-import org.antlr.v4.runtime.CommonTokenStream;
+import org.checkerframework.checker.nullness.qual.NonNull;
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.hibernate.UnknownEntityTypeException;
 import org.hibernate.annotations.NamedEntityGraph;
 import org.hibernate.boot.model.NamedGraphCreator;
-import org.hibernate.grammars.graph.GraphLanguageLexer;
 import org.hibernate.grammars.graph.GraphLanguageParser;
 import org.hibernate.graph.InvalidGraphException;
-import org.hibernate.graph.internal.parse.EntityNameResolver;
+import org.hibernate.graph.spi.GraphParserEntityClassResolver;
+import org.hibernate.graph.spi.GraphParserEntityNameResolver;
 import org.hibernate.graph.internal.parse.GraphParsing;
 import org.hibernate.graph.spi.RootGraphImplementor;
 import org.hibernate.metamodel.model.domain.EntityDomainType;
 
-import java.util.function.Function;
-
+import static org.hibernate.graph.internal.parse.GraphParsing.parseText;
 import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
 
 /**
@@ -41,43 +39,49 @@ class NamedGraphCreatorParsed implements NamedGraphCreator {
 	}
 
 	@Override
-	public  RootGraphImplementor createEntityGraph(
-			Function, EntityDomainType> entityDomainClassResolver,
-			Function> entityDomainNameResolver) {
-		final GraphLanguageLexer lexer = new GraphLanguageLexer( CharStreams.fromString( annotation.graph() ) );
-		final GraphLanguageParser parser = new GraphLanguageParser( new CommonTokenStream( lexer ) );
-		final GraphLanguageParser.GraphContext graphContext = parser.graph();
-
-		final EntityNameResolver entityNameResolver = new EntityNameResolver() {
-			@Override
-			public  EntityDomainType resolveEntityName(String entityName) {
-				//noinspection unchecked
-				final EntityDomainType entityDomainType = (EntityDomainType) entityDomainNameResolver.apply( entityName );
-				if ( entityDomainType != null ) {
-					return entityDomainType;
-				}
-				throw new UnknownEntityTypeException( entityName );
-			}
-		};
-
+	public RootGraphImplementor createEntityGraph(
+			GraphParserEntityClassResolver entityDomainClassResolver,
+			GraphParserEntityNameResolver entityDomainNameResolver) {
+		final var graphContext = parseText( annotation.graph() );
+		final var typeIndicator = graphContext.typeIndicator();
+		final EntityDomainType entityDomainType;
+		final String jpaEntityName;
 		if ( entityType == null ) {
-			if ( graphContext.typeIndicator() == null ) {
-				throw new InvalidGraphException( "Expecting graph text to include an entity name : " + annotation.graph() );
+			if ( typeIndicator == null ) {
+				throw new InvalidGraphException( "Expecting graph text to include an entity name: " + annotation.graph() );
 			}
-			final String jpaEntityName = graphContext.typeIndicator().TYPE_NAME().toString();
-			//noinspection unchecked
-			final EntityDomainType entityDomainType = (EntityDomainType) entityDomainNameResolver.apply( jpaEntityName );
-			final String name = this.name == null ? jpaEntityName : this.name;
-			return GraphParsing.parse( name, entityDomainType, graphContext.attributeList(), entityNameResolver );
+			jpaEntityName = typeIndicator.TYPE_NAME().toString();
+			entityDomainType = entityDomainNameResolver.resolveEntityName( jpaEntityName );
 		}
 		else {
-			if ( graphContext.typeIndicator() != null ) {
-				throw new InvalidGraphException( "Expecting graph text to not include an entity name : " + annotation.graph() );
+			if ( typeIndicator != null ) {
+				throw new InvalidGraphException( "Expecting graph text to not include an entity name: " + annotation.graph() );
 			}
-			//noinspection unchecked
-			final EntityDomainType entityDomainType = (EntityDomainType) entityDomainClassResolver.apply( (Class) entityType );
-			final String name = this.name == null ? entityDomainType.getName() : this.name;
-			return GraphParsing.parse( name, entityDomainType, graphContext.attributeList(), entityNameResolver );
+			entityDomainType = entityDomainClassResolver.resolveEntityClass( entityType );
+			jpaEntityName = entityDomainType.getName();
+		}
+		return visit( name == null ? jpaEntityName : name,
+				entityDomainType, entityDomainNameResolver, graphContext );
+	}
+
+	private static @NonNull RootGraphImplementor visit(
+			String name,
+			EntityDomainType entityDomainType, GraphParserEntityNameResolver entityDomainNameResolver,
+			GraphLanguageParser.GraphContext graphContext) {
+		return GraphParsing.visit( name, entityDomainType, graphContext.attributeList(),
+				entityName -> resolve( entityName, entityDomainNameResolver ) );
+	}
+
+	private static @NonNull EntityDomainType resolve(
+			String entityName, GraphParserEntityNameResolver entityDomainNameResolver) {
+		final var entityDomainType =
+				(EntityDomainType)
+						entityDomainNameResolver.resolveEntityName( entityName );
+		if ( entityDomainType == null ) {
+			throw new UnknownEntityTypeException( entityName );
+		}
+		else {
+			return entityDomainType;
 		}
 	}
 }
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NaturalIdBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NaturalIdBinder.java
index 626c1d6d4081..98d5f8a5d6ab 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NaturalIdBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NaturalIdBinder.java
@@ -53,7 +53,7 @@ private static Identifier uniqueKeyName(MetadataBuildingContext context, Annotat
 
 	private static void addColumnsToUniqueKey(AnnotatedColumns columns, Identifier name) {
 		final var collector = columns.getBuildingContext().getMetadataCollector();
-		final Table table = columns.getTable();
+		final var table = columns.getTable();
 		final var uniqueKey = table.getOrCreateUniqueKey( name.render( collector.getDatabase().getDialect() ) );
 		final var property = columns.resolveProperty();
 		if ( property.isComposite() ) {
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java
index e9c32b3564cc..dc3084f43870 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java
@@ -109,6 +109,8 @@ public class PropertyBinder {
 
 	private String name;
 	private String returnedClassName;
+	private boolean generic;
+	private boolean genericSpecialization;
 	private boolean lazy;
 	private String lazyGroup;
 	private AccessType accessType;
@@ -166,6 +168,14 @@ private void setReturnedClassName(String returnedClassName) {
 		this.returnedClassName = returnedClassName;
 	}
 
+	public void setGeneric(boolean generic) {
+		this.generic = generic;
+	}
+
+	public void setGenericSpecialization(boolean genericSpecialization) {
+		this.genericSpecialization = genericSpecialization;
+	}
+
 	public void setLazy(boolean lazy) {
 		this.lazy = lazy;
 	}
@@ -441,6 +451,8 @@ public Property makeProperty() {
 		property.setCascade( cascadeTypes );
 		property.setPropertyAccessorName( accessType.getType() );
 		property.setReturnedClassName( returnedClassName );
+		property.setGeneric( generic );
+		property.setGenericSpecialization( genericSpecialization );
 //		property.setPropertyAccessStrategy( propertyAccessStrategy );
 		handleValueGeneration( property );
 		handleNaturalId( property );
@@ -1002,6 +1014,11 @@ private AnnotatedColumns bindBasicOrComposite(
 			ClassDetails returnedClass) {
 		final var memberDetails = inferredData.getAttributeMember();
 
+		if ( propertyHolder.isEntity() && propertyHolder.getPersistentClass().isAbstract() ) {
+			// When the type of the member is a type variable, we mark it as generic for abstract classes
+			setGeneric( inferredData.getClassOrElementType().getTypeKind() == TypeDetails.Kind.TYPE_VARIABLE );
+		}
+
 		// overrides from @MapsId or @IdClass if needed
 		final PropertyData overridingProperty =
 				overridingProperty( propertyHolder, isIdentifierMapper, memberDetails );
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyHolder.java
index 877648c46934..9c701c96e5eb 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyHolder.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyHolder.java
@@ -61,12 +61,14 @@ public interface PropertyHolder {
 	String getPath();
 
 	/**
-	 * return null if the column is not overridden, or an array of column if true
+	 * return null if the column is not overridden,
+	 * or an array of columns if it is
 	 */
 	Column[] getOverriddenColumn(String propertyName);
 
 	/**
-	 * return null if the column is not overridden, or an array of column if true
+	 * return null if the column is not overridden,
+	 * or an array of columns if it is
 	 */
 	JoinColumn[] getOverriddenJoinColumn(String propertyName);
 
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/SoftDeleteHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/SoftDeleteHelper.java
index 9a67d40f0bff..7d1f25e30b77 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/SoftDeleteHelper.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/SoftDeleteHelper.java
@@ -70,11 +70,10 @@ private static BasicValue createSoftDeleteIndicatorValue(
 		}
 		else {
 			final ConverterDescriptor converterDescriptor =
-					ConverterDescriptors.of( softDeleteConfig.converter(),
-							context.getBootstrapContext().getClassmateContext() );
+					ConverterDescriptors.of( softDeleteConfig.converter() );
 			softDeleteIndicatorValue.setJpaAttributeConverterDescriptor( converterDescriptor );
 			softDeleteIndicatorValue.setImplicitJavaTypeAccess(
-					typeConfiguration -> converterDescriptor.getRelationalValueResolvedType().getErasedType()
+					typeConfiguration -> converterDescriptor.getRelationalValueResolvedType()
 			);
 		}
 
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java
index b62c3d5e4542..0a7609eaad38 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java
@@ -542,7 +542,8 @@ private static Table addTable(
 					logicalName.render(),
 					subselect,
 					isAbstract,
-					buildingContext
+					buildingContext,
+					logicalName.isExplicit()
 			);
 		}
 	}
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java
index c4d105c34f5f..4c753d9ce1b1 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java
@@ -23,6 +23,7 @@
 public class Identifier implements Comparable {
 	private final String text;
 	private final boolean isQuoted;
+	private final boolean isExplicit;
 
 	/**
 	 * Means to generate an {@link Identifier} instance from its simple text form.
@@ -62,7 +63,7 @@ public static Identifier toIdentifier(String text) {
 	 * @return The identifier form, or {@code null} if text was {@code null}
 	 */
 	public static Identifier toIdentifier(String text, boolean quote) {
-		return toIdentifier( text, quote, true );
+		return toIdentifier( text, quote, true, false );
 	}
 
 	/**
@@ -81,6 +82,25 @@ public static Identifier toIdentifier(String text, boolean quote) {
 	 * @return The identifier form, or {@code null} if text was {@code null}
 	 */
 	public static Identifier toIdentifier(String text, boolean quote, boolean autoquote) {
+		return toIdentifier( text, quote, autoquote, false );
+	}
+
+	/**
+	 * Means to generate an {@link Identifier} instance from its simple text form.
+	 * 

+ * If passed {@code text} is {@code null}, {@code null} is returned. + *

+ * If passed {@code text} is surrounded in quote markers, the returned Identifier + * is considered quoted. Quote markers include back-ticks (`), double-quotes ("), + * and brackets ([ and ]). + * + * @param text The text form + * @param quote Whether to quote unquoted text forms + * @param autoquote Whether to quote the result if it contains special characters + * @param isExplicit Whether the name is explicitly set + * @return The identifier form, or {@code null} if text was {@code null} + */ + public static Identifier toIdentifier(String text, boolean quote, boolean autoquote, boolean isExplicit) { if ( isBlank( text ) ) { return null; } @@ -106,7 +126,7 @@ public static Identifier toIdentifier(String text, boolean quote, boolean autoqu else if ( autoquote && !quote ) { quote = autoquote( text, start, end ); } - return new Identifier( text.substring( start, end ), quote ); + return new Identifier( text.substring( start, end ), quote, isExplicit ); } private static boolean autoquote(String text, int start, int end) { @@ -184,6 +204,17 @@ public static String unQuote(String name) { * @param quoted Is this a quoted identifier? */ public Identifier(String text, boolean quoted) { + this( text, quoted, false ); + } + + /** + * Constructs an identifier instance. + * + * @param text The identifier text. + * @param quoted Is this a quoted identifier? + * @param isExplicit Whether the name is explicitly set + */ + public Identifier(String text, boolean quoted, boolean isExplicit) { if ( isEmpty( text ) ) { throw new IllegalIdentifierException( "Identifier text cannot be null" ); } @@ -192,6 +223,7 @@ public Identifier(String text, boolean quoted) { } this.text = text; this.isQuoted = quoted; + this.isExplicit = isExplicit; } /** @@ -202,6 +234,7 @@ public Identifier(String text, boolean quoted) { protected Identifier(String text) { this.text = text; this.isQuoted = false; + this.isExplicit = false; } /** @@ -295,4 +328,8 @@ public static boolean areEqual(Identifier id1, Identifier id2) { public static Identifier quote(Identifier identifier) { return identifier.quoted(); } + + public boolean isExplicit() { + return isExplicit; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java index 11bc77e5d211..4eb081da0e0b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java @@ -11,6 +11,7 @@ import java.util.function.Function; import java.util.function.Supplier; +import org.checkerframework.checker.nullness.qual.NonNull; import org.hibernate.MappingException; import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.MetadataBuildingContext; @@ -45,6 +46,7 @@ import static org.hibernate.type.SqlTypes.SMALLINT; import static org.hibernate.type.descriptor.java.JavaTypeHelper.isTemporal; +import static org.hibernate.type.descriptor.java.TemporalJavaType.resolveJdbcTypeCode; /** * BasicValue.Resolution resolver for cases where no explicit @@ -68,7 +70,7 @@ public static BasicValue.Resolution from( final var typeConfiguration = bootstrapContext.getTypeConfiguration(); final var basicTypeRegistry = typeConfiguration.getBasicTypeRegistry(); - final var reflectedJtd = reflectedJtdResolver.get(); + final JavaType reflectedJtd; // NOTE: the distinction that is made below wrt `explicitJavaType` and `reflectedJtd` // is needed temporarily to trigger "legacy resolution" versus "ORM6 resolution. @@ -110,7 +112,7 @@ else if ( explicitJdbcType != null ) { } } } - else if ( reflectedJtd != null ) { + else if ( ( reflectedJtd = reflectedJtdResolver.get() ) != null ) { // we were able to determine the "reflected java-type" // Use JTD if we know it to apply any specialized resolutions if ( reflectedJtd instanceof EnumJavaType enumJavaType ) { @@ -150,29 +152,14 @@ else if ( explicitJdbcType != null ) { if ( registeredType != null ) { // so here is the legacy resolution - jdbcMapping = resolveSqlTypeIndicators( stdIndicators, registeredType, reflectedJtd ); + jdbcMapping = resolveSqlTypeIndicators( stdIndicators, registeredType, registeredType.getJavaTypeDescriptor() ); } else { // there was not a "legacy" BasicType registration, // so use `JavaType#getRecommendedJdbcType`, if one, // to create a mapping - final JdbcType recommendedJdbcType; - try { - recommendedJdbcType = reflectedJtd.getRecommendedJdbcType( stdIndicators ); - } - catch (JdbcTypeRecommendationException jtre) { - if ( buildingContext.getMetadataCollector() - .getEntityBindingMap().values().stream() - .anyMatch( pc -> pc.getMappedClass().equals(resolvedJavaType) ) ) { - throw new MappingException( "Incorrect use of entity type '" - + resolvedJavaType.getTypeName() - + "' (possibly due to missing association mapping annotation)", - jtre ); - } - else { - throw jtre; - } - } + final JdbcType recommendedJdbcType = + recommendedJdbcType( resolvedJavaType, stdIndicators, buildingContext, reflectedJtd ); if ( recommendedJdbcType != null ) { jdbcMapping = resolveSqlTypeIndicators( stdIndicators, @@ -194,7 +181,7 @@ else if ( reflectedJtd instanceof SerializableJavaType else { if ( explicitJdbcType != null ) { // we have an explicit STD, but no JTD - infer JTD - // NOTE : yes it's an odd case, but easy to implement here, so... + // NOTE: yes it's an odd case, but easy to implement here, so... Integer length = null; Integer scale = null; if ( selectable instanceof Column column ) { @@ -212,8 +199,10 @@ else if ( column.getLength() != null ) { } } - final JavaType recommendedJtd = - explicitJdbcType.getJdbcRecommendedJavaTypeMapping( length, scale, typeConfiguration ); + final var recommendedJavaType = + explicitJdbcType.getRecommendedJavaType( length, scale, typeConfiguration ); + // TODO: check this type cast + final var recommendedJtd = (JavaType) recommendedJavaType; jdbcMapping = resolveSqlTypeIndicators( stdIndicators, basicTypeRegistry.resolve( recommendedJtd, explicitJdbcType ), @@ -252,6 +241,27 @@ else if ( column.getLength() != null ) { ); } + private static JdbcType recommendedJdbcType( + Type resolvedJavaType, + JdbcTypeIndicators stdIndicators, + MetadataBuildingContext buildingContext, + JavaType reflectedJtd) { + try { + return reflectedJtd.getRecommendedJdbcType( stdIndicators ); + } + catch (JdbcTypeRecommendationException jtre) { + if ( buildingContext.getMetadataCollector() + .getEntityBindingMap().values().stream() + .anyMatch( pc -> pc.getMappedClass().equals( resolvedJavaType ) ) ) { + throw new MappingException( "Incorrect use of entity type '" + resolvedJavaType.getTypeName() + + "' (possibly due to missing association mapping annotation)", jtre ); + } + else { + throw jtre; + } + } + } + private static BasicType registeredType( JdbcType explicitJdbcType, Function> explicitMutabilityPlanAccess, @@ -309,7 +319,11 @@ private static BasicType pluralBasicType( pluralJavaType.resolveType( bootstrapContext.getTypeConfiguration(), dialect, - resolveSqlTypeIndicators( stdIndicators, registeredElementType, elementJavaType ), + resolveSqlTypeIndicators( + stdIndicators, + registeredElementType, + registeredElementType.getJavaTypeDescriptor() + ), selectable instanceof ColumnTypeInformation information ? information : null, stdIndicators ); @@ -483,43 +497,16 @@ public static BasicValue.Resolution fromTemporal( JdbcType explicitJdbcType, Function> explicitMutabilityPlanAccess, JdbcTypeIndicators stdIndicators) { - final var typeConfiguration = stdIndicators.getTypeConfiguration(); - final var basicTypeRegistry = typeConfiguration.getBasicTypeRegistry(); - final var requestedTemporalPrecision = stdIndicators.getTemporalPrecision(); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Case #1 - explicit JavaType if ( explicitJavaType != null ) { - if ( !isTemporal( explicitJavaType ) ) { - throw new MappingException( - "Explicit JavaType [" + explicitJavaType + - "] defined for temporal value must implement TemporalJavaType" - ); - } - - @SuppressWarnings("unchecked") - final var explicitTemporalJtd = (TemporalJavaType) explicitJavaType; - if ( requestedTemporalPrecision != null && explicitTemporalJtd.getPrecision() != requestedTemporalPrecision ) { - throw new MappingException( - "Temporal precision (`jakarta.persistence.TemporalType`) mismatch... requested precision = " + requestedTemporalPrecision + - "; explicit JavaType (`" + explicitTemporalJtd + "`) precision = " + explicitTemporalJtd.getPrecision() - - ); - } - - final var jdbcType = - explicitJdbcType != null - ? explicitJdbcType - : explicitTemporalJtd.getRecommendedJdbcType( stdIndicators ); - final var jdbcMapping = basicTypeRegistry.resolve( explicitTemporalJtd, jdbcType ); - return new InferredBasicValueResolution<>( - jdbcMapping, - explicitTemporalJtd, - explicitTemporalJtd, - jdbcType, - jdbcMapping, - determineMutabilityPlan( explicitMutabilityPlanAccess, explicitTemporalJtd, typeConfiguration ) + return fromTemporalExplicitJavaType( + explicitJavaType, + explicitJdbcType, + explicitMutabilityPlanAccess, + stdIndicators ); } @@ -530,19 +517,10 @@ public static BasicValue.Resolution fromTemporal( // due to the new annotations being used if ( explicitJdbcType != null ) { - final TemporalJavaType temporalJavaType = - requestedTemporalPrecision != null - ? reflectedJtd.resolveTypeForPrecision( requestedTemporalPrecision, typeConfiguration ) - // Avoid using the DateJavaType and prefer the JdbcTimestampJavaType - : reflectedJtd.resolveTypeForPrecision( reflectedJtd.getPrecision(), typeConfiguration ); - final BasicType jdbcMapping = basicTypeRegistry.resolve( temporalJavaType, explicitJdbcType ); - return new InferredBasicValueResolution<>( - jdbcMapping, - temporalJavaType, - temporalJavaType, + return fromTemporalExplicitJdbcType( + reflectedJtd, explicitJdbcType, - jdbcMapping, - temporalJavaType.getMutabilityPlan() + stdIndicators ); } @@ -552,17 +530,31 @@ public static BasicValue.Resolution fromTemporal( // - for the moment continue to use the legacy resolution to registered // BasicType + return fromTemporalImplicit( + reflectedJtd, + explicitMutabilityPlanAccess, + stdIndicators + ); + } + + private static @NonNull InferredBasicValueResolution fromTemporalImplicit( + TemporalJavaType reflectedJtd, + Function> explicitMutabilityPlanAccess, + JdbcTypeIndicators stdIndicators) { + final var typeConfiguration = stdIndicators.getTypeConfiguration(); + final var basicTypeRegistry = typeConfiguration.getBasicTypeRegistry(); + final var requestedTemporalPrecision = stdIndicators.getTemporalPrecision(); + final BasicType basicType; if ( requestedTemporalPrecision != null && requestedTemporalPrecision != reflectedJtd.getPrecision() ) { basicType = basicTypeRegistry.resolve( reflectedJtd.resolveTypeForPrecision( requestedTemporalPrecision, typeConfiguration ), - TemporalJavaType.resolveJdbcTypeCode( requestedTemporalPrecision ) + resolveJdbcTypeCode( requestedTemporalPrecision ) ); } else { basicType = basicTypeRegistry.resolve( - // Avoid using the DateJavaType and prefer the JdbcTimestampJavaType reflectedJtd.resolveTypeForPrecision( reflectedJtd.getPrecision(), typeConfiguration ), reflectedJtd.getRecommendedJdbcType( stdIndicators ) ); @@ -578,6 +570,69 @@ public static BasicValue.Resolution fromTemporal( ); } + private static @NonNull InferredBasicValueResolution fromTemporalExplicitJdbcType( + TemporalJavaType reflectedJtd, + JdbcType explicitJdbcType, + JdbcTypeIndicators stdIndicators) { + final var typeConfiguration = stdIndicators.getTypeConfiguration(); + final var basicTypeRegistry = typeConfiguration.getBasicTypeRegistry(); + final var requestedTemporalPrecision = stdIndicators.getTemporalPrecision(); + + final var temporalJavaType = + requestedTemporalPrecision != null + ? reflectedJtd.resolveTypeForPrecision( requestedTemporalPrecision, typeConfiguration ) + : reflectedJtd.resolveTypeForPrecision( reflectedJtd.getPrecision(), typeConfiguration ); + final var jdbcMapping = basicTypeRegistry.resolve( temporalJavaType, explicitJdbcType ); + return new InferredBasicValueResolution<>( + jdbcMapping, + temporalJavaType, + temporalJavaType, + explicitJdbcType, + jdbcMapping, + temporalJavaType.getMutabilityPlan() + ); + } + + private static @NonNull InferredBasicValueResolution fromTemporalExplicitJavaType( + BasicJavaType explicitJavaType, + JdbcType explicitJdbcType, + Function> explicitMutabilityPlanAccess, + JdbcTypeIndicators stdIndicators) { + final var typeConfiguration = stdIndicators.getTypeConfiguration(); + final var basicTypeRegistry = typeConfiguration.getBasicTypeRegistry(); + final var requestedTemporalPrecision = stdIndicators.getTemporalPrecision(); + + if ( !isTemporal( explicitJavaType ) ) { + throw new MappingException( "Explicit JavaType [" + explicitJavaType + + "] defined for temporal value must implement TemporalJavaType" ); + } + + @SuppressWarnings("unchecked") + final var explicitTemporalJtd = (TemporalJavaType) explicitJavaType; + if ( requestedTemporalPrecision != null + && explicitTemporalJtd.getPrecision() != requestedTemporalPrecision ) { + throw new MappingException( + "Temporal precision (TemporalType) mismatch; requested precision = %s; explicit JavaType (%s) precision = %s" + .formatted( requestedTemporalPrecision, explicitTemporalJtd, explicitTemporalJtd.getPrecision() ) + + ); + } + + final var jdbcType = + explicitJdbcType != null + ? explicitJdbcType + : explicitTemporalJtd.getRecommendedJdbcType( stdIndicators ); + final var jdbcMapping = basicTypeRegistry.resolve( explicitTemporalJtd, jdbcType ); + return new InferredBasicValueResolution<>( + jdbcMapping, + explicitTemporalJtd, + explicitTemporalJtd, + jdbcType, + jdbcMapping, + determineMutabilityPlan( explicitMutabilityPlanAccess, explicitTemporalJtd, typeConfiguration ) + ); + } + private static MutabilityPlan determineMutabilityPlan( Function> explicitMutabilityPlanAccess, JavaType javaType, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/NamedConverterResolution.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/NamedConverterResolution.java index 9884e6441f40..b48be9cb9bf2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/NamedConverterResolution.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/NamedConverterResolution.java @@ -66,13 +66,10 @@ public static NamedConverterResolution from( MetadataBuildingContext context) { assert name.startsWith( ConverterDescriptor.TYPE_NAME_PREFIX ); final String converterClassName = name.substring( ConverterDescriptor.TYPE_NAME_PREFIX.length() ); - final var bootstrapContext = context.getBootstrapContext(); final Class> converterClass = bootstrapContext.getClassLoaderService().classForName( converterClassName ); - final ConverterDescriptor converterDescriptor = - ConverterDescriptors.of( converterClass, bootstrapContext.getClassmateContext() ); - + final var converterDescriptor = ConverterDescriptors.of( converterClass ); return fromInternal( explicitJtdAccess, explicitStdAccess, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ScanningCoordinator.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ScanningCoordinator.java index a6bc6eae4043..35a390369be4 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ScanningCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ScanningCoordinator.java @@ -261,8 +261,7 @@ public void applyScanResultsToManagedResources( // converter classes are safe to load because we never enhance them, // and notice we use the ClassLoaderService specifically, not the temp ClassLoader (if any) managedResources.addAttributeConverterDefinition( - ConverterDescriptors.of( classLoaderService.classForName( classDescriptor.getName() ), - bootstrapContext.getClassmateContext() ) + ConverterDescriptors.of( classLoaderService.classForName( classDescriptor.getName() ) ) ); } else if ( classDescriptor.getCategorization() == ClassDescriptor.Categorization.MODEL ) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/VersionResolution.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/VersionResolution.java index d4250858d261..66cd05b562d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/VersionResolution.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/VersionResolution.java @@ -28,21 +28,21 @@ */ public class VersionResolution implements BasicValue.Resolution { - // todo (6.0) : support explicit JTD? - // todo (6.0) : support explicit STD? - - public static VersionResolution from( + public static VersionResolution from( Function implicitJavaTypeAccess, TimeZoneStorageType timeZoneStorageType, MetadataBuildingContext context) { - - // todo (6.0) : add support for Dialect-specific interpretation? - final var typeConfiguration = context.getBootstrapContext().getTypeConfiguration(); final var implicitJavaType = implicitJavaTypeAccess.apply( typeConfiguration ); - final JavaType registered = typeConfiguration.getJavaTypeRegistry().getDescriptor( implicitJavaType ); - final var basicJavaType = (BasicJavaType) registered; + final var registered = typeConfiguration.getJavaTypeRegistry().resolveDescriptor( implicitJavaType ); + return resolve( timeZoneStorageType, context, (BasicJavaType) registered ); + } + private static VersionResolution resolve( + TimeZoneStorageType timeZoneStorageType, + MetadataBuildingContext context, + BasicJavaType basicJavaType) { + final var typeConfiguration = context.getBootstrapContext().getTypeConfiguration(); final var recommendedJdbcType = basicJavaType.getRecommendedJdbcType( new JdbcTypeIndicators() { @Override @@ -50,7 +50,7 @@ public TypeConfiguration getTypeConfiguration() { return typeConfiguration; } - @Override + @Override @SuppressWarnings("deprecation") public TemporalType getTemporalPrecision() { // if it is a temporal version, it needs to be a TIMESTAMP return TemporalType.TIMESTAMP; @@ -106,7 +106,6 @@ public Dialect getDialect() { final var basicTypeRegistry = typeConfiguration.getBasicTypeRegistry(); final var basicType = basicTypeRegistry.resolve( basicJavaType, recommendedJdbcType ); final var legacyType = basicTypeRegistry.getRegisteredType( basicJavaType.getJavaTypeClass() ); - assert legacyType.getJdbcType().getDefaultSqlTypeCode() == recommendedJdbcType.getDefaultSqlTypeCode(); return new VersionResolution<>( basicJavaType, recommendedJdbcType, basicType, legacyType ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java index af8a90295a7a..a8b3a9443efe 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java @@ -762,7 +762,7 @@ public void contributeType(CompositeUserType type) { } final int preferredSqlTypeCodeForDuration = getPreferredSqlTypeCodeForDuration( serviceRegistry ); - if ( preferredSqlTypeCodeForDuration != SqlTypes.INTERVAL_SECOND ) { + if ( preferredSqlTypeCodeForDuration != SqlTypes.DURATION ) { adaptToPreferredSqlTypeCode( typeConfiguration, jdbcTypeRegistry, @@ -772,9 +772,6 @@ public void contributeType(CompositeUserType type) { "org.hibernate.type.DurationType" ); } - else { - addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INTERVAL_SECOND, SqlTypes.DURATION ); - } addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INET, SqlTypes.VARBINARY ); addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.GEOMETRY, SqlTypes.VARBINARY ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Database.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Database.java index 23ba3cf59a64..c6e66ef366ee 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Database.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Database.java @@ -103,7 +103,24 @@ public JdbcEnvironment getJdbcEnvironment() { * @return The wrapped Identifier form */ public Identifier toIdentifier(String text) { - return text == null ? null : jdbcEnvironment.getIdentifierHelper().toIdentifier( text ); + return toIdentifier( text, false ); + } + + /** + * Wrap the raw name of a database object in its Identifier form accounting + * for quoting from any of: + *

    + *
  • explicit quoting in the name itself
  • + *
  • global request to quote all identifiers
  • + *
+ * + * @param text The raw object name + * @param isExplicit Whether the name is explicitly set + * @return The wrapped Identifier form + * @implNote Quoting from database keywords happens only when building physical identifiers. + */ + public Identifier toIdentifier(String text, boolean isExplicit) { + return text == null ? null : jdbcEnvironment.getIdentifierHelper().toIdentifier( text, false, isExplicit ); } public PhysicalNamingStrategy getPhysicalNamingStrategy() { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Namespace.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Namespace.java index 9df90bc003bf..ccf36577a86c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Namespace.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Namespace.java @@ -95,9 +95,9 @@ public void registerTable(Identifier logicalName, Table table) { final Table previous = tables.put( logicalName, table ); if ( previous != null ) { BOOT_LOGGER.replacingTableRegistration( - String.valueOf(logicalName), - String.valueOf(previous), - String.valueOf(table) + String.valueOf( logicalName ), + String.valueOf( previous ), + String.valueOf( table ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedNameParser.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedNameParser.java index b2b735e495e7..b9c940568ba0 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedNameParser.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedNameParser.java @@ -110,7 +110,7 @@ public NameParts parse(String text, Identifier defaultCatalog, Identifier defaul if ( isQuotedInEntirety( text ) ) { return new NameParts( defaultCatalog, defaultSchema, - Identifier.toIdentifier( unquote( text ), true ) ); + Identifier.toIdentifier( unquote( text ), true, true, true ) ); } String catalogName = null; @@ -169,7 +169,7 @@ else if ( defaultCatalog != null ) { return new NameParts( Identifier.toIdentifier( catalogName, catalogWasQuoted ), Identifier.toIdentifier( schemaName, schemaWasQuoted ), - Identifier.toIdentifier( name, nameWasQuoted ) + Identifier.toIdentifier( name, nameWasQuoted, true, true ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AnnotationMetadataSourceProcessorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AnnotationMetadataSourceProcessorImpl.java index 5257460098c5..5bb023ee7849 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AnnotationMetadataSourceProcessorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AnnotationMetadataSourceProcessorImpl.java @@ -74,15 +74,13 @@ public AnnotationMetadataSourceProcessorImpl( ? void.class : explicitDomainType, registration.getConverterType(), - registration.isAutoApply(), - rootMetadataBuildingContext + registration.isAutoApply() ) ); } ); domainModelSource.getConverterRegistrations().forEach( (registration) -> { converterRegistry.addAttributeConverter( ConverterDescriptors.of( classLoaderService.classForName( registration.converterClass().getClassName() ), - registration.autoApply(), false, - bootstrapContext.getClassmateContext() + registration.autoApply(), false ) ); } ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/AbstractEntitySourceImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/AbstractEntitySourceImpl.java index b8cc59f58235..2c2addd58c71 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/AbstractEntitySourceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/AbstractEntitySourceImpl.java @@ -41,6 +41,8 @@ import org.hibernate.internal.util.collections.CollectionHelper; import static java.util.Collections.emptyMap; +import static org.hibernate.internal.util.StringHelper.isNotEmpty; +import static org.hibernate.internal.util.StringHelper.unqualify; /** * @author Steve Ebersole @@ -97,19 +99,20 @@ protected AbstractEntitySourceImpl(MappingDocument sourceMappingDocument, JaxbHb ); } - public static EntityNamingSourceImpl extractEntityNamingSource( + static EntityNamingSourceImpl extractEntityNamingSource( MappingDocument sourceMappingDocument, EntityInfo jaxbEntityMapping) { final String className = sourceMappingDocument.qualifyClassName( jaxbEntityMapping.getName() ); + final String mappingEntityName = jaxbEntityMapping.getEntityName(); final String entityName; final String jpaEntityName; - if ( StringHelper.isNotEmpty( jaxbEntityMapping.getEntityName() ) ) { - entityName = jaxbEntityMapping.getEntityName(); - jpaEntityName = jaxbEntityMapping.getEntityName(); + if ( isNotEmpty( mappingEntityName ) ) { + entityName = mappingEntityName; + jpaEntityName = mappingEntityName; } else { entityName = className; - jpaEntityName = StringHelper.unqualify( className ); + jpaEntityName = unqualify( className ); } return new EntityNamingSourceImpl( entityName, className, jpaEntityName ); } @@ -122,7 +125,7 @@ private FilterSource[] buildFilterSources() { return NO_FILTER_SOURCES; } - FilterSource[] results = new FilterSource[size]; + final var results = new FilterSource[size]; for ( int i = 0; i < size; i++ ) { JaxbHbmFilterType element = jaxbClassElement.getFilter().get( i ); results[i] = new FilterSourceImpl( sourceMappingDocument(), element ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/EntityNamingSourceImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/EntityNamingSourceImpl.java index 68bc41743838..dd9cc77701be 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/EntityNamingSourceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/EntityNamingSourceImpl.java @@ -5,9 +5,10 @@ package org.hibernate.boot.model.source.internal.hbm; import org.hibernate.boot.model.source.spi.EntityNamingSource; -import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.PersistentClass; +import static org.hibernate.internal.util.StringHelper.isNotEmpty; + /** * Implementation of EntityNamingSource * @@ -24,12 +25,13 @@ public EntityNamingSourceImpl(String entityName, String className, String jpaEnt this.entityName = entityName; this.className = className; this.jpaEntityName = jpaEntityName; - - this.typeName = StringHelper.isNotEmpty( className ) ? className : entityName; + this.typeName = isNotEmpty( className ) ? className : entityName; } public EntityNamingSourceImpl(PersistentClass entityBinding) { - this( entityBinding.getEntityName(), entityBinding.getClassName(), entityBinding.getJpaEntityName() ); + this( entityBinding.getEntityName(), + entityBinding.getClassName(), + entityBinding.getJpaEntityName() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java index 4277507b3062..62ec035b9ba0 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java @@ -45,6 +45,7 @@ import org.hibernate.boot.spi.NaturalIdUniqueKeyBinder; import org.hibernate.boot.spi.SecondPass; import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.dialect.Dialect; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; import org.hibernate.generator.internal.GeneratedGeneration; @@ -80,6 +81,7 @@ import org.hibernate.mapping.SingleTableSubclass; import org.hibernate.mapping.SortableValue; import org.hibernate.mapping.Table; +import org.hibernate.mapping.ToOne; import org.hibernate.mapping.UnionSubclass; import org.hibernate.mapping.UniqueKey; import org.hibernate.mapping.Value; @@ -470,7 +472,7 @@ private void bindJoinedSubclassEntity( keyBinding.makeNationalized(); } entityDescriptor.setKey( keyBinding ); - keyBinding.setOnDeleteAction( getOnDeleteAction( entitySource.isCascadeDeleteEnabled() ) ); + keyBinding.setOnDeleteAction( getOnDeleteAction( entitySource ) ); relationalObjectBinder.bindColumns( mappingDocument, entitySource.getPrimaryKeyColumnSources(), @@ -486,7 +488,8 @@ public Identifier determineImplicitName(LocalMetadataBuildingContext context) { } ); keyBinding.sortProperties(); - setForeignKeyName( keyBinding, entitySource.getExplicitForeignKeyName() ); + setForeignKeyName( keyBinding, + entitySource.getExplicitForeignKeyName() ); // model.getKey().setType( new Type( model.getIdentifier() ) ); entityDescriptor.createPrimaryKey(); entityDescriptor.createForeignKey(); @@ -1056,11 +1059,9 @@ private void handleNaturalIdBinding( NaturalIdMutability naturalIdMutability) { if ( naturalIdMutability != NaturalIdMutability.NOT_NATURAL_ID ) { attributeBinding.setNaturalIdentifier( true ); - if ( naturalIdMutability == NaturalIdMutability.IMMUTABLE ) { attributeBinding.setUpdatable( false ); } - final var metadataCollector = mappingDocument.getMetadataCollector(); final String entityName = entityBinding.getEntityName(); var ukBinder = metadataCollector.locateNaturalIdUniqueKeyBinder( entityName ); @@ -1068,7 +1069,6 @@ private void handleNaturalIdBinding( ukBinder = new NaturalIdUniqueKeyBinderImpl( mappingDocument, entityBinding ); metadataCollector.registerNaturalIdUniqueKeyBinder( entityName, ukBinder ); } - ukBinder.addAttributeBinding( attributeBinding ); } } @@ -1078,12 +1078,10 @@ private Property createPluralAttribute( PluralAttributeSource attributeSource, PersistentClass entityDescriptor) { final Collection collectionBinding; - if ( attributeSource instanceof PluralAttributeSourceListImpl pluralAttributeSourceList ) { final var list = new org.hibernate.mapping.List(sourceDocument, entityDescriptor); collectionBinding = list; bindCollectionMetadata( sourceDocument, attributeSource, collectionBinding ); - registerSecondPass( new PluralAttributeListSecondPass( sourceDocument, pluralAttributeSourceList, list ), sourceDocument @@ -1092,7 +1090,6 @@ private Property createPluralAttribute( else if ( attributeSource instanceof PluralAttributeSourceSetImpl ) { collectionBinding = new Set( sourceDocument, entityDescriptor ); bindCollectionMetadata( sourceDocument, attributeSource, collectionBinding ); - registerSecondPass( new PluralAttributeSetSecondPass( sourceDocument, attributeSource, collectionBinding ), sourceDocument @@ -1102,7 +1099,6 @@ else if ( attributeSource instanceof PluralAttributeSourceMapImpl pluralAttribut final var map = new org.hibernate.mapping.Map( sourceDocument, entityDescriptor ); collectionBinding = map; bindCollectionMetadata( sourceDocument, attributeSource, collectionBinding ); - registerSecondPass( new PluralAttributeMapSecondPass( sourceDocument, pluralAttributeSourceMap, map ), sourceDocument @@ -1111,7 +1107,6 @@ else if ( attributeSource instanceof PluralAttributeSourceMapImpl pluralAttribut else if ( attributeSource instanceof PluralAttributeSourceBagImpl ) { collectionBinding = new Bag( sourceDocument, entityDescriptor ); bindCollectionMetadata( sourceDocument, attributeSource, collectionBinding ); - registerSecondPass( new PluralAttributeBagSecondPass( sourceDocument, attributeSource, collectionBinding ), sourceDocument @@ -1120,7 +1115,6 @@ else if ( attributeSource instanceof PluralAttributeSourceBagImpl ) { else if ( attributeSource instanceof PluralAttributeSourceIdBagImpl ) { collectionBinding = new IdentifierBag( sourceDocument, entityDescriptor ); bindCollectionMetadata( sourceDocument, attributeSource, collectionBinding ); - registerSecondPass( new PluralAttributeIdBagSecondPass( sourceDocument, attributeSource, collectionBinding ), sourceDocument @@ -1130,9 +1124,7 @@ else if ( attributeSource instanceof PluralAttributeSourceArrayImpl arraySource final var array = new Array(sourceDocument, entityDescriptor); collectionBinding = array; bindCollectionMetadata( sourceDocument, attributeSource, collectionBinding ); - array.setElementClassName( sourceDocument.qualifyClassName( arraySource.getElementClass() ) ); - registerSecondPass( new PluralAttributeArraySecondPass( sourceDocument, arraySource, array ), sourceDocument @@ -1142,7 +1134,6 @@ else if ( attributeSource instanceof PluralAttributeSourcePrimitiveArrayImpl plu final var primitiveArray = new PrimitiveArray( sourceDocument, entityDescriptor ); collectionBinding = primitiveArray; bindCollectionMetadata( sourceDocument, attributeSource, collectionBinding ); - registerSecondPass( new PluralAttributePrimitiveArraySecondPass( sourceDocument, @@ -1158,16 +1149,17 @@ else if ( attributeSource instanceof PluralAttributeSourcePrimitiveArrayImpl plu ); } - sourceDocument.getMetadataCollector().addCollectionBinding( collectionBinding ); + sourceDocument.getMetadataCollector() + .addCollectionBinding( collectionBinding ); final var attribute = new Property(); attribute.setValue( collectionBinding ); bindProperty( sourceDocument, attributeSource, attribute ); - return attribute; } - private void bindCollectionMetadata(MappingDocument mappingDocument, PluralAttributeSource source, Collection binding) { + private void bindCollectionMetadata( + MappingDocument mappingDocument, PluralAttributeSource source, Collection binding) { binding.setRole( source.getAttributeRole().getFullPath() ); binding.setInverse( source.isInverse() ); binding.setMutable( source.isMutable() ); @@ -1183,39 +1175,11 @@ private void bindCollectionMetadata(MappingDocument mappingDocument, PluralAttri applyCaching( mappingDocument, source.getCaching(), binding ); - // bind the collection type info - String typeName = source.getTypeInformation().getName(); - final Map typeParameters = new HashMap<>(); - if ( typeName != null ) { - // see if there is a corresponding type-def - final var typeDefinition = - mappingDocument.getMetadataCollector().getTypeDefinition( typeName ); - if ( typeDefinition != null ) { - typeName = typeDefinition.getTypeImplementorClass().getName(); - if ( typeDefinition.getParameters() != null ) { - typeParameters.putAll( typeDefinition.getParameters() ); - } - } - else { - // it could be an unqualified class name, in which case we should qualify - // it with the implicit package name for this context, if one. - typeName = mappingDocument.qualifyClassName( typeName ); - } - } - if ( source.getTypeInformation().getParameters() != null ) { - typeParameters.putAll( source.getTypeInformation().getParameters() ); - } + bindCollectionType( mappingDocument, source, binding ); - binding.setTypeName( typeName ); - binding.setTypeParameters( typeParameters ); - - if ( source.getFetchCharacteristics().getFetchTiming() == FetchTiming.DELAYED ) { - binding.setLazy( true ); - binding.setExtraLazy( source.getFetchCharacteristics().isExtraLazy() ); - } - else { - binding.setLazy( false ); - } + final var fetchCharacteristics = source.getFetchCharacteristics(); + binding.setLazy( fetchCharacteristics.getFetchTiming() == FetchTiming.DELAYED ); + binding.setExtraLazy( fetchCharacteristics.isExtraLazy() ); setupFetching( source, binding ); @@ -1226,23 +1190,16 @@ private void bindCollectionMetadata(MappingDocument mappingDocument, PluralAttri binding.setLoaderName( source.getCustomLoaderName() ); bindCustomSql( source, binding ); - if ( source instanceof Sortable sortable ) { - if ( sortable.isSorted() ) { - binding.setSorted( true ); - final String comparatorName = sortable.getComparatorName(); - if ( !comparatorName.equals( "natural" ) ) { - binding.setComparatorClassName( comparatorName ); - } - } - else { - binding.setSorted( false ); + if ( source instanceof Sortable sortable && sortable.isSorted() ) { + binding.setSorted( true ); + final String comparatorName = sortable.getComparatorName(); + if ( !comparatorName.equals( "natural" ) ) { + binding.setComparatorClassName( comparatorName ); } } - if ( source instanceof Orderable orderable ) { - if ( orderable.isOrdered() ) { - binding.setOrderBy( orderable.getOrder() ); - } + if ( source instanceof Orderable orderable && orderable.isOrdered() ) { + binding.setOrderBy( orderable.getOrder() ); } final String cascadeStyle = source.getCascadeStyleName(); @@ -1261,6 +1218,41 @@ private void bindCollectionMetadata(MappingDocument mappingDocument, PluralAttri } } + private static void bindCollectionType( + MappingDocument mappingDocument, PluralAttributeSource source, Collection binding) { + // bind the collection type info + final String explicitTypeName = source.getTypeInformation().getName(); + final Map typeParameters = new HashMap<>(); + final String typeName; + if ( explicitTypeName != null ) { + // see if there is a corresponding type-def + final var typeDefinition = + mappingDocument.getMetadataCollector() + .getTypeDefinition( explicitTypeName ); + if ( typeDefinition != null ) { + typeName = typeDefinition.getTypeImplementorClass().getName(); + final var parameters = typeDefinition.getParameters(); + if ( parameters != null ) { + typeParameters.putAll( parameters ); + } + } + else { + // it could be an unqualified class name, in which case qualify + // it with the implicit package name for this context, if one. + typeName = mappingDocument.qualifyClassName( explicitTypeName ); + } + } + else { + typeName = null; + } + final var parameters = source.getTypeInformation().getParameters(); + if ( parameters != null ) { + typeParameters.putAll( parameters ); + } + binding.setTypeName( typeName ); + binding.setTypeParameters( typeParameters ); + } + private static void bindCustomSql(PluralAttributeSource source, Collection binding) { if ( source.getCustomSqlInsert() != null ) { binding.setCustomSQLInsert( @@ -1293,26 +1285,20 @@ private static void bindCustomSql(PluralAttributeSource source, Collection bindi } private static void setupFetching(PluralAttributeSource source, Collection binding) { - switch ( source.getFetchCharacteristics().getFetchStyle() ) { - case SELECT: - binding.setFetchMode( FetchMode.SELECT ); - break; - case JOIN: - binding.setFetchMode( FetchMode.JOIN ); - break; + final var fetchCharacteristics = source.getFetchCharacteristics(); + final var fetchStyle = fetchCharacteristics.getFetchStyle(); + binding.setFetchMode( switch ( fetchStyle ) { + case SELECT, BATCH, SUBSELECT -> FetchMode.SELECT; + case JOIN -> FetchMode.JOIN; + } ); + switch ( fetchStyle ) { case BATCH: - binding.setFetchMode( FetchMode.SELECT ); - binding.setBatchSize( source.getFetchCharacteristics().getBatchSize() ); + binding.setBatchSize( fetchCharacteristics.getBatchSize() ); break; case SUBSELECT: - binding.setFetchMode( FetchMode.SELECT ); binding.setSubselectLoadable( true ); - // todo : this could totally be done using a "symbol map" approach binding.getOwner().setSubselectLoadableCollections( true ); break; - default: - throw new AssertionFailure( "Unexpected FetchStyle : " - + source.getFetchCharacteristics().getFetchStyle().name() ); } } @@ -1385,7 +1371,8 @@ private Identifier determineTable( for ( var relationalValueSource : relationalValueSources ) { // We need to get the containing table name for both columns and formulas, // particularly when a column/formula is for a property on a secondary table. - if ( !Objects.equals( tableName, relationalValueSource.getContainingTableName() ) ) { + final String containingTableName = relationalValueSource.getContainingTableName(); + if ( !Objects.equals( tableName, containingTableName ) ) { if ( tableName != null ) { throw new MappingException( String.format( @@ -1393,12 +1380,12 @@ private Identifier determineTable( "Attribute [%s] referenced columns from multiple tables: %s, %s", attributeName, tableName, - relationalValueSource.getContainingTableName() + containingTableName ), mappingDocument.getOrigin() ); } - tableName = relationalValueSource.getContainingTableName(); + tableName = containingTableName; } } return database.toIdentifier( tableName ); @@ -1429,8 +1416,7 @@ private void bindSecondaryTable( } secondaryTable.setComment( tableSource.getComment() ); } - else { - final var inLineViewSource = (InLineViewSource) tableSpecificationSource; + else if ( tableSpecificationSource instanceof InLineViewSource inLineViewSource ) { logicalTableName = toIdentifier( inLineViewSource.getLogicalName() ); secondaryTable = new Table( metadataBuildingContext.getCurrentContributorName(), @@ -1439,6 +1425,9 @@ private void bindSecondaryTable( false ); } + else { + throw new AssertionFailure( "Unexpected table specification soure type" ); + } secondaryTableJoin.setTable( secondaryTable ); entityTableXref.addSecondaryTable( mappingDocument, logicalTableName, secondaryTableJoin ); @@ -1456,13 +1445,14 @@ private void bindSecondaryTable( } final var keyBinding = - new DependantValue( mappingDocument, secondaryTable, persistentClass.getIdentifier() ); + new DependantValue( mappingDocument, secondaryTable, + persistentClass.getIdentifier() ); if ( mappingDocument.getBuildingOptions().useNationalizedCharacterData() ) { keyBinding.makeNationalized(); } secondaryTableJoin.setKey( keyBinding ); - keyBinding.setOnDeleteAction( getOnDeleteAction( secondaryTableSource.isCascadeDeleteEnabled() ) ); + keyBinding.setOnDeleteAction( getOnDeleteAction( secondaryTableSource ) ); // NOTE: no Type info to bind... @@ -1482,7 +1472,8 @@ public Identifier determineImplicitName(LocalMetadataBuildingContext context) { ); keyBinding.sortProperties(); - setForeignKeyName( keyBinding, secondaryTableSource.getExplicitForeignKeyName() ); + setForeignKeyName( keyBinding, + secondaryTableSource.getExplicitForeignKeyName() ); // skip creating primary and foreign keys for a subselect. if ( secondaryTable.getSubselect() == null ) { @@ -1633,15 +1624,8 @@ private Property createOneToOneAttribute( oneToOneSource.getAttributeRole() ); - final String propertyRef = oneToOneBinding.getReferencedPropertyName(); - if ( propertyRef != null ) { - handlePropertyReference( - sourceDocument, - oneToOneBinding.getReferencedEntityName(), - propertyRef, - "" - ); - } + handlePropertyRef( sourceDocument, oneToOneBinding, + "" ); // Defer the creation of the foreign key as we need the // associated entity persister to be initialized so that @@ -1656,25 +1640,36 @@ private Property createOneToOneAttribute( return property; } + private void handlePropertyRef( + MappingDocument sourceDocument, + ToOne binding, + String synopsis) { + final String propertyRef = binding.getReferencedPropertyName(); + if ( propertyRef != null ) { + handlePropertyReference( + sourceDocument, + binding.getReferencedEntityName(), + propertyRef, + synopsis + ); + } + } + private void handlePropertyReference( MappingDocument mappingDocument, String referencedEntityName, String referencedPropertyName, String sourceElementSynopsis) { - final var entityBinding = - mappingDocument.getMetadataCollector() - .getEntityBinding( referencedEntityName ); + final var collector = mappingDocument.getMetadataCollector(); + final var entityBinding = collector.getEntityBinding( referencedEntityName ); if ( entityBinding == null ) { // entity may just not have been processed yet - set up a delayed handler registerDelayedPropertyReferenceHandler( - new DelayedPropertyReferenceHandlerImpl( - referencedEntityName, - referencedPropertyName, - true, - sourceElementSynopsis, - mappingDocument.getOrigin() - ), - mappingDocument + mappingDocument, + referencedEntityName, + referencedPropertyName, + sourceElementSynopsis, + collector ); } else { @@ -1683,14 +1678,11 @@ private void handlePropertyReference( if ( propertyBinding == null ) { // attribute may just not have been processed yet - set up a delayed handler registerDelayedPropertyReferenceHandler( - new DelayedPropertyReferenceHandlerImpl( - referencedEntityName, - referencedPropertyName, - true, - sourceElementSynopsis, - mappingDocument.getOrigin() - ), - mappingDocument + mappingDocument, + referencedEntityName, + referencedPropertyName, + sourceElementSynopsis, + collector ); } else { @@ -1706,15 +1698,26 @@ private void handlePropertyReference( } private void registerDelayedPropertyReferenceHandler( - DelayedPropertyReferenceHandlerImpl handler, - MetadataBuildingContext buildingContext) { + MappingDocument mappingDocument, + String referencedEntityName, + String referencedPropertyName, + String sourceElementSynopsis, + InFlightMetadataCollector collector) { + final var handler = + new DelayedPropertyReferenceHandlerImpl( + referencedEntityName, + referencedPropertyName, + true, + sourceElementSynopsis, + mappingDocument.getOrigin() + ); BOOT_LOGGER.tracef( "Property [%s.%s] referenced by property-ref [%s] was not yet available - creating delayed handler", handler.referencedEntityName, handler.referencedPropertyName, handler.sourceElementSynopsis ); - buildingContext.getMetadataCollector().addDelayedPropertyReferenceHandler( handler ); + collector.addDelayedPropertyReferenceHandler( handler ); } public void bindOneToOne( @@ -1729,19 +1732,8 @@ public void bindOneToOne( oneToOneBinding ); - if ( oneToOneSource.isConstrained() ) { - final String cascadeStyleName = oneToOneSource.getCascadeStyleName(); - if ( cascadeStyleName != null && cascadeStyleName.contains( "delete-orphan" ) ) { - throw new MappingException( - String.format( - Locale.ENGLISH, - "one-to-one attribute [%s] cannot specify orphan delete cascading as it is constrained", - oneToOneSource.getAttributeRole().getFullPath() - ), - sourceDocument.getOrigin() - ); - } + checkConstrainedOneToOneOrphanDelete( sourceDocument, oneToOneSource ); oneToOneBinding.setConstrained( true ); oneToOneBinding.setForeignKeyType( ForeignKeyDirection.FROM_PARENT ); } @@ -1749,15 +1741,7 @@ public void bindOneToOne( oneToOneBinding.setForeignKeyType( ForeignKeyDirection.TO_PARENT ); } - final var fetchCharacteristics = oneToOneSource.getFetchCharacteristics(); - oneToOneBinding.setLazy( fetchCharacteristics.getFetchTiming() == FetchTiming.DELAYED ); - oneToOneBinding.setFetchMode( - fetchCharacteristics.getFetchStyle() == FetchStyle.SELECT - ? FetchMode.SELECT - : FetchMode.JOIN - ); - oneToOneBinding.setUnwrapProxy( fetchCharacteristics.isUnwrapProxies() ); - + handleFetchCharacteristics( oneToOneSource, oneToOneBinding ); final String referencedEntityAttributeName = oneToOneSource.getReferencedEntityAttributeName(); if ( isNotEmpty( referencedEntityAttributeName ) ) { @@ -1775,11 +1759,21 @@ public void bindOneToOne( DEPRECATION_LOGGER.logDeprecationOfEmbedXmlSupport(); } - if ( isNotEmpty( oneToOneSource.getExplicitForeignKeyName() ) ) { - setForeignKeyName( oneToOneBinding, oneToOneSource.getExplicitForeignKeyName() ); - } + setForeignKeyName( oneToOneBinding, + oneToOneSource.getExplicitForeignKeyName() ); + + oneToOneBinding.setOnDeleteAction( getOnDeleteAction( oneToOneSource ) ); + } - oneToOneBinding.setOnDeleteAction( getOnDeleteAction( oneToOneSource.isCascadeDeleteEnabled() ) ); + private static void handleFetchCharacteristics(SingularAttributeSourceToOne toOneSource, ToOne toOneBinding) { + final var fetchCharacteristics = toOneSource.getFetchCharacteristics(); + toOneBinding.setLazy( fetchCharacteristics.getFetchTiming() == FetchTiming.DELAYED ); + toOneBinding.setFetchMode( + fetchCharacteristics.getFetchStyle() == FetchStyle.SELECT + ? FetchMode.SELECT + : FetchMode.JOIN + ); + toOneBinding.setUnwrapProxy( fetchCharacteristics.isUnwrapProxies() ); } private Property createManyToOneAttribute( @@ -1787,18 +1781,42 @@ private Property createManyToOneAttribute( SingularAttributeSourceManyToOne manyToOneSource, ManyToOne manyToOneBinding, String containingClassName) { - final String attributeName = manyToOneSource.getName(); + final String referencedEntityName = + handleReferencedEntity( sourceDocument, manyToOneSource, manyToOneBinding, containingClassName ); + if ( manyToOneSource.isUnique() ) { + manyToOneBinding.markAsLogicalOneToOne(); + } + + bindManyToOneAttribute( sourceDocument, manyToOneSource, manyToOneBinding, referencedEntityName ); + + handlePropertyRef( sourceDocument, manyToOneBinding, + "" ); + + final var property = new Property(); + property.setValue( manyToOneBinding ); + bindProperty( sourceDocument, manyToOneSource, property ); + + checkManyToOneOrphanDelete( sourceDocument, manyToOneSource, manyToOneBinding ); + + return property; + } + + private String handleReferencedEntity( + MappingDocument sourceDocument, + SingularAttributeSourceManyToOne manyToOneSource, + ManyToOne manyToOneBinding, + String containingClassName) { final String explicitReferencedEntityName = manyToOneSource.getReferencedEntityName(); - final String referencedEntityName; if ( explicitReferencedEntityName != null ) { - referencedEntityName = explicitReferencedEntityName; + return explicitReferencedEntityName; } else { + final String attributeName = manyToOneSource.getName(); final var reflectedPropertyClass = reflectedPropertyClass( sourceDocument, containingClassName, attributeName ); if ( reflectedPropertyClass != null ) { - referencedEntityName = reflectedPropertyClass.getName(); + return reflectedPropertyClass.getName(); } else { prepareValueTypeViaReflection( @@ -1808,57 +1826,54 @@ private Property createManyToOneAttribute( attributeName, manyToOneSource.getAttributeRole() ); - referencedEntityName = manyToOneBinding.getTypeName(); + return manyToOneBinding.getTypeName(); } } + } - if ( manyToOneSource.isUnique() ) { - manyToOneBinding.markAsLogicalOneToOne(); - } - - bindManyToOneAttribute( sourceDocument, manyToOneSource, manyToOneBinding, referencedEntityName ); - - final String propertyRef = manyToOneBinding.getReferencedPropertyName(); - if ( propertyRef != null ) { - handlePropertyReference( - sourceDocument, - manyToOneBinding.getReferencedEntityName(), - propertyRef, - "" + private static void checkManyToOneOrphanDelete( + MappingDocument sourceDocument, + SingularAttributeSourceManyToOne manyToOneSource, + ManyToOne manyToOneBinding) { + // TODO: would be better to delay this until the end of binding (second pass, etc) + // in order to properly allow for a singular unique column for a many-to-one to + // to also trigger a "logical one-to-one". As is, this can occasionally lead to + // false exceptions if the many-to-one column binding is delayed and the + // uniqueness is indicated on the rather than on the + // + // Ideally, would love to see a SimpleValue#validate approach, rather than a + // SimpleValue#isValid that is then handled at a higher level (Property, etc). + // The reason being that the current approach misses the exact reason a + // "validation" fails since it loses "context" + final String cascadeStyleName = manyToOneSource.getCascadeStyleName(); + if ( cascadeStyleName != null && cascadeStyleName.contains( "delete-orphan" ) + && !manyToOneBinding.isLogicalOneToOne() ) { + throw new MappingException( + String.format( + Locale.ENGLISH, + "many-to-one attribute [%s] specified delete-orphan but is not specified as unique; " + + "remove delete-orphan cascading or specify unique=\"true\"", + manyToOneSource.getAttributeRole().getFullPath() + ), + sourceDocument.getOrigin() ); } + } - final var property = new Property(); - property.setValue( manyToOneBinding ); - bindProperty( sourceDocument, manyToOneSource, property ); - - if ( isNotEmpty( manyToOneSource.getCascadeStyleName() ) ) { - // todo : would be better to delay this the end of binding (second pass, etc) - // in order to properly allow for a singular unique column for a many-to-one to - // also trigger a "logical one-to-one". As-is, this can occasionally lead to - // false exceptions if the many-to-one column binding is delayed and the - // uniqueness is indicated on the rather than on the - // - // Ideally, would love to see a SimpleValue#validate approach, rather than a - // SimpleValue#isValid that is then handled at a higher level (Property, etc). - // The reason being that the current approach misses the exact reason - // a "validation" fails since it loses "context" - if ( manyToOneSource.getCascadeStyleName().contains( "delete-orphan" ) ) { - if ( !manyToOneBinding.isLogicalOneToOne() ) { - throw new MappingException( - String.format( - Locale.ENGLISH, - "many-to-one attribute [%s] specified delete-orphan but is not specified as unique; " + - "remove delete-orphan cascading or specify unique=\"true\"", - manyToOneSource.getAttributeRole().getFullPath() - ), - sourceDocument.getOrigin() - ); - } - } + private static void checkConstrainedOneToOneOrphanDelete( + MappingDocument sourceDocument, + SingularAttributeSourceOneToOne oneToOneSource) { + final String cascadeStyleName = oneToOneSource.getCascadeStyleName(); + if ( cascadeStyleName != null && cascadeStyleName.contains( "delete-orphan" ) ) { + throw new MappingException( + String.format( + Locale.ENGLISH, + "one-to-one attribute [%s] cannot specify orphan delete cascading as it is constrained", + oneToOneSource.getAttributeRole().getFullPath() + ), + sourceDocument.getOrigin() + ); } - - return property; } private void bindManyToOneAttribute( @@ -1868,24 +1883,10 @@ private void bindManyToOneAttribute( String referencedEntityName) { // NOTE: no type information to bind - manyToOneBinding.setReferencedEntityName( referencedEntityName ); - final String referencedEntityAttributeName = manyToOneSource.getReferencedEntityAttributeName(); - if ( isNotEmpty( referencedEntityAttributeName ) ) { - manyToOneBinding.setReferencedPropertyName( referencedEntityAttributeName ); - manyToOneBinding.setReferenceToPrimaryKey( false ); - } - else { - manyToOneBinding.setReferenceToPrimaryKey( true ); - } + handleReferencedEntity( manyToOneBinding, referencedEntityName, + manyToOneSource.getReferencedEntityAttributeName() ); - final var fetchCharacteristics = manyToOneSource.getFetchCharacteristics(); - manyToOneBinding.setLazy( fetchCharacteristics.getFetchTiming() == FetchTiming.DELAYED ); - manyToOneBinding.setUnwrapProxy( fetchCharacteristics.isUnwrapProxies() ); - manyToOneBinding.setFetchMode( - fetchCharacteristics.getFetchStyle() == FetchStyle.SELECT - ? FetchMode.SELECT - : FetchMode.JOIN - ); + handleFetchCharacteristics( manyToOneSource, manyToOneBinding ); if ( manyToOneSource.isEmbedXml() == Boolean.TRUE ) { DEPRECATION_LOGGER.logDeprecationOfEmbedXmlSupport(); @@ -1893,7 +1894,8 @@ private void bindManyToOneAttribute( manyToOneBinding.setIgnoreNotFound( manyToOneSource.isIgnoreNotFound() ); - setForeignKeyName( manyToOneBinding, manyToOneSource.getExplicitForeignKeyName() ); + setForeignKeyName( manyToOneBinding, + manyToOneSource.getExplicitForeignKeyName() ); final var columnBinder = new ManyToOneColumnBinder( sourceDocument, @@ -1918,7 +1920,6 @@ private void bindManyToOneAttribute( manyToOneBinding, referencedEntityName ); - if ( canBindColumnsImmediately && fkSecondPass.canProcessImmediately() ) { fkSecondPass.doSecondPass( null ); } @@ -1927,7 +1928,19 @@ private void bindManyToOneAttribute( } } - manyToOneBinding.setOnDeleteAction( getOnDeleteAction( manyToOneSource.isCascadeDeleteEnabled() ) ); + manyToOneBinding.setOnDeleteAction( getOnDeleteAction( manyToOneSource ) ); + } + + private static void handleReferencedEntity( + ManyToOne manyToOneBinding, String referencedEntityName, String referencedEntityAttributeName) { + manyToOneBinding.setReferencedEntityName( referencedEntityName ); + if ( isNotEmpty( referencedEntityAttributeName ) ) { + manyToOneBinding.setReferencedPropertyName( referencedEntityAttributeName ); + manyToOneBinding.setReferenceToPrimaryKey( false ); + } + else { + manyToOneBinding.setReferenceToPrimaryKey( true ); + } } private static void setForeignKeyName(SimpleValue manyToOneBinding, String foreignKeyName) { @@ -1946,13 +1959,9 @@ private Property createAnyAssociationAttribute( SingularAttributeSourceAny anyMapping, Any anyBinding, String entityName) { - final var attributeRole = anyMapping.getAttributeRole(); - bindAny( sourceDocument, anyMapping, anyBinding, attributeRole ); - prepareValueTypeViaReflection( sourceDocument, anyBinding, entityName, anyMapping.getName(), attributeRole ); - anyBinding.createForeignKey(); final var property = new Property(); @@ -1999,9 +2008,7 @@ private void bindAny( anyMapping.getKeySource().getRelationalValueSources(), anyBinding.getKeyMapping(), true, - context -> implicitNamingStrategy.determineAnyKeyColumnName( - anyMapping.getKeySource() - ) + context -> implicitNamingStrategy.determineAnyKeyColumnName( anyMapping.getKeySource() ) ); } @@ -2022,9 +2029,9 @@ private BasicType anyDiscriminatorType(Any anyBinding, TypeResolution discrim ); } else { - return metadataBuildingContext.getBootstrapContext() - .getTypeConfiguration().getBasicTypeRegistry() - .resolve( StandardBasicTypes.STRING ); + return + metadataBuildingContext.getBootstrapContext().getTypeConfiguration().getBasicTypeRegistry() + .resolve( StandardBasicTypes.STRING ); } } @@ -2111,11 +2118,8 @@ private BasicType resolveExplicitlyNamedAnyDiscriminatorType( } throw new org.hibernate.MappingException( - String.format( - Locale.ROOT, - "Unable to resolve explicit any-discriminator type name - %s", - typeName - ) + "Unable to resolve explicit any-discriminator type name:" + + typeName ); } } @@ -2141,7 +2145,8 @@ private void prepareValueTypeViaReflection( throw new MappingException( String.format( Locale.ENGLISH, - "Attribute mapping must define a name attribute: containingClassName=[%s], propertyName=[%s], role=[%s]", + "Attribute mapping must define a name attribute:" + + " containingClassName=[%s], propertyName=[%s], role=[%s]", containingClassName, propertyName, attributeRole.getFullPath() @@ -2157,7 +2162,8 @@ private void prepareValueTypeViaReflection( throw new MappingException( String.format( Locale.ENGLISH, - "Error calling Value#setTypeUsingReflection: containingClassName=[%s], propertyName=[%s], role=[%s]", + "Error calling Value#setTypeUsingReflection:" + + " containingClassName=[%s], propertyName=[%s], role=[%s]", containingClassName, propertyName, attributeRole.getFullPath() @@ -2194,13 +2200,15 @@ private void bindProperty( property.setUpdatable( singularAttributeSource.isUpdatable() ); // isBytecodeLazy() refers to whether a property is lazy via bytecode enhancement (not proxies) property.setLazy( singularAttributeSource.isBytecodeLazy() ); - handleGenerationTiming( mappingDocument, propertySource, property, singularAttributeSource.getGenerationTiming() ); + handleGenerationTiming( mappingDocument, propertySource, property, + singularAttributeSource.getGenerationTiming() ); } property.setMetaAttributes( propertySource.getToolingHintContext().getMetaAttributeMap() ); if ( BOOT_LOGGER.isTraceEnabled() ) { - BOOT_LOGGER.trace( "Mapped property: " + propertySource.getName() + " -> [" + columns( property.getValue() ) + "]" ); + BOOT_LOGGER.trace( "Mapped property: " + propertySource.getName() + + " -> [" + columns( property.getValue() ) + "]" ); } } @@ -2362,8 +2370,9 @@ private static void bindVirtual(String role, Component componentBinding) { // virtual (what used to be called embedded) is just a conceptual composition... // for example if ( componentBinding.getOwner().hasPojoRepresentation() ) { - BOOT_LOGGER.bindingVirtualComponentToOwner( role, componentBinding.getOwner().getClassName() ); - componentBinding.setComponentClassName( componentBinding.getOwner().getClassName() ); + final String ownerClassName = componentBinding.getOwner().getClassName(); + BOOT_LOGGER.bindingVirtualComponentToOwner( role, ownerClassName ); + componentBinding.setComponentClassName( ownerClassName ); } else { BOOT_LOGGER.bindingVirtualComponentAsDynamic( role ); @@ -2537,32 +2546,32 @@ public TypeResolution(String typeName, Map parameters) { private static TypeResolution resolveType( MappingDocument sourceDocument, HibernateTypeSource typeSource) { - if ( StringHelper.isEmpty( typeSource.getName() ) ) { + final String typeSourceName = typeSource.getName(); + if ( StringHelper.isEmpty( typeSourceName ) ) { return null; } final var typeDefinition = sourceDocument.getMetadataCollector() - .getTypeDefinition( typeSource.getName() ); + .getTypeDefinition( typeSourceName ); final Map typeParameters = new HashMap<>(); final String typeName; if ( typeDefinition == null ) { - typeName = typeSource.getName(); + typeName = typeSourceName; } else { // the explicit name referred to a type-def typeName = typeDefinition.getTypeImplementorClass().getName(); - if ( typeDefinition.getParameters() != null ) { - typeParameters.putAll( typeDefinition.getParameters() ); + final var parameters = typeDefinition.getParameters(); + if ( parameters != null ) { + typeParameters.putAll( parameters ); } } - // parameters on the property mapping should override parameters in the type-def final var parameters = typeSource.getParameters(); if ( parameters != null ) { typeParameters.putAll( parameters ); } - return new TypeResolution( typeName, typeParameters ); } @@ -2573,12 +2582,10 @@ private Table bindEntityTableSpecification( final EntitySource entitySource, PersistentClass entityDescriptor) { final String contributorName = mappingDocument.getCurrentContributorName(); - final boolean isTable = tableSpecSource instanceof TableSource; final boolean isAbstract = entityDescriptor.isAbstract() != null && entityDescriptor.isAbstract(); final Identifier logicalTableName; final Table table; - if ( isTable ) { - final var tableSource = (TableSource) tableSpecSource; + if ( tableSpecSource instanceof TableSource tableSource ) { logicalTableName = logicalTableName( mappingDocument, entitySource, tableSource ); table = tableForEntity( tableSource, @@ -2588,8 +2595,7 @@ private Table bindEntityTableSpecification( isAbstract ); } - else { - final var inlineViewSource = (InLineViewSource) tableSpecSource; + else if ( tableSpecSource instanceof InLineViewSource inlineViewSource ) { logicalTableName = database.toIdentifier( inlineViewSource.getLogicalName() ); table = tableForSubselect( inlineViewSource, @@ -2599,6 +2605,9 @@ private Table bindEntityTableSpecification( logicalTableName ); } + else { + throw new AssertionFailure( "Unexpected table specification source type" ); + } final var metadataCollector = mappingDocument.getMetadataCollector(); @@ -2609,8 +2618,7 @@ private Table bindEntityTableSpecification( superEntityTableXref( mappingDocument, entitySource, entityDescriptor, metadataCollector ) ); - if ( isTable ) { - final var tableSource = (TableSource) tableSpecSource; + if ( tableSpecSource instanceof TableSource tableSource ) { table.setRowId( tableSource.getRowId() ); final String checkConstraint = tableSource.getCheckConstraint(); if ( isNotEmpty( checkConstraint ) ) { @@ -2805,7 +2813,6 @@ private void registerSecondPass(SecondPass secondPass, MetadataBuildingContext c } - public static final class DelayedPropertyReferenceHandlerImpl implements InFlightMetadataCollector.DelayedPropertyReferenceHandler { public final String referencedEntityName; @@ -2947,9 +2954,7 @@ private void bindCollectionTable() { } else { final var tableSpecSource = pluralAttributeSource.getCollectionTableSpecificationSource(); - final Identifier logicalCatalogName = determineCatalogName( tableSpecSource ); - final Identifier logicalSchemaName = determineSchemaName( tableSpecSource ); - final var namespace = database.locateNamespace( logicalCatalogName, logicalSchemaName ); + final var namespace = locateTableNamespace( tableSpecSource ); final Table collectionTable; if ( tableSpecSource instanceof TableSource tableSource ) { @@ -2963,19 +2968,21 @@ private void bindCollectionTable() { ) ); } - else { + else if ( tableSpecSource instanceof InLineViewSource inlineViewSource ) { collectionTable = new Table( metadataBuildingContext.getCurrentContributorName(), namespace, - ( (InLineViewSource) tableSpecSource ).getSelectStatement(), + inlineViewSource.getSelectStatement(), false ); } + else { + throw new AssertionFailure( "Unexpected table specification source type" ); + } collectionBinding.setCollectionTable( collectionTable ); } - final var collectionTable = collectionBinding.getCollectionTable(); if ( BOOT_LOGGER.isTraceEnabled() ) { @@ -2984,36 +2991,33 @@ private void bindCollectionTable() { collectionTable.getName() ); } - if ( pluralAttributeSource.getCollectionTableComment() != null ) { - collectionTable.setComment( pluralAttributeSource.getCollectionTableComment() ); + final String tableComment = pluralAttributeSource.getCollectionTableComment(); + if ( tableComment != null ) { + collectionTable.setComment( tableComment ); } - if ( pluralAttributeSource.getCollectionTableCheck() != null ) { - collectionTable.addCheckConstraint( pluralAttributeSource.getCollectionTableCheck() ); + final String tableCheck = pluralAttributeSource.getCollectionTableCheck(); + if ( tableCheck != null ) { + collectionTable.addCheckConstraint( tableCheck ); } } private Identifier logicalName(TableSource tableSource) { - if ( isNotEmpty( tableSource.getExplicitTableName() ) ) { - return toIdentifier( tableSource.getExplicitTableName(), + final String explicitTableName = tableSource.getExplicitTableName(); + if ( isNotEmpty( explicitTableName ) ) { + return toIdentifier( explicitTableName, mappingDocument.getEffectiveDefaults().isDefaultQuoteIdentifiers() ); } else { - final var owner = collectionBinding.getOwner(); - final var ownerEntityNaming = new EntityNamingSourceImpl( - owner.getEntityName(), - owner.getClassName(), - owner.getJpaEntityName() - ); final var implicitNamingSource = new ImplicitCollectionTableNameSource() { @Override public Identifier getOwningPhysicalTableName() { - return owner.getTable().getNameIdentifier(); + return collectionBinding.getOwner().getTable().getNameIdentifier(); } @Override public EntityNaming getOwningEntityNaming() { - return ownerEntityNaming; + return new EntityNamingSourceImpl( collectionBinding.getOwner() ); } @Override @@ -3037,10 +3041,11 @@ protected void createBackReferences() { && !collectionBinding.getKey().isNullable() ) { // for non-inverse one-to-many, with a not-null fk, add a backref! final var oneToMany = (OneToMany) collectionBinding.getElement(); - final String entityName = oneToMany.getReferencedEntityName(); - final var referenced = getReferencedEntityBinding( entityName ); + final var referenced = + getReferencedEntityBinding( oneToMany.getReferencedEntityName() ); final var backref = new Backref(); - backref.setName( '_' + collectionBinding.getOwnerEntityName() + "." + pluralAttributeSource.getName() + "Backref" ); + backref.setName( '_' + collectionBinding.getOwnerEntityName() + + "." + pluralAttributeSource.getName() + "Backref" ); backref.setOptional( true ); backref.setUpdatable( false ); backref.setSelectable( false ); @@ -3075,8 +3080,9 @@ protected void bindCollectionKey() { collectionBinding.getCollectionTable(), keyVal ); - setForeignKeyName( key, keySource.getExplicitForeignKeyName() ); - key.setOnDeleteAction( getOnDeleteAction( pluralAttributeSource.getKeySource().isCascadeDeleteEnabled() ) ); + setForeignKeyName( key, + keySource.getExplicitForeignKeyName() ); + key.setOnDeleteAction( getOnDeleteAction( keySource ) ); // final ImplicitJoinColumnNameSource.Nature implicitNamingNature; // if ( getPluralAttributeSource().getElementSource() instanceof PluralAttributeElementSourceManyToMany @@ -3105,7 +3111,7 @@ protected void bindCollectionKey() { } protected void bindCollectionIdentifier() { - final CollectionIdSource idSource = getPluralAttributeSource().getCollectionIdSource(); + final var idSource = getPluralAttributeSource().getCollectionIdSource(); if ( idSource != null ) { final var idBagBinding = (IdentifierCollection) getCollectionBinding(); final var idBinding = new BasicValue( @@ -3142,204 +3148,204 @@ protected void bindCollectionIndex() { } protected void bindCollectionElement() { - final PluralAttributeElementSource pluralElementSource = getPluralAttributeSource().getElementSource(); + final var pluralElementSource = pluralAttributeSource.getElementSource(); if ( BOOT_LOGGER.isTraceEnabled() ) { - BOOT_LOGGER.tracef( - "Binding [%s] element type for a [%s]", - pluralElementSource.getNature(), - getPluralAttributeSource().getNature() - ); + BOOT_LOGGER.tracef( "Binding [%s] element type for a [%s]", + pluralElementSource.getNature(), pluralAttributeSource.getNature() ); } - final var collectionBinding = getCollectionBinding(); - final var mappingDocument = getMappingDocument(); if ( pluralElementSource instanceof PluralAttributeElementSourceBasic elementSource ) { - final var elementBinding = - new BasicValue( mappingDocument, collectionBinding.getCollectionTable() ); - - bindSimpleValueType( - mappingDocument, - elementSource.getExplicitHibernateTypeSource(), - elementBinding - ); - - relationalObjectBinder.bindColumnsAndFormulas( - this.mappingDocument, - elementSource.getRelationalValueSources(), - elementBinding, - elementSource.areValuesNullableByDefault(), - context -> context.getMetadataCollector().getDatabase() - .toIdentifier( Collection.DEFAULT_ELEMENT_COLUMN_NAME ) - ); - - collectionBinding.setElement( elementBinding ); - // Collection#setWhere is used to set the "where" clause that applies to the collection table - // (the table containing the basic elements) - // This "where" clause comes from the collection mapping; e.g., - collectionBinding.setWhere( getPluralAttributeSource().getWhere() ); + bindBasicElement( elementSource ); } else if ( pluralElementSource instanceof PluralAttributeElementSourceEmbedded elementSource ) { - final var elementBinding = new Component( mappingDocument, collectionBinding ); - - final var embeddableSource = elementSource.getEmbeddableSource(); - bindComponent( - this.mappingDocument, - embeddableSource, - elementBinding, - null, - embeddableSource.getAttributePathBase().getProperty(), - false - ); - - collectionBinding.setElement( elementBinding ); - // Collection#setWhere is used to set the "where" clause that applies to the collection table - // (the table containing the embeddable elements) - // This "where" clause comes from the collection mapping; e.g., - collectionBinding.setWhere( getPluralAttributeSource().getWhere() ); + bindEmbeddedElement( elementSource ); } else if ( pluralElementSource instanceof PluralAttributeElementSourceOneToMany elementSource ) { - final var elementBinding = - new OneToMany( mappingDocument, collectionBinding.getOwner() ); - this.collectionBinding.setElement( elementBinding ); - - final var referencedEntityBinding = - getReferencedEntityBinding( elementSource.getReferencedEntityName() ); - - this.collectionBinding.setWhere( - getNonEmptyOrConjunctionIfBothNonEmpty( - referencedEntityBinding.getWhere(), - getPluralAttributeSource().getWhere() - ) - ); - - elementBinding.setReferencedEntityName( referencedEntityBinding.getEntityName() ); - elementBinding.setAssociatedClass( referencedEntityBinding ); - elementBinding.setIgnoreNotFound( elementSource.isIgnoreNotFound() ); + bindOneToManyElement( elementSource ); } else if ( pluralElementSource instanceof PluralAttributeElementSourceManyToMany elementSource ) { - final var elementBinding = - new ManyToOne( mappingDocument, collectionBinding.getCollectionTable() ); - - relationalObjectBinder.bindColumnsAndFormulas( - mappingDocument, - elementSource.getRelationalValueSources(), - elementBinding, - false, - context -> context.getMetadataCollector().getDatabase() - .toIdentifier( Collection.DEFAULT_ELEMENT_COLUMN_NAME ) - ); - - final var fetchCharacteristics = elementSource.getFetchCharacteristics(); - elementBinding.setLazy( fetchCharacteristics.getFetchTiming() != FetchTiming.IMMEDIATE ); - elementBinding.setFetchMode( - fetchCharacteristics.getFetchStyle() == FetchStyle.SELECT - ? FetchMode.SELECT - : FetchMode.JOIN - ); - - setForeignKeyName( elementBinding, elementSource.getExplicitForeignKeyName() ); - - final String referencedEntityName = elementSource.getReferencedEntityName(); - elementBinding.setReferencedEntityName( referencedEntityName ); - final String referencedEntityAttributeName = elementSource.getReferencedEntityAttributeName(); - if ( isNotEmpty( referencedEntityAttributeName ) ) { - elementBinding.setReferencedPropertyName( referencedEntityAttributeName ); - elementBinding.setReferenceToPrimaryKey( false ); - } - else { - elementBinding.setReferenceToPrimaryKey( true ); - } - - collectionBinding.setElement( elementBinding ); - - final PersistentClass referencedEntityBinding = getReferencedEntityBinding( referencedEntityName ); - - // Collection#setWhere is used to set the "where" clause that applies to the collection table - // (which is the join table for a many-to-many association). - // This "where" clause comes from the collection mapping; e.g., - collectionBinding.setWhere( getPluralAttributeSource().getWhere() ); + bindManyToManyElement( elementSource ); + } + else if ( pluralElementSource instanceof PluralAttributeElementSourceManyToAny elementSource ) { + bindManyToAnyElement( elementSource ); + } + } - collectionBinding.setManyToManyWhere( - getNonEmptyOrConjunctionIfBothNonEmpty( - referencedEntityBinding.getWhere(), - elementSource.getWhere() - ) - ); + private void bindManyToAnyElement(PluralAttributeElementSourceManyToAny elementSource) { + final var elementBinding = + new Any( mappingDocument, + collectionBinding.getCollectionTable() ); + bindAny( mappingDocument, elementSource, elementBinding, + pluralAttributeSource.getAttributeRole().append( "element" ) ); + collectionBinding.setElement( elementBinding ); + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (which is the join table for a many-to-any association). + // This "where" clause comes from the collection mapping; e.g., + collectionBinding.setWhere( getPluralAttributeSource().getWhere() ); + } - collectionBinding.setManyToManyOrdering( elementSource.getOrder() ); + private void bindManyToManyElement(PluralAttributeElementSourceManyToMany elementSource) { + final var elementBinding = + new ManyToOne( mappingDocument, + collectionBinding.getCollectionTable() ); + relationalObjectBinder.bindColumnsAndFormulas( + mappingDocument, + elementSource.getRelationalValueSources(), + elementBinding, + false, + context -> context.getMetadataCollector().getDatabase() + .toIdentifier( Collection.DEFAULT_ELEMENT_COLUMN_NAME ) + ); + handleFetchCharacteristics( elementSource, elementBinding ); + setForeignKeyName( elementBinding, + elementSource.getExplicitForeignKeyName() ); + final String referencedEntityName = elementSource.getReferencedEntityName(); + handleReferencedEntity( elementBinding, referencedEntityName, + elementSource.getReferencedEntityAttributeName() ); + collectionBinding.setElement( elementBinding ); + final var referencedEntityBinding = getReferencedEntityBinding( referencedEntityName ); + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (which is the join table for a many-to-many association). + // This "where" clause comes from the collection mapping; e.g., + collectionBinding.setWhere( pluralAttributeSource.getWhere() ); + collectionBinding.setManyToManyWhere( + getNonEmptyOrConjunctionIfBothNonEmpty( + referencedEntityBinding.getWhere(), + elementSource.getWhere() + ) + ); + collectionBinding.setManyToManyOrdering( elementSource.getOrder() ); + bindManyToManyFilters( elementSource, mappingDocument, collectionBinding, elementBinding ); + } - if ( !isEmpty( elementSource.getFilterSources() ) - || elementSource.getWhere() != null ) { - if ( collectionBinding.getFetchMode() == FetchMode.JOIN + private void bindManyToManyFilters( + PluralAttributeElementSourceManyToMany elementSource, + MappingDocument mappingDocument, + Collection collectionBinding, + ManyToOne elementBinding) { + if ( !isEmpty( elementSource.getFilterSources() ) + || elementSource.getWhere() != null ) { + if ( collectionBinding.getFetchMode() == FetchMode.JOIN && elementBinding.getFetchMode() != FetchMode.JOIN ) { - throw new MappingException( - String.format( - Locale.ENGLISH, - "many-to-many defining filter or where without join fetching is not " + - "valid within collection [%s] using join fetching", - getPluralAttributeSource().getAttributeRole().getFullPath() - ), - mappingDocument.getOrigin() - ); - } - } - - for ( var filterSource : elementSource.getFilterSources() ) { - if ( filterSource.getName() == null ) { - if ( BOOT_LOGGER.isTraceEnabled() ) { - BOOT_LOGGER.tracef( - "Encountered filter with no name associated with many-to-many [%s]; skipping", + throw new MappingException( + String.format( + Locale.ENGLISH, + "many-to-many defining filter or where without join fetching is not " + + "valid within collection [%s] using join fetching", getPluralAttributeSource().getAttributeRole().getFullPath() - ); - } - continue; - } - - if ( filterSource.getCondition() == null ) { - throw new MappingException( - String.format( - Locale.ENGLISH, - "No filter condition found for filter [%s] associated with many-to-many [%s]", - filterSource.getName(), - getPluralAttributeSource().getAttributeRole().getFullPath() - ), - mappingDocument.getOrigin() - ); - } + ), + mappingDocument.getOrigin() + ); + } + } - if ( BOOT_LOGGER.isTraceEnabled() ) { - BOOT_LOGGER.tracef( - "Applying many-to-many filter [%s] as [%s] to collection [%s]", - filterSource.getName(), - filterSource.getCondition(), - getPluralAttributeSource().getAttributeRole().getFullPath() - ); - } + for ( var filterSource : elementSource.getFilterSources() ) { + bindManyToManyFilter( mappingDocument, collectionBinding, filterSource ); + } + } - collectionBinding.addManyToManyFilter( - filterSource.getName(), - filterSource.getCondition(), - filterSource.shouldAutoInjectAliases(), - filterSource.getAliasToTableMap(), - filterSource.getAliasToEntityMap() + private void bindManyToManyFilter( + MappingDocument mappingDocument, Collection collectionBinding, FilterSource filterSource) { + final String name = filterSource.getName(); + if ( name == null ) { + if ( BOOT_LOGGER.isTraceEnabled() ) { + BOOT_LOGGER.tracef( + "Encountered filter with no name associated with many-to-many [%s]; skipping", + getPluralAttributeSource().getAttributeRole().getFullPath() ); } } - else if ( pluralElementSource instanceof PluralAttributeElementSourceManyToAny elementSource ) { - final var elementBinding = new Any( mappingDocument, collectionBinding.getCollectionTable() ); - bindAny( - this.mappingDocument, - elementSource, - elementBinding, - getPluralAttributeSource().getAttributeRole().append( "element" ) + else { + final String condition = filterSource.getCondition(); + if ( condition == null ) { + throw new MappingException( + String.format( + Locale.ENGLISH, + "No filter condition found for filter [%s] associated with many-to-many [%s]", + name, + getPluralAttributeSource().getAttributeRole().getFullPath() + ), + mappingDocument.getOrigin() + ); + } + if ( BOOT_LOGGER.isTraceEnabled() ) { + BOOT_LOGGER.tracef( + "Applying many-to-many filter [%s] as [%s] to collection [%s]", + name, + condition, + getPluralAttributeSource().getAttributeRole().getFullPath() + ); + } + collectionBinding.addManyToManyFilter( + name, + condition, + filterSource.shouldAutoInjectAliases(), + filterSource.getAliasToTableMap(), + filterSource.getAliasToEntityMap() ); - collectionBinding.setElement( elementBinding ); - // Collection#setWhere is used to set the "where" clause that applies to the collection table - // (which is the join table for a many-to-any association). - // This "where" clause comes from the collection mapping; e.g., - collectionBinding.setWhere( getPluralAttributeSource().getWhere() ); } } + private void bindOneToManyElement(PluralAttributeElementSourceOneToMany elementSource) { + final var elementBinding = + new OneToMany( mappingDocument, + collectionBinding.getOwner() ); + collectionBinding.setElement( elementBinding ); + final var referencedEntityBinding = + getReferencedEntityBinding( elementSource.getReferencedEntityName() ); + collectionBinding.setWhere( + getNonEmptyOrConjunctionIfBothNonEmpty( + referencedEntityBinding.getWhere(), + pluralAttributeSource.getWhere() + ) + ); + elementBinding.setReferencedEntityName( referencedEntityBinding.getEntityName() ); + elementBinding.setAssociatedClass( referencedEntityBinding ); + elementBinding.setIgnoreNotFound( elementSource.isIgnoreNotFound() ); + } + + private void bindEmbeddedElement(PluralAttributeElementSourceEmbedded elementSource) { + final var elementBinding = new Component( mappingDocument, collectionBinding ); + final var embeddableSource = elementSource.getEmbeddableSource(); + bindComponent( mappingDocument, embeddableSource, elementBinding, null, + embeddableSource.getAttributePathBase().getProperty(), false ); + collectionBinding.setElement( elementBinding ); + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (the table containing the embeddable elements) + // This "where" clause comes from the collection mapping; e.g., + collectionBinding.setWhere( pluralAttributeSource.getWhere() ); + } + + private void bindBasicElement(PluralAttributeElementSourceBasic elementSource) { + final var elementBinding = + new BasicValue( mappingDocument, + collectionBinding.getCollectionTable() ); + bindSimpleValueType( mappingDocument, elementSource.getExplicitHibernateTypeSource(), elementBinding ); + relationalObjectBinder.bindColumnsAndFormulas( + mappingDocument, + elementSource.getRelationalValueSources(), + elementBinding, + elementSource.areValuesNullableByDefault(), + context -> context.getMetadataCollector().getDatabase() + .toIdentifier( Collection.DEFAULT_ELEMENT_COLUMN_NAME ) + ); + collectionBinding.setElement( elementBinding ); + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (the table containing the basic elements) + // This "where" clause comes from the collection mapping; e.g., + collectionBinding.setWhere( pluralAttributeSource.getWhere() ); + } + + private static void handleFetchCharacteristics( + PluralAttributeElementSourceManyToMany elementSource, ManyToOne elementBinding) { + final var characteristics = elementSource.getFetchCharacteristics(); + elementBinding.setLazy( characteristics.getFetchTiming() != FetchTiming.IMMEDIATE ); + elementBinding.setFetchMode( + characteristics.getFetchStyle() == FetchStyle.SELECT + ? FetchMode.SELECT + : FetchMode.JOIN + ); + } + private PersistentClass getReferencedEntityBinding(String referencedEntityName) { final var entityBinding = mappingDocument.getMetadataCollector() @@ -3352,7 +3358,7 @@ private PersistentClass getReferencedEntityBinding(String referencedEntityName) getPluralAttributeSource().getAttributeRole().getFullPath(), referencedEntityName ), - getMappingDocument().getOrigin() + mappingDocument.getOrigin() ); } return entityBinding; @@ -3452,10 +3458,12 @@ protected void createBackReferences() { && !collectionBinding.isInverse() && !indexIsFormula ) { final var oneToMany = (OneToMany) collectionBinding.getElement(); - final String entityName = oneToMany.getReferencedEntityName(); - final var referenced = getMappingDocument().getMetadataCollector().getEntityBinding( entityName ); + final var referenced = + getMappingDocument().getMetadataCollector() + .getEntityBinding( oneToMany.getReferencedEntityName() ); final var backref = new IndexBackref(); - backref.setName( '_' + collectionBinding.getOwnerEntityName() + "." + getPluralAttributeSource().getName() + "IndexBackref" ); + backref.setName( '_' + collectionBinding.getOwnerEntityName() + + "." + getPluralAttributeSource().getName() + "IndexBackref" ); backref.setOptional( true ); backref.setUpdatable( false ); backref.setSelectable( false ); @@ -3531,10 +3539,12 @@ private void createIndexBackRef( && !collectionBinding.getKey().isNullable() && !collectionBinding.isInverse() ) { final var oneToMany = (OneToMany) collectionBinding.getElement(); - final String entityName = oneToMany.getReferencedEntityName(); - final var referenced = mappingDocument.getMetadataCollector().getEntityBinding( entityName ); + final var referenced = + mappingDocument.getMetadataCollector() + .getEntityBinding( oneToMany.getReferencedEntityName() ); final var backref = new IndexBackref(); - backref.setName( '_' + collectionBinding.getOwnerEntityName() + "." + pluralAttributeSource.getName() + "IndexBackref" ); + backref.setName( '_' + collectionBinding.getOwnerEntityName() + + "." + pluralAttributeSource.getName() + "IndexBackref" ); backref.setOptional( true ); backref.setUpdatable( false ); backref.setSelectable( false ); @@ -3575,7 +3585,6 @@ protected void bindCollectionIndex() { @Override protected void createBackReferences() { super.createBackReferences(); - createIndexBackRef( getMappingDocument(), getPluralAttributeSource(), @@ -3591,7 +3600,9 @@ public void bindListOrArrayIndex( final var indexSource = (PluralAttributeSequentialIndexSource) attributeSource.getIndexSource(); - final var indexBinding = new BasicValue( mappingDocument, collectionBinding.getCollectionTable() ); + final var indexBinding = + new BasicValue( mappingDocument, + collectionBinding.getCollectionTable() ); bindSimpleValueType( mappingDocument, indexSource.getTypeInformation(), indexBinding ); relationalObjectBinder.bindColumnsAndFormulas( @@ -3624,70 +3635,108 @@ private void bindMapKey( final org.hibernate.mapping.Map collectionBinding) { final var indexSource = pluralAttributeSource.getIndexSource(); if ( indexSource instanceof PluralAttributeMapKeySourceBasic mapKeySource ) { - final var value = new BasicValue( mappingDocument, collectionBinding.getCollectionTable() ); - bindSimpleValueType( mappingDocument, mapKeySource.getTypeInformation(), value ); - if ( !value.isTypeSpecified() ) { - throw new MappingException( - "map index element must specify a type: " - + pluralAttributeSource.getAttributeRole().getFullPath(), - mappingDocument.getOrigin() - ); - } - - relationalObjectBinder.bindColumnsAndFormulas( - mappingDocument, - mapKeySource.getRelationalValueSources(), - value, - true, - context -> database.toIdentifier( IndexedCollection.DEFAULT_INDEX_COLUMN_NAME ) - ); - - collectionBinding.setIndex( value ); + bindBasicMapKey( mappingDocument, pluralAttributeSource, collectionBinding, mapKeySource ); } else if ( indexSource instanceof PluralAttributeMapKeySourceEmbedded mapKeySource ) { - final var componentBinding = new Component( mappingDocument, collectionBinding ); - bindComponent( - mappingDocument, - mapKeySource.getEmbeddableSource(), - componentBinding, - null, - pluralAttributeSource.getName(), - false - ); - collectionBinding.setIndex( componentBinding ); + bindEmbeddedMapKey( mappingDocument, pluralAttributeSource, collectionBinding, mapKeySource ); } else if ( indexSource instanceof PluralAttributeMapKeyManyToManySource mapKeySource ) { - final var mapKeyBinding = new ManyToOne( mappingDocument, collectionBinding.getCollectionTable() ); + bindManyToManyMapKey( mappingDocument, pluralAttributeSource, collectionBinding, mapKeySource ); + } + else if ( indexSource instanceof PluralAttributeMapKeyManyToAnySource mapKeySource) { + bindManyToAnyMapKey( mappingDocument, pluralAttributeSource, collectionBinding, mapKeySource ); + } + } - mapKeyBinding.setReferencedEntityName( mapKeySource.getReferencedEntityName() ); + private void bindManyToAnyMapKey( + MappingDocument mappingDocument, + IndexedPluralAttributeSource pluralAttributeSource, + org.hibernate.mapping.Map collectionBinding, + PluralAttributeMapKeyManyToAnySource mapKeySource) { + final var mapKeyBinding = + new Any( mappingDocument, + collectionBinding.getCollectionTable() ); + bindAny( mappingDocument, mapKeySource, mapKeyBinding, + pluralAttributeSource.getAttributeRole().append( "key" ) ); + collectionBinding.setIndex( mapKeyBinding ); + } - relationalObjectBinder.bindColumnsAndFormulas( - mappingDocument, - mapKeySource.getRelationalValueSources(), - mapKeyBinding, - true, - context -> implicitNamingStrategy.determineMapKeyColumnName( - new ImplicitMapKeyColumnNameSource() { - @Override - public AttributePath getPluralAttributePath() { - return pluralAttributeSource.getAttributePath(); - } + private void bindManyToManyMapKey( + MappingDocument mappingDocument, + IndexedPluralAttributeSource pluralAttributeSource, + org.hibernate.mapping.Map collectionBinding, + PluralAttributeMapKeyManyToManySource mapKeySource) { + final var mapKeyBinding = + new ManyToOne( mappingDocument, + collectionBinding.getCollectionTable() ); - @Override - public MetadataBuildingContext getBuildingContext() { - return context; - } + mapKeyBinding.setReferencedEntityName( mapKeySource.getReferencedEntityName() ); + + relationalObjectBinder.bindColumnsAndFormulas( + mappingDocument, + mapKeySource.getRelationalValueSources(), + mapKeyBinding, + true, + context -> implicitNamingStrategy.determineMapKeyColumnName( + new ImplicitMapKeyColumnNameSource() { + @Override + public AttributePath getPluralAttributePath() { + return pluralAttributeSource.getAttributePath(); } - ) + + @Override + public MetadataBuildingContext getBuildingContext() { + return context; + } + } + ) + ); + collectionBinding.setIndex( mapKeyBinding ); + } + + private void bindEmbeddedMapKey( + MappingDocument mappingDocument, + IndexedPluralAttributeSource pluralAttributeSource, + org.hibernate.mapping.Map collectionBinding, + PluralAttributeMapKeySourceEmbedded mapKeySource) { + final var componentBinding = new Component( mappingDocument, collectionBinding ); + bindComponent( + mappingDocument, + mapKeySource.getEmbeddableSource(), + componentBinding, + null, + pluralAttributeSource.getName(), + false + ); + collectionBinding.setIndex( componentBinding ); + } + + private void bindBasicMapKey( + MappingDocument mappingDocument, + IndexedPluralAttributeSource pluralAttributeSource, + org.hibernate.mapping.Map collectionBinding, + PluralAttributeMapKeySourceBasic mapKeySource) { + final var value = + new BasicValue( mappingDocument, + collectionBinding.getCollectionTable() ); + bindSimpleValueType( mappingDocument, mapKeySource.getTypeInformation(), value ); + if ( !value.isTypeSpecified() ) { + throw new MappingException( + "map index element must specify a type: " + + pluralAttributeSource.getAttributeRole().getFullPath(), + mappingDocument.getOrigin() ); - collectionBinding.setIndex( mapKeyBinding ); - } - else if ( indexSource instanceof PluralAttributeMapKeyManyToAnySource mapKeySource) { - final var mapKeyBinding = new Any( mappingDocument, collectionBinding.getCollectionTable() ); - bindAny( mappingDocument, mapKeySource, mapKeyBinding, - pluralAttributeSource.getAttributeRole().append( "key" ) ); - collectionBinding.setIndex( mapKeyBinding ); } + + relationalObjectBinder.bindColumnsAndFormulas( + mappingDocument, + mapKeySource.getRelationalValueSources(), + value, + true, + context -> database.toIdentifier( IndexedCollection.DEFAULT_INDEX_COLUMN_NAME ) + ); + + collectionBinding.setIndex( value ); } private class ManyToOneColumnBinder implements ImplicitColumnNamingSecondPass { @@ -3727,7 +3776,8 @@ public boolean canProcessImmediately() { } final var referencedEntityBinding = - mappingDocument.getMetadataCollector().getEntityBinding( referencedEntityName ); + mappingDocument.getMetadataCollector() + .getEntityBinding( referencedEntityName ); if ( referencedEntityBinding == null ) { return false; } @@ -3759,7 +3809,8 @@ public void doSecondPass(Map persistentClasses) { // column making up the FK. final var referencedEntityBinding = - mappingDocument.getMetadataCollector().getEntityBinding( referencedEntityName ); + mappingDocument.getMetadataCollector() + .getEntityBinding( referencedEntityName ); if ( referencedEntityBinding == null ) { throw new AssertionFailure( @@ -3810,7 +3861,8 @@ private ManyToOneFkSecondPass( String referencedEntityName) { if ( referencedEntityName == null ) { throw new MappingException( - "entity name referenced by many-to-one required [" + manyToOneSource.getAttributeRole().getFullPath() + "]", + "entity name referenced by many-to-one required [" + + manyToOneSource.getAttributeRole().getFullPath() + "]", mappingDocument.getOrigin() ); } @@ -3841,7 +3893,9 @@ public void doSecondPass(Map persistentClasses) throws manyToOneBinding.createForeignKey(); } else { - manyToOneBinding.createPropertyRefConstraints( mappingDocument.getMetadataCollector().getEntityBindingMap() ); + manyToOneBinding.createPropertyRefConstraints( + mappingDocument.getMetadataCollector() + .getEntityBindingMap() ); } } @@ -3945,10 +3999,14 @@ public Identifier getUserProvidedIdentifier() { } } ); - uniqueKey.setName( uniqueKeyName.render( mappingDocument.getMetadataCollector().getDatabase().getDialect() ) ); + uniqueKey.setName( uniqueKeyName.render( getDialect() ) ); entityBinding.getTable().addUniqueKey( uniqueKey ); } + + private Dialect getDialect() { + return mappingDocument.getMetadataCollector().getDatabase().getDialect(); + } } private String columns(Value value) { @@ -3962,7 +4020,7 @@ private String columns(Value value) { return builder.toString(); } - private static OnDeleteAction getOnDeleteAction(boolean entitySource) { - return entitySource ? OnDeleteAction.CASCADE : OnDeleteAction.NO_ACTION; + private static OnDeleteAction getOnDeleteAction(ForeignKeyContributingSource entitySource) { + return entitySource.isCascadeDeleteEnabled() ? OnDeleteAction.CASCADE : OnDeleteAction.NO_ACTION; } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/HibernateAnnotations.java b/hibernate-core/src/main/java/org/hibernate/boot/models/HibernateAnnotations.java index 7c343f3bac4b..08f5522c47c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/HibernateAnnotations.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/HibernateAnnotations.java @@ -475,6 +475,10 @@ public interface HibernateAnnotations { NaturalIdCache.class, NaturalIdCacheAnnotation.class ); + OrmAnnotationDescriptor NATURAL_ID_CLASS = new OrmAnnotationDescriptor<>( + NaturalIdClass.class, + NaturalIdClassAnnotation.class + ); OrmAnnotationDescriptor NOT_FOUND = new OrmAnnotationDescriptor<>( NotFound.class, NotFoundAnnotation.class diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/NaturalIdClassAnnotation.java b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/NaturalIdClassAnnotation.java new file mode 100644 index 000000000000..654b5e292dc6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/NaturalIdClassAnnotation.java @@ -0,0 +1,53 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.boot.models.annotations.internal; + +import org.hibernate.annotations.NaturalIdClass; +import org.hibernate.models.spi.ModelsContext; + +import java.lang.annotation.Annotation; +import java.util.Map; + +@SuppressWarnings({ "ClassExplicitlyAnnotation", "unused" }) +@jakarta.annotation.Generated("org.hibernate.orm.build.annotations.ClassGeneratorProcessor") +public class NaturalIdClassAnnotation implements NaturalIdClass { + private Class value; + + /** + * Used in creating dynamic annotation instances (e.g. from XML) + */ + public NaturalIdClassAnnotation(ModelsContext modelContext) { + this.value = void.class; + } + + /** + * Used in creating annotation instances from JDK variant + */ + public NaturalIdClassAnnotation(NaturalIdClass annotation, ModelsContext modelContext) { + this.value = annotation.value(); + } + + /** + * Used in creating annotation instances from Jandex variant + */ + public NaturalIdClassAnnotation(Map attributeValues, ModelsContext modelContext) { + this.value = (Class) attributeValues.get( "value" ); + } + + @Override + public Class annotationType() { + return NaturalIdClass.class; + } + + @Override + public Class value() { + return value; + } + + public void value(Class value) { + this.value = value; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/internal/GlobalRegistrationsImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/models/internal/GlobalRegistrationsImpl.java index 674c4e1a73ec..bb29a06bef95 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/internal/GlobalRegistrationsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/internal/GlobalRegistrationsImpl.java @@ -157,8 +157,7 @@ public GlobalRegistrationsImpl(ModelsContext sourceModelContext, BootstrapContex @Override public T as(Class type) { - //noinspection unchecked - return (T) this; + return type.cast( this ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/DynamicModelHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/DynamicModelHelper.java index 9894f3953083..fb689fb3df4b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/DynamicModelHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/DynamicModelHelper.java @@ -11,26 +11,20 @@ import org.hibernate.boot.jaxb.mapping.spi.JaxbAnyMapping; import org.hibernate.boot.jaxb.mapping.spi.JaxbAssociationAttribute; import org.hibernate.boot.jaxb.mapping.spi.JaxbAttributesContainer; -import org.hibernate.boot.jaxb.mapping.spi.JaxbAttributesContainerImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbBasicImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbBasicMapping; import org.hibernate.boot.jaxb.mapping.spi.JaxbElementCollectionImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbEmbeddable; -import org.hibernate.boot.jaxb.mapping.spi.JaxbEmbeddedIdImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbEmbeddedMapping; import org.hibernate.boot.jaxb.mapping.spi.JaxbEntity; import org.hibernate.boot.jaxb.mapping.spi.JaxbEntityImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbManagedType; import org.hibernate.boot.jaxb.mapping.spi.JaxbMappedSuperclassImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbPluralAttribute; -import org.hibernate.boot.jaxb.mapping.spi.JaxbTenantIdImpl; -import org.hibernate.boot.jaxb.mapping.spi.JaxbUserTypeImpl; import org.hibernate.boot.models.internal.ModelsHelper; import org.hibernate.boot.models.xml.UnknownAttributeTypeException; import org.hibernate.boot.models.xml.spi.XmlDocumentContext; import org.hibernate.boot.spi.BootstrapContext; -import org.hibernate.internal.util.StringHelper; -import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.models.internal.ClassTypeDetailsImpl; import org.hibernate.models.internal.MutableClassDetailsRegistry; import org.hibernate.models.internal.ParameterizedTypeDetailsImpl; @@ -39,7 +33,6 @@ import org.hibernate.models.internal.dynamic.DynamicFieldDetails; import org.hibernate.models.internal.jdk.JdkClassDetails; import org.hibernate.models.spi.ClassDetails; -import org.hibernate.models.spi.ClassDetailsRegistry; import org.hibernate.models.spi.ModelsContext; import org.hibernate.models.spi.MutableClassDetails; import org.hibernate.models.spi.TypeDetails; @@ -58,6 +51,9 @@ import static org.hibernate.internal.util.NullnessHelper.nullif; import static org.hibernate.internal.util.ReflectHelper.OBJECT_CLASS_NAME; import static org.hibernate.internal.util.StringHelper.isNotEmpty; +import static org.hibernate.internal.util.StringHelper.isNotEmpty; +import static org.hibernate.internal.util.StringHelper.nullIfEmpty; +import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty; import static org.hibernate.models.internal.ModifierUtils.DYNAMIC_ATTRIBUTE_MODIFIERS; /** @@ -74,10 +70,9 @@ static void prepareDynamicClass( JaxbManagedType jaxbManagedType, XmlDocumentContext xmlDocumentContext) { if ( jaxbManagedType instanceof JaxbEntityImpl jaxbDynamicEntity ) { - final JaxbAttributesContainerImpl attributes = jaxbDynamicEntity.getAttributes(); - + final var attributes = jaxbDynamicEntity.getAttributes(); if ( attributes != null ) { - if ( CollectionHelper.isNotEmpty( attributes.getIdAttributes() ) ) { + if ( isNotEmpty( attributes.getIdAttributes() ) ) { // attributes.getIdAttributes().forEach( (jaxbId) -> { final TypeDetails attributeJavaType = determineAttributeJavaTypeDetails( @@ -99,13 +94,13 @@ static void prepareDynamicClass( } else if ( attributes.getEmbeddedIdAttribute() != null ) { // - final JaxbEmbeddedIdImpl embeddedId = attributes.getEmbeddedIdAttribute(); - final TypeDetails attributeJavaType = determineAttributeJavaTypeDetails( + final var embeddedId = attributes.getEmbeddedIdAttribute(); + final var attributeJavaType = determineAttributeJavaTypeDetails( jaxbManagedType, embeddedId, xmlDocumentContext ); - final DynamicFieldDetails member = new DynamicFieldDetails( + final var member = new DynamicFieldDetails( embeddedId.getName(), attributeJavaType, classDetails, @@ -118,14 +113,15 @@ else if ( attributes.getEmbeddedIdAttribute() != null ) { } // - if ( attributes.getNaturalId() != null ) { - attributes.getNaturalId().getBasicAttributes().forEach( (jaxbBasic) -> { - final TypeDetails attributeJavaType = determineAttributeJavaTypeDetails( + final var naturalId = attributes.getNaturalId(); + if ( naturalId != null ) { + naturalId.getBasicAttributes().forEach( (jaxbBasic) -> { + final var attributeJavaType = determineAttributeJavaTypeDetails( jaxbManagedType, jaxbBasic, xmlDocumentContext ); - final DynamicFieldDetails member = new DynamicFieldDetails( + final var member = new DynamicFieldDetails( jaxbBasic.getName(), attributeJavaType, classDetails, @@ -137,13 +133,13 @@ else if ( attributes.getEmbeddedIdAttribute() != null ) { classDetails.addField( member ); } ); - attributes.getNaturalId().getEmbeddedAttributes().forEach( (jaxbEmbedded) -> { - final TypeDetails attributeJavaType = determineAttributeJavaTypeDetails( + naturalId.getEmbeddedAttributes().forEach( (jaxbEmbedded) -> { + final var attributeJavaType = determineAttributeJavaTypeDetails( jaxbManagedType, jaxbEmbedded, xmlDocumentContext ); - final DynamicFieldDetails member = new DynamicFieldDetails( + final var member = new DynamicFieldDetails( jaxbEmbedded.getName(), attributeJavaType, classDetails, @@ -155,12 +151,12 @@ else if ( attributes.getEmbeddedIdAttribute() != null ) { classDetails.addField( member ); } ); - attributes.getNaturalId().getManyToOneAttributes().forEach( (jaxbManyToOne) -> { - final TypeDetails attributeJavaType = determineAttributeJavaTypeDetails( + naturalId.getManyToOneAttributes().forEach( (jaxbManyToOne) -> { + final var attributeJavaType = determineAttributeJavaTypeDetails( jaxbManyToOne, xmlDocumentContext ); - final DynamicFieldDetails member = new DynamicFieldDetails( + final var member = new DynamicFieldDetails( jaxbManyToOne.getName(), attributeJavaType, classDetails, @@ -172,12 +168,12 @@ else if ( attributes.getEmbeddedIdAttribute() != null ) { classDetails.addField( member ); } ); - attributes.getNaturalId().getAnyMappingAttributes().forEach( (jaxbAnyMapping) -> { - final TypeDetails attributeJavaType = determineAttributeJavaTypeDetails( + naturalId.getAnyMappingAttributes().forEach( (jaxbAnyMapping) -> { + final var attributeJavaType = determineAttributeJavaTypeDetails( jaxbAnyMapping, xmlDocumentContext ); - final DynamicFieldDetails member = new DynamicFieldDetails( + final var member = new DynamicFieldDetails( jaxbAnyMapping.getName(), attributeJavaType, classDetails, @@ -192,14 +188,14 @@ else if ( attributes.getEmbeddedIdAttribute() != null ) { } // - final JaxbTenantIdImpl tenantId = jaxbDynamicEntity.getTenantId(); + final var tenantId = jaxbDynamicEntity.getTenantId(); if ( tenantId != null ) { - final TypeDetails attributeJavaType = determineAttributeJavaTypeDetails( + final var attributeJavaType = determineAttributeJavaTypeDetails( jaxbManagedType, tenantId, xmlDocumentContext ); - final DynamicFieldDetails member = new DynamicFieldDetails( + final var member = new DynamicFieldDetails( tenantId.getName(), attributeJavaType, classDetails, @@ -212,18 +208,17 @@ else if ( attributes.getEmbeddedIdAttribute() != null ) { } } else if ( jaxbManagedType instanceof JaxbMappedSuperclassImpl jaxbMappedSuperclass ) { - final JaxbAttributesContainerImpl attributes = jaxbMappedSuperclass.getAttributes(); - + final var attributes = jaxbMappedSuperclass.getAttributes(); if ( attributes != null ) { - if ( CollectionHelper.isNotEmpty( attributes.getIdAttributes() ) ) { + if ( isNotEmpty( attributes.getIdAttributes() ) ) { // attributes.getIdAttributes().forEach( (jaxbId) -> { - final TypeDetails attributeJavaType = determineAttributeJavaTypeDetails( + final var attributeJavaType = determineAttributeJavaTypeDetails( jaxbManagedType, jaxbId, xmlDocumentContext ); - final DynamicFieldDetails member = new DynamicFieldDetails( + final var member = new DynamicFieldDetails( jaxbId.getName(), attributeJavaType, classDetails, @@ -237,13 +232,13 @@ else if ( jaxbManagedType instanceof JaxbMappedSuperclassImpl jaxbMappedSupercla } else { // - final JaxbEmbeddedIdImpl embeddedId = attributes.getEmbeddedIdAttribute(); - final TypeDetails attributeJavaType = determineAttributeJavaTypeDetails( + final var embeddedId = attributes.getEmbeddedIdAttribute(); + final var attributeJavaType = determineAttributeJavaTypeDetails( jaxbManagedType, embeddedId, xmlDocumentContext ); - final DynamicFieldDetails member = new DynamicFieldDetails( + final var member = new DynamicFieldDetails( embeddedId.getName(), attributeJavaType, classDetails, @@ -262,7 +257,7 @@ else if ( jaxbManagedType instanceof JaxbMappedSuperclassImpl jaxbMappedSupercla if ( attributes != null ) { // attributes.getBasicAttributes().forEach( (jaxbBasic) -> { - final DynamicFieldDetails member = new DynamicFieldDetails( + final var member = new DynamicFieldDetails( jaxbBasic.getName(), determineAttributeJavaTypeDetails( jaxbManagedType, jaxbBasic, xmlDocumentContext ), classDetails, @@ -276,7 +271,7 @@ else if ( jaxbManagedType instanceof JaxbMappedSuperclassImpl jaxbMappedSupercla // attributes.getEmbeddedAttributes().forEach( (jaxbEmbedded) -> { - final DynamicFieldDetails member = new DynamicFieldDetails( + final var member = new DynamicFieldDetails( jaxbEmbedded.getName(), determineAttributeJavaTypeDetails( jaxbManagedType, jaxbEmbedded, xmlDocumentContext ), classDetails, @@ -290,7 +285,7 @@ else if ( jaxbManagedType instanceof JaxbMappedSuperclassImpl jaxbMappedSupercla // attributes.getOneToOneAttributes().forEach( (jaxbOneToOne) -> { - final DynamicFieldDetails member = new DynamicFieldDetails( + final var member = new DynamicFieldDetails( jaxbOneToOne.getName(), determineAttributeJavaTypeDetails( jaxbOneToOne, xmlDocumentContext ), classDetails, @@ -304,7 +299,7 @@ else if ( jaxbManagedType instanceof JaxbMappedSuperclassImpl jaxbMappedSupercla // attributes.getManyToOneAttributes().forEach( (jaxbManyToOne) -> { - final DynamicFieldDetails member = new DynamicFieldDetails( + final var member = new DynamicFieldDetails( jaxbManyToOne.getName(), determineAttributeJavaTypeDetails( jaxbManyToOne, xmlDocumentContext ), classDetails, @@ -318,7 +313,7 @@ else if ( jaxbManagedType instanceof JaxbMappedSuperclassImpl jaxbMappedSupercla // attributes.getAnyMappingAttributes().forEach( (jaxbAnyMapping) -> { - final DynamicFieldDetails member = new DynamicFieldDetails( + final var member = new DynamicFieldDetails( jaxbAnyMapping.getName(), determineAttributeJavaTypeDetails( jaxbAnyMapping, xmlDocumentContext ), classDetails, @@ -332,8 +327,8 @@ else if ( jaxbManagedType instanceof JaxbMappedSuperclassImpl jaxbMappedSupercla // attributes.getElementCollectionAttributes().forEach( (jaxbElementCollection) -> { - final TypeDetails elementType = determineAttributeJavaTypeDetails( jaxbElementCollection, xmlDocumentContext ); - final DynamicFieldDetails member = new DynamicFieldDetails( + final var elementType = determineAttributeJavaTypeDetails( jaxbElementCollection, xmlDocumentContext ); + final var member = new DynamicFieldDetails( jaxbElementCollection.getName(), makeCollectionType( classDetails, jaxbElementCollection, elementType, xmlDocumentContext ), classDetails, @@ -347,8 +342,8 @@ else if ( jaxbManagedType instanceof JaxbMappedSuperclassImpl jaxbMappedSupercla // attributes.getOneToManyAttributes().forEach( (jaxbOneToMany) -> { - final TypeDetails elementType = determineAttributeJavaTypeDetails( jaxbOneToMany, xmlDocumentContext ); - final DynamicFieldDetails member = new DynamicFieldDetails( + final var elementType = determineAttributeJavaTypeDetails( jaxbOneToMany, xmlDocumentContext ); + final var member = new DynamicFieldDetails( jaxbOneToMany.getName(), // todo : this is wrong. should be the collection-type (List, ...) // wrapping the result from determineAttributeJavaTypeDetails @@ -364,8 +359,8 @@ else if ( jaxbManagedType instanceof JaxbMappedSuperclassImpl jaxbMappedSupercla // attributes.getManyToManyAttributes().forEach( (jaxbManyToMany) -> { - final TypeDetails elementType = determineAttributeJavaTypeDetails( jaxbManyToMany, xmlDocumentContext ); - final DynamicFieldDetails member = new DynamicFieldDetails( + final var elementType = determineAttributeJavaTypeDetails( jaxbManyToMany, xmlDocumentContext ); + final var member = new DynamicFieldDetails( jaxbManyToMany.getName(), makeCollectionType( classDetails, jaxbManyToMany, elementType, xmlDocumentContext ), classDetails, @@ -379,11 +374,11 @@ else if ( jaxbManagedType instanceof JaxbMappedSuperclassImpl jaxbMappedSupercla // attributes.getPluralAnyMappingAttributes().forEach( (jaxbPluralAnyMapping) -> { - final TypeDetails attributeType = determineAttributeJavaTypeDetails( + final var attributeType = determineAttributeJavaTypeDetails( jaxbPluralAnyMapping, xmlDocumentContext ); - final DynamicFieldDetails member = new DynamicFieldDetails( + final var member = new DynamicFieldDetails( jaxbPluralAnyMapping.getName(), attributeType, classDetails, @@ -409,7 +404,7 @@ private static TypeDetails makeCollectionType( JaxbPluralAttribute jaxbPluralAttribute, TypeDetails elementType, XmlDocumentContext xmlDocumentContext) { - final MutableClassDetailsRegistry classDetailsRegistry = xmlDocumentContext + final var classDetailsRegistry = xmlDocumentContext .getBootstrapContext() .getModelsContext() .getClassDetailsRegistry() @@ -484,9 +479,9 @@ private static ClassDetails setType(JaxbPluralAttribute jaxbPluralAttribute, Mut } private static boolean isSorted(JaxbPluralAttribute jaxbPluralAttribute) { - return StringHelper.isNotEmpty( jaxbPluralAttribute.getSort() ) + return isNotEmpty( jaxbPluralAttribute.getSort() ) || jaxbPluralAttribute.getSortNatural() != null - || StringHelper.isNotEmpty( jaxbPluralAttribute.getOrderBy() ); + || isNotEmpty( jaxbPluralAttribute.getOrderBy() ); } private static ClassDetails mapType(JaxbPluralAttribute jaxbPluralAttribute, MutableClassDetailsRegistry classDetailsRegistry) { @@ -523,7 +518,7 @@ private static ClassDetails determineAttributeJavaType( // explicit final String target = jaxbBasicMapping.getTarget(); if ( isNotEmpty( target ) ) { - final SimpleTypeInterpretation simpleTypeInterpretation = SimpleTypeInterpretation.interpret( target ); + final var simpleTypeInterpretation = SimpleTypeInterpretation.interpret( target ); if ( simpleTypeInterpretation == null ) { throw new UnknownAttributeTypeException( String.format( @@ -541,7 +536,7 @@ private static ClassDetails determineAttributeJavaType( final ModelsContext modelsContext = bootstrapContext.getModelsContext(); // UserType - final JaxbUserTypeImpl userTypeNode = jaxbBasicMapping.getType(); + final var userTypeNode = jaxbBasicMapping.getType(); if ( userTypeNode != null ) { final String userTypeImplName = userTypeNode.getValue(); if ( isNotEmpty( userTypeImplName ) ) { @@ -556,10 +551,10 @@ private static ClassDetails determineAttributeJavaType( // JavaType final String javaTypeImplName = jaxbBasicMapping.getJavaType(); if ( isNotEmpty( javaTypeImplName ) ) { - final ClassDetails javaTypeImplDetails = xmlDocumentContext.resolveJavaType( javaTypeImplName ); + final var javaTypeImplDetails = xmlDocumentContext.resolveJavaType( javaTypeImplName ); // safe to convert to class, though unfortunate to have to instantiate it... final JavaType javaType = createInstance( javaTypeImplDetails ); - final Class modelClass = javaType.getJavaTypeClass(); + final var modelClass = javaType.getJavaTypeClass(); return modelsContext.getClassDetailsRegistry().getClassDetails( modelClass.getName() ); } @@ -568,7 +563,7 @@ private static ClassDetails determineAttributeJavaType( final Integer jdbcTypeCode = jaxbBasicMapping.getJdbcTypeCode(); final JdbcType jdbcType; if ( isNotEmpty( jdbcTypeImplName ) ) { - final ClassDetails jdbcTypeImplDetails = xmlDocumentContext.resolveJavaType( javaTypeImplName ); + final var jdbcTypeImplDetails = xmlDocumentContext.resolveJavaType( javaTypeImplName ); jdbcType = createInstance( jdbcTypeImplDetails ); } else if ( jdbcTypeCode != null ) { @@ -578,13 +573,13 @@ else if ( jdbcTypeCode != null ) { jdbcType = null; } if ( jdbcType != null ) { - final JavaType javaType = jdbcType.getJdbcRecommendedJavaTypeMapping( 0, 0, bootstrapContext.getTypeConfiguration() ); - final Class modelClass = javaType.getJavaTypeClass(); + final var javaType = jdbcType.getRecommendedJavaType( 0, 0, bootstrapContext.getTypeConfiguration() ); + final var modelClass = javaType.getJavaTypeClass(); return modelsContext.getClassDetailsRegistry().getClassDetails( modelClass.getName() ); } if ( jaxbBasicMapping instanceof JaxbBasicImpl jaxbBasicAttribute ) { - final TemporalType temporalType = jaxbBasicAttribute.getTemporal(); + final var temporalType = jaxbBasicAttribute.getTemporal(); if ( temporalType != null ) { return resolveTemporalJavaType( temporalType, xmlDocumentContext ); } @@ -592,10 +587,10 @@ else if ( jdbcTypeCode != null ) { final String declaringTypeName; if ( declaringType instanceof JaxbEntity jaxbEntity ) { - declaringTypeName = StringHelper.nullIfEmpty( jaxbEntity.getName() ); + declaringTypeName = nullIfEmpty( jaxbEntity.getName() ); } else if ( declaringType instanceof JaxbEmbeddable jaxbEmbeddable ) { - declaringTypeName = StringHelper.nullIfEmpty( jaxbEmbeddable.getName() ); + declaringTypeName = nullIfEmpty( jaxbEmbeddable.getName() ); } else { declaringTypeName = null; @@ -617,16 +612,16 @@ else if ( declaringType instanceof JaxbEmbeddable jaxbEmbeddable ) { * SimpleTypeInterpretation we only care about the wrapper. */ private static ClassDetails resolveBasicMappingTarget(SimpleTypeInterpretation targetInterpretation, XmlDocumentContext xmlDocumentContext) { - final ModelsContext modelsContext = xmlDocumentContext.getBootstrapContext().getModelsContext(); - final ClassDetailsRegistry classDetailsRegistry = modelsContext.getClassDetailsRegistry(); + final var modelsContext = xmlDocumentContext.getBootstrapContext().getModelsContext(); + final var classDetailsRegistry = modelsContext.getClassDetailsRegistry(); return classDetailsRegistry.resolveClassDetails( targetInterpretation.getJavaType().getName() ); } private static MutableClassDetails resolveTemporalJavaType( TemporalType temporalType, XmlDocumentContext xmlDocumentContext) { - final ModelsContext modelsContext = xmlDocumentContext.getBootstrapContext().getModelsContext(); - final MutableClassDetailsRegistry classDetailsRegistry = modelsContext.getClassDetailsRegistry().as( MutableClassDetailsRegistry.class ); + final var modelsContext = xmlDocumentContext.getBootstrapContext().getModelsContext(); + final var classDetailsRegistry = modelsContext.getClassDetailsRegistry().as( MutableClassDetailsRegistry.class ); switch ( temporalType ) { case DATE -> { return (MutableClassDetails) classDetailsRegistry.resolveClassDetails( @@ -682,8 +677,8 @@ private static TypeDetails determineAttributeJavaTypeDetails( XmlDocumentContext xmlDocumentContext) { final String target = jaxbAssociationAttribute.getTargetEntity(); if ( isNotEmpty( target ) ) { - final ModelsContext modelsContext = xmlDocumentContext.getBootstrapContext().getModelsContext(); - final ClassDetails classDetails = ModelsHelper.resolveClassDetails( + final var modelsContext = xmlDocumentContext.getBootstrapContext().getModelsContext(); + final var classDetails = ModelsHelper.resolveClassDetails( target, modelsContext.getClassDetailsRegistry(), () -> new DynamicClassDetails( @@ -711,8 +706,8 @@ private static TypeDetails determineAttributeJavaTypeDetails( XmlDocumentContext xmlDocumentContext) { // Logically this is Object, which is what we return here for now. // todo : might be nice to allow specifying a "common interface" - final ModelsContext modelsContext = xmlDocumentContext.getBootstrapContext().getModelsContext(); - final ClassDetails objectClassDetails = modelsContext.getClassDetailsRegistry().resolveClassDetails( OBJECT_CLASS_NAME ); + final var modelsContext = xmlDocumentContext.getBootstrapContext().getModelsContext(); + final var objectClassDetails = modelsContext.getClassDetailsRegistry().resolveClassDetails( OBJECT_CLASS_NAME ); return new ClassTypeDetailsImpl( objectClassDetails, TypeDetails.Kind.CLASS ); } @@ -723,7 +718,7 @@ private static TypeDetails determineAttributeJavaTypeDetails( private static TypeDetails determineAttributeJavaTypeDetails( JaxbElementCollectionImpl jaxbElementCollection, XmlDocumentContext xmlDocumentContext) { - final LimitedCollectionClassification classification = nullif( jaxbElementCollection.getClassification(), LimitedCollectionClassification.BAG ); + final var classification = nullif( jaxbElementCollection.getClassification(), LimitedCollectionClassification.BAG ); return switch ( classification ) { case BAG -> resolveCollectionType( Collection.class, xmlDocumentContext ); case LIST -> resolveCollectionType( List.class, xmlDocumentContext ); @@ -733,7 +728,7 @@ private static TypeDetails determineAttributeJavaTypeDetails( } private static TypeDetails resolveCollectionType(Class collectionType, XmlDocumentContext xmlDocumentContext) { - final ClassDetails classDetails = xmlDocumentContext + final var classDetails = xmlDocumentContext .getBootstrapContext() .getModelsContext() .getClassDetailsRegistry() diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/XmlAnnotationHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/XmlAnnotationHelper.java index 36608dcaed68..54bb3cb56d32 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/XmlAnnotationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/XmlAnnotationHelper.java @@ -20,6 +20,7 @@ import java.time.OffsetTime; import java.time.ZonedDateTime; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import java.util.UUID; import java.util.function.Consumer; @@ -32,6 +33,7 @@ import org.hibernate.annotations.ResultCheckStyle; import org.hibernate.annotations.SecondaryRow; import org.hibernate.boot.internal.LimitedCollectionClassification; +import org.hibernate.boot.jaxb.mapping.GenerationTiming; import org.hibernate.boot.jaxb.mapping.spi.JaxbAssociationOverrideImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbAttributeOverrideImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbBasicMapping; @@ -90,6 +92,7 @@ import org.hibernate.boot.models.xml.spi.XmlDocument; import org.hibernate.boot.models.xml.spi.XmlDocumentContext; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; +import org.hibernate.generator.EventType; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.models.ModelsException; @@ -479,6 +482,27 @@ public static void applyNationalized( memberDetails.applyAnnotationUsage( HibernateAnnotations.NATIONALIZED, xmlDocumentContext.getModelBuildingContext() ); } + public static void applyGenerated( + GenerationTiming timing, + MutableMemberDetails memberDetails, + XmlDocumentContext xmlDocumentContext) { + if ( timing == null ) { + return; + } + + EnumSet eventTypes = timing.getEventTypes(); + if ( eventTypes == null ) { + return; + } + + final GeneratedAnnotation generatedAnn = (GeneratedAnnotation) memberDetails.applyAnnotationUsage( + HibernateAnnotations.GENERATED, + xmlDocumentContext.getModelBuildingContext() + ); + + generatedAnn.event( eventTypes.toArray( new EventType[0] ) ); + } + public static void applyGeneratedValue( JaxbGeneratedValueImpl jaxbGeneratedValue, MutableMemberDetails memberDetails, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/attr/BasicAttributeProcessing.java b/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/attr/BasicAttributeProcessing.java index 60628ed0b286..8849ddd0df24 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/attr/BasicAttributeProcessing.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/attr/BasicAttributeProcessing.java @@ -78,8 +78,8 @@ else if ( jaxbBasic.getColumn() != null ) { XmlAnnotationHelper.applyLob( jaxbBasic.getLob(), memberDetails, xmlDocumentContext ); XmlAnnotationHelper.applyEnumerated( jaxbBasic.getEnumerated(), memberDetails, xmlDocumentContext ); XmlAnnotationHelper.applyNationalized( jaxbBasic.getNationalized(), memberDetails, xmlDocumentContext ); + XmlAnnotationHelper.applyGenerated( jaxbBasic.getGenerated(), memberDetails, xmlDocumentContext ); - // todo : value generation // todo : ... return memberDetails; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/query/HbmResultSetMappingDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/query/HbmResultSetMappingDescriptor.java index 4f41cab22f95..ebcc4de5a834 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/query/HbmResultSetMappingDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/query/HbmResultSetMappingDescriptor.java @@ -49,7 +49,6 @@ import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.FetchableContainer; import org.hibernate.sql.results.graph.entity.EntityValuedFetchable; -import org.hibernate.type.BasicType; import java.util.ArrayList; import java.util.Collections; @@ -956,7 +955,7 @@ public ResultMementoBasicStandard resolve(ResultSetMappingResolutionContext reso ); if ( hibernateTypeName != null ) { - final BasicType namedType = + final var namedType = resolutionContext.getTypeConfiguration().getBasicTypeRegistry() .getRegisteredType( hibernateTypeName ); @@ -964,12 +963,12 @@ public ResultMementoBasicStandard resolve(ResultSetMappingResolutionContext reso throw new IllegalArgumentException( "Could not resolve named type : " + hibernateTypeName ); } - return new ResultMementoBasicStandard( columnName, namedType, resolutionContext ); + return new ResultMementoBasicStandard( columnName, namedType ); } // todo (6.0) : column name may be optional in HBM - double check - return new ResultMementoBasicStandard( columnName, null, resolutionContext ); + return new ResultMementoBasicStandard( columnName, null ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java index d4228f87ecf2..a89d6af99f63 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java @@ -45,6 +45,8 @@ import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl; import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.format.jackson.Jackson3JsonFormatMapper; +import org.hibernate.type.format.jackson.Jackson3XmlFormatMapper; import org.hibernate.type.format.jackson.JacksonIntegration; import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; @@ -308,6 +310,11 @@ private static void addJsonFormatMappers(StrategySelectorImpl strategySelector) JacksonJsonFormatMapper.SHORT_NAME, JacksonJsonFormatMapper.class ); + strategySelector.registerStrategyImplementor( + FormatMapper.class, + Jackson3JsonFormatMapper.SHORT_NAME, + Jackson3JsonFormatMapper.class + ); if ( JacksonIntegration.isJacksonOsonExtensionAvailable() ) { strategySelector.registerStrategyImplementor( FormatMapper.class, @@ -323,6 +330,11 @@ private static void addXmlFormatMappers(StrategySelectorImpl strategySelector) { JacksonXmlFormatMapper.SHORT_NAME, JacksonXmlFormatMapper.class ); + strategySelector.registerStrategyImplementor( + FormatMapper.class, + Jackson3XmlFormatMapper.SHORT_NAME, + Jackson3XmlFormatMapper.class + ); strategySelector.registerStrategyImplementor( FormatMapper.class, JaxbXmlFormatMapper.SHORT_NAME, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/BootstrapContext.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/BootstrapContext.java index c64311da83e9..38dd0f028076 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/BootstrapContext.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/BootstrapContext.java @@ -125,13 +125,6 @@ public interface BootstrapContext { */ ClassLoaderAccess getClassLoaderAccess(); - /** - * Access to the shared {@link ClassmateContext} object used - * throughout the bootstrap process. - */ - @Incubating - ClassmateContext getClassmateContext(); - /** * Access to the {@link ArchiveDescriptorFactory} used for scanning. * diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/ClassmateContext.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/ClassmateContext.java deleted file mode 100644 index 73f6256575df..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/ClassmateContext.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.boot.spi; - -import com.fasterxml.classmate.MemberResolver; -import com.fasterxml.classmate.TypeResolver; -import org.hibernate.Incubating; - -/** - * Exposes the Classmate {@link TypeResolver} and {@link MemberResolver}. - * - * @author Steve Ebersole - */ -@Incubating -public class ClassmateContext { - private TypeResolver typeResolver = new TypeResolver(); - private MemberResolver memberResolver = new MemberResolver( typeResolver ); - - public TypeResolver getTypeResolver() { - if ( typeResolver == null ) { - throw new IllegalStateException( "Classmate context has been released" ); - } - return typeResolver; - } - - public MemberResolver getMemberResolver() { - if ( memberResolver == null ) { - throw new IllegalStateException( "Classmate context has been released" ); - } - return memberResolver; - } - - public void release() { - typeResolver = null; - memberResolver = null; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java index fc2903a9660e..405d82711e12 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java @@ -140,6 +140,7 @@ default AnnotationDescriptorRegistry getAnnotationDescriptorRegistry() { * @param subselect A select statement which defines a logical table, much * like a DB view. * @param isAbstract Is the table abstract (i.e. not really existing in the DB)? + * @param isExplicit Whether the name is explicitly set * * @return The created table metadata, or the existing reference. */ @@ -149,7 +150,8 @@ Table addTable( String name, String subselect, boolean isAbstract, - MetadataBuildingContext buildingContext); + MetadataBuildingContext buildingContext, + boolean isExplicit); /** * Adds a 'denormalized table' to this repository. diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java index 2b072677721c..6cf719d46cab 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java @@ -32,28 +32,26 @@ public interface InheritanceChecker { * Should the given property be included in the owner's base fetch group? */ public static boolean includeInBaseFetchGroup( - Property bootMapping, + Property property, boolean isEnhanced, InheritanceChecker inheritanceChecker, boolean collectionsInDefaultFetchGroupEnabled) { - final var value = bootMapping.getValue(); - - if ( ! isEnhanced ) { - if ( value instanceof ToOne toOne ) { - if ( toOne.isUnwrapProxy() ) { - BYTECODE_INTERCEPTOR_LOGGER.toOneLazyNoProxyButNotEnhanced( - bootMapping.getPersistentClass().getEntityName(), - bootMapping.getName() - ); - } + final var value = property.getValue(); + + if ( !isEnhanced ) { + if ( value instanceof ToOne toOne && toOne.isUnwrapProxy() ) { + BYTECODE_INTERCEPTOR_LOGGER.toOneLazyNoProxyButNotEnhanced( + property.getPersistentClass().getEntityName(), + property.getName() + ); } return true; } // if we get here, we know the property owner is enhanced for laziness // - // NOTE : we make the (potentially untrue) assumption here that - // if the owner is enhanced, then all classes are enhanced.. + // NOTE: we make the (potentially untrue) assumption here that + // if the owner is enhanced, then all classes are enhanced. if ( value instanceof ToOne toOne ) { @@ -64,7 +62,7 @@ public static boolean includeInBaseFetchGroup( // it is lazy. see if we should select the FK - if ( bootMapping.getLazyGroup() != null ) { + if ( property.getLazyGroup() != null ) { // a non-base fetch group was explicitly specified // // really this should indicate to not select it as part of the base group. @@ -72,9 +70,9 @@ public static boolean includeInBaseFetchGroup( // we simply log a message that we are ignoring the `@LazyGroup` for to-ones BYTECODE_INTERCEPTOR_LOGGER.lazyGroupIgnoredForToOne( - bootMapping.getLazyGroup(), - bootMapping.getPersistentClass().getEntityName(), - bootMapping.getName() + property.getLazyGroup(), + property.getPersistentClass().getEntityName(), + property.getName() ); // at a later time - for example 6.0 when we can implement the join solution @@ -84,13 +82,13 @@ public static boolean includeInBaseFetchGroup( // for now, fall through } - if ( ! toOne.isReferenceToPrimaryKey() ) { + if ( !toOne.isReferenceToPrimaryKey() ) { // we do not have a reference to the associated primary-key return false; } - if ( toOne.getColumnSpan() == 0 ) { - // generally this would indicate a "shared PK" on-to-one and there + if ( !toOne.hasColumns() ) { + // generally this would indicate a "shared PK" one-to-one and there // is no column for the association on the owner table - do not add // the association to the base group (which would force an immediate // select from the association table effectively making this @@ -98,15 +96,16 @@ public static boolean includeInBaseFetchGroup( return false; } - final boolean unwrapExplicitlyRequested = toOne.isUnwrapProxy() && !toOne.isUnwrapProxyImplicit(); + final boolean unwrapExplicitlyRequested = + toOne.isUnwrapProxy() && !toOne.isUnwrapProxyImplicit(); if ( inheritanceChecker.hasSubclasses( toOne.getReferencedEntityName() ) ) { // the associated type has subclasses - we cannot use the enhanced proxy and will generate a HibernateProxy if ( unwrapExplicitlyRequested ) { // NO_PROXY was explicitly requested BYTECODE_INTERCEPTOR_LOGGER.lazyNoProxyButAssociatedHasSubclasses( - bootMapping.getPersistentClass().getEntityName(), - bootMapping.getName(), + property.getPersistentClass().getEntityName(), + property.getName(), toOne.getReferencedEntityName() ); } @@ -117,8 +116,8 @@ public static boolean includeInBaseFetchGroup( if ( toOne instanceof ManyToOne manyToOne && manyToOne.isIgnoreNotFound() ) { if ( unwrapExplicitlyRequested ) { BYTECODE_INTERCEPTOR_LOGGER.notFoundIgnoreWithNoProxySkippingFkSelection( - bootMapping.getPersistentClass().getEntityName(), - bootMapping.getName() + property.getPersistentClass().getEntityName(), + property.getName() ); return false; } @@ -135,7 +134,7 @@ public static boolean includeInBaseFetchGroup( } return collectionsInDefaultFetchGroupEnabled && ( value instanceof Collection ) - || ! bootMapping.isLazy(); + || ! property.isLazy(); } public static T performWork( @@ -143,7 +142,7 @@ public static T performWork( BiFunction work, String entityName, String attributeName) { - SharedSessionContractImplementor session = interceptor.getLinkedSession(); + var session = interceptor.getLinkedSession(); final boolean isTempSession; final boolean isJta; @@ -236,23 +235,20 @@ enum Cause { NO_SF_UUID } - private static LazyInitializationException createLazyInitializationException(final Cause cause, final String entityName, final String attributeName) { - final String reason = switch ( cause ) { - case NO_SESSION -> "no session and settings disallow loading outside the Session"; - case CLOSED_SESSION -> "session is closed and settings disallow loading outside the Session"; - case DISCONNECTED_SESSION -> "session is disconnected and settings disallow loading outside the Session"; - case NO_SF_UUID -> "could not determine SessionFactory UUId to create temporary Session for loading"; - }; - - final String message = String.format( + private static LazyInitializationException createLazyInitializationException( + Cause cause, String entityName, String attributeName) { + return new LazyInitializationException( String.format( Locale.ROOT, "Unable to perform requested lazy initialization [%s.%s] - %s", entityName, attributeName, - reason - ); - - return new LazyInitializationException( message ); + switch ( cause ) { + case NO_SESSION -> "no session and settings disallow loading outside the Session"; + case CLOSED_SESSION -> "session is closed and settings disallow loading outside the Session"; + case DISCONNECTED_SESSION -> "session is disconnected and settings disallow loading outside the Session"; + case NO_SF_UUID -> "could not determine SessionFactory UUId to create temporary Session for loading"; + } + ) ); } private static SharedSessionContractImplementor openTemporarySessionForLoading( diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/BytecodeEnhancementMetadataPojoImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/BytecodeEnhancementMetadataPojoImpl.java index c8536e8792b0..b793a37648f2 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/BytecodeEnhancementMetadataPojoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/BytecodeEnhancementMetadataPojoImpl.java @@ -14,18 +14,14 @@ import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.bytecode.spi.NotInstrumentedException; -import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.ManagedEntity; -import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.mapping.PersistentClass; -import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.CompositeType; import org.checkerframework.checker.nullness.qual.Nullable; @@ -50,9 +46,9 @@ public static BytecodeEnhancementMetadataPojoImpl from( CompositeType nonAggregatedCidMapper, boolean collectionsInDefaultFetchGroupEnabled, Metadata metadata) { - final Class mappedClass = persistentClass.getMappedClass(); + final var mappedClass = persistentClass.getMappedClass(); final boolean enhancedForLazyLoading = isPersistentAttributeInterceptableType( mappedClass ); - final LazyAttributesMetadata lazyAttributesMetadata = enhancedForLazyLoading + final var lazyAttributesMetadata = enhancedForLazyLoading ? LazyAttributesMetadata.from( persistentClass, true, collectionsInDefaultFetchGroupEnabled, metadata ) : LazyAttributesMetadata.nonEnhanced( persistentClass.getEntityName() ); @@ -139,7 +135,7 @@ public boolean isAttributeLoaded(Object entity, String attributeName) { return true; } - final BytecodeLazyAttributeInterceptor interceptor = extractLazyInterceptor( entity ); + final var interceptor = extractLazyInterceptor( entity ); if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { return interceptor.isAttributeLoaded( attributeName ); } @@ -154,12 +150,12 @@ public boolean isAttributeLoaded(Object entity, String attributeName) { @Override public PersistentAttributeInterceptable createEnhancedProxy(EntityKey entityKey, boolean addEmptyEntry, SharedSessionContractImplementor session) { - final EntityPersister persister = entityKey.getPersister(); + final var persister = entityKey.getPersister(); final Object identifier = entityKey.getIdentifier(); - final PersistenceContext persistenceContext = session.getPersistenceContext(); + final var persistenceContext = session.getPersistenceContext(); // first, instantiate the entity instance to use as the proxy - final PersistentAttributeInterceptable entity = asPersistentAttributeInterceptable( persister.instantiate( identifier, session ) ); + final var entity = asPersistentAttributeInterceptable( persister.instantiate( identifier, session ) ); // clear the fields that are marked as dirty in the dirtiness tracker processIfSelfDirtinessTracker( entity, BytecodeEnhancementMetadataPojoImpl::clearDirtyAttributes ); @@ -170,8 +166,8 @@ public PersistentAttributeInterceptable createEnhancedProxy(EntityKey entityKey, // if requested, add the "holder entry" to the PC if ( addEmptyEntry ) { - EntityHolder entityHolder = persistenceContext.getEntityHolder( entityKey ); - EntityEntry entityEntry = persistenceContext.addEntry( + final var entityHolder = persistenceContext.getEntityHolder( entityKey ); + final var entityEntry = persistenceContext.addEntry( entity, Status.MANAGED, // loaded state @@ -223,14 +219,12 @@ public LazyAttributeLoadingInterceptor injectInterceptor( ) ); } - final LazyAttributeLoadingInterceptor interceptor = new LazyAttributeLoadingInterceptor( - this.lazyAttributeLoadingInterceptorState, + final var interceptor = new LazyAttributeLoadingInterceptor( + lazyAttributeLoadingInterceptorState, identifier, session ); - injectInterceptor( entity, interceptor, session ); - return interceptor; } @@ -257,10 +251,11 @@ public void injectEnhancedEntityAsProxyInterceptor( //This state object needs to be lazily initialized as it needs access to the Persister, but once //initialized it can be reused across multiple sessions. public EnhancementAsProxyLazinessInterceptor.EntityRelatedState getEnhancementAsProxyLazinessInterceptorMetastate(SharedSessionContractImplementor session) { - EnhancementAsProxyLazinessInterceptor.EntityRelatedState state = this.enhancementAsProxyInterceptorState; + var state = this.enhancementAsProxyInterceptorState; if ( state == null ) { - final EntityPersister entityPersister = session.getFactory().getMappingMetamodel() - .getEntityDescriptor( entityName ); + final var entityPersister = + session.getFactory().getMappingMetamodel() + .getEntityDescriptor( entityName ); state = new EnhancementAsProxyLazinessInterceptor.EntityRelatedState( entityPersister, nonAggregatedCidMapper, @@ -277,7 +272,8 @@ public void injectInterceptor( PersistentAttributeInterceptor interceptor, SharedSessionContractImplementor session) { if ( !enhancedForLazyLoading ) { - throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + "] is not enhanced for lazy loading" ); + throw new NotInstrumentedException( "Entity class '" + entityClass.getName() + + "' is not enhanced for lazy loading" ); } if ( !entityClass.isInstance( entity ) ) { @@ -296,7 +292,8 @@ public void injectInterceptor( @Override public @Nullable BytecodeLazyAttributeInterceptor extractLazyInterceptor(Object entity) throws NotInstrumentedException { if ( !enhancedForLazyLoading ) { - throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + "] is not enhanced for lazy loading" ); + throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + + "] is not enhanced for lazy loading" ); } if ( !entityClass.isInstance( entity ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java index 1c86fd450e63..8b0475ec0754 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java @@ -9,7 +9,6 @@ import net.bytebuddy.description.modifier.Visibility; import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; -import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImplConstants; import org.hibernate.bytecode.spi.BasicProxyFactory; import org.hibernate.engine.spi.PrimeAmongSecondarySupertypes; import org.hibernate.internal.util.collections.ArrayHelper; @@ -17,16 +16,17 @@ import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; +import static org.hibernate.proxy.ProxyConfiguration.INTERCEPTOR_FIELD_NAME; + public class BasicProxyFactoryImpl implements BasicProxyFactory { - private static final Class[] NO_INTERFACES = ArrayHelper.EMPTY_CLASS_ARRAY; + private static final Class[] NO_INTERFACES = ArrayHelper.EMPTY_CLASS_ARRAY; private static final String PROXY_NAMING_SUFFIX = "HibernateBasicProxy"; - private final Class proxyClass; - private final Constructor proxyClassConstructor; + private final Class proxyClass; + private final Constructor proxyClassConstructor; - @SuppressWarnings({ "unchecked", "rawtypes" }) - public BasicProxyFactoryImpl(final Class superClass, final Class interfaceClass, final ByteBuddyState byteBuddyState) { + public BasicProxyFactoryImpl(final Class superClass, final Class interfaceClass, final ByteBuddyState byteBuddyState) { if ( superClass == null && interfaceClass == null ) { throw new AssertionFailure( "attempting to build proxy without any superclass or interfaces" ); } @@ -35,9 +35,9 @@ public BasicProxyFactoryImpl(final Class superClass, final Class interfaceClass, throw new AssertionFailure( "Ambiguous call: we assume invocation with EITHER a superClass OR an interfaceClass" ); } - final Class superClassOrMainInterface = superClass != null ? superClass : interfaceClass; - final ByteBuddyState.ProxyDefinitionHelpers helpers = byteBuddyState.getProxyDefinitionHelpers(); - final EnhancerImplConstants constants = byteBuddyState.getEnhancerConstants(); + final var superClassOrMainInterface = superClass != null ? superClass : interfaceClass; + final var helpers = byteBuddyState.getProxyDefinitionHelpers(); + final var constants = byteBuddyState.getEnhancerConstants(); final String proxyClassName = superClassOrMainInterface.getName() + "$" + PROXY_NAMING_SUFFIX; this.proxyClass = byteBuddyState.loadBasicProxy( superClassOrMainInterface, proxyClassName, (byteBuddy, namingStrategy) -> @@ -45,11 +45,11 @@ public BasicProxyFactoryImpl(final Class superClass, final Class interfaceClass, .with( namingStrategy ) .subclass( superClass == null ? Object.class : superClass, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR ) .implement( interfaceClass == null ? NO_INTERFACES : new Class[]{ interfaceClass } ) - .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) - .method( byteBuddyState.getProxyDefinitionHelpers().getVirtualNotFinalizerFilter() ) - .intercept( byteBuddyState.getProxyDefinitionHelpers().getDelegateToInterceptorDispatcherMethodDelegation() ) + .defineField( INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) + .method( helpers.getVirtualNotFinalizerFilter() ) + .intercept( helpers.getDelegateToInterceptorDispatcherMethodDelegation() ) .implement( constants.INTERFACES_for_ProxyConfiguration ) - .intercept( byteBuddyState.getProxyDefinitionHelpers().getInterceptorFieldAccessor() ) + .intercept( helpers.getInterceptorFieldAccessor() ) ) ); @@ -63,21 +63,25 @@ public BasicProxyFactoryImpl(final Class superClass, final Class interfaceClass, @Override public Object getProxy() { - final PrimeAmongSecondarySupertypes instance; + final var instance = instantiateProxy(); + final var proxyConfiguration = instance.asProxyConfiguration(); + if ( proxyConfiguration == null ) { + throw new HibernateException( "Produced proxy does not correctly implement ProxyConfiguration" ); + } + // Create a dedicated interceptor for the proxy. + // This is required as the interceptor is stateful. + proxyConfiguration.$$_hibernate_set_interceptor( + new PassThroughInterceptor( proxyClass.getName() ) ); + return instance; + } + + private PrimeAmongSecondarySupertypes instantiateProxy() { try { - instance = (PrimeAmongSecondarySupertypes) proxyClassConstructor.newInstance(); + return (PrimeAmongSecondarySupertypes) proxyClassConstructor.newInstance(); } catch (Throwable t) { throw new HibernateException( "Unable to instantiate proxy instance", t ); } - final ProxyConfiguration proxyConfiguration = instance.asProxyConfiguration(); - if ( proxyConfiguration == null ) { - throw new HibernateException( "Produced proxy does not correctly implement ProxyConfiguration" ); - } - // Create a dedicated interceptor for the proxy. This is required as the interceptor is stateful. - final ProxyConfiguration.Interceptor interceptor = new PassThroughInterceptor( proxyClass.getName() ); - proxyConfiguration.$$_hibernate_set_interceptor( interceptor ); - return instance; } public boolean isInstance(Object object) { diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/DefaultCacheKeysFactory.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/DefaultCacheKeysFactory.java index 388812b964de..6348e4737e5d 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/DefaultCacheKeysFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/DefaultCacheKeysFactory.java @@ -4,17 +4,17 @@ */ package org.hibernate.cache.internal; -import java.io.Serializable; import org.hibernate.cache.spi.CacheKeysFactory; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.BasicType; import org.hibernate.type.Type; /** - * Second level cache providers now have the option to use custom key implementations. + * Second-level cache providers now have the option to use custom key implementations. * This was done as the default key implementation is very generic and is quite * a large object to allocate in large quantities at runtime. * In some extreme cases, for example when the hit ratio is very low, this was making the efficiency @@ -45,20 +45,30 @@ public class DefaultCacheKeysFactory implements CacheKeysFactory { public static Object staticCreateCollectionKey(Object id, CollectionPersister persister, SessionFactoryImplementor factory, String tenantIdentifier) { final Type keyType = persister.getKeyType(); - final Serializable disassembledKey = keyType.disassemble( id, factory ); + final var coercedId = getCoercedId( id, keyType ); + final var disassembledKey = keyType.disassemble( coercedId, factory ); final boolean idIsArray = disassembledKey.getClass().isArray(); + final String role = persister.getRole(); return tenantIdentifier == null && !idIsArray - ? new BasicCacheKeyImplementation( id, disassembledKey, keyType, persister.getRole() ) - : new CacheKeyImplementation( id, disassembledKey, keyType, persister.getRole(), tenantIdentifier ); + ? new BasicCacheKeyImplementation( coercedId, disassembledKey, keyType, role ) + : new CacheKeyImplementation( coercedId, disassembledKey, keyType, role, tenantIdentifier ); } public static Object staticCreateEntityKey(Object id, EntityPersister persister, SessionFactoryImplementor factory, String tenantIdentifier) { final Type keyType = persister.getIdentifierType(); - final Serializable disassembledKey = keyType.disassemble( id, factory ); + final var coercedId = getCoercedId( id, keyType ); + final var disassembledKey = keyType.disassemble( coercedId, factory ); final boolean idIsArray = disassembledKey.getClass().isArray(); + final String rootEntityName = persister.getRootEntityName(); return tenantIdentifier == null && !idIsArray - ? new BasicCacheKeyImplementation( id, disassembledKey, keyType, persister.getRootEntityName() ) - : new CacheKeyImplementation( id, disassembledKey, keyType, persister.getRootEntityName(), tenantIdentifier ); + ? new BasicCacheKeyImplementation( coercedId, disassembledKey, keyType, rootEntityName ) + : new CacheKeyImplementation( coercedId, disassembledKey, keyType, rootEntityName, tenantIdentifier ); + } + + private static Object getCoercedId(Object id, Type keyType) { + return keyType instanceof BasicType basicType + ? basicType.getJavaTypeDescriptor().coerce( id ) + : id; } public static Object staticCreateNaturalIdKey( diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/DisabledCaching.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/DisabledCaching.java index 8986678a4b13..ade8b582440b 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/DisabledCaching.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/DisabledCaching.java @@ -220,10 +220,9 @@ public void evict(Class cls) { } @Override - @SuppressWarnings("unchecked") public T unwrap(Class type) { if ( type.isAssignableFrom( DisabledCaching.class ) ) { - return (T) this; + return type.cast( this ); } else { throw new PersistenceException( "Hibernate cannot unwrap Cache as '" + type.getName() + "'" ); diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java index 093e2fc606be..c927b2d92702 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java @@ -4,9 +4,6 @@ */ package org.hibernate.cache.internal; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.Locale; import java.util.Map; import java.util.Objects; @@ -71,9 +68,6 @@ public class EnabledCaching implements CacheImplementor, DomainDataRegionBuildin private final Map namedQueryResultsCacheMap = new ConcurrentHashMap<>(); - private final Set legacySecondLevelCacheNames = new LinkedHashSet<>(); - private final Map> legacyNaturalIdAccessesForRegion = new ConcurrentHashMap<>(); - public EnabledCaching(SessionFactoryImplementor sessionFactory) { this.sessionFactory = sessionFactory; final var options = sessionFactory.getSessionFactoryOptions(); @@ -105,7 +99,6 @@ private TimestampsCache buildTimestampsCache(SessionFactoryImplementor sessionFa DEFAULT_UPDATE_TIMESTAMPS_REGION_UNQUALIFIED_NAME, sessionFactory ); - legacySecondLevelCacheNames.add( timestampsRegion.getName() ); return sessionFactory.getSessionFactoryOptions().getTimestampsCacheFactory() .buildTimestampsCache( this, timestampsRegion ); } @@ -134,27 +127,16 @@ public void prime(Set cacheRegionConfigs) { for ( var entityAccessConfig : regionConfig.getEntityCaching() ) { final var navigableRole = entityAccessConfig.getNavigableRole(); entityAccessMap.put( navigableRole, region.getEntityDataAccess( navigableRole ) ); - legacySecondLevelCacheNames.add( qualifiedRegionName( region ) ); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Natural-id caching - final var naturalIdCaching = regionConfig.getNaturalIdCaching(); - if ( naturalIdCaching.isEmpty() ) { - legacyNaturalIdAccessesForRegion.put( region.getName(), Collections.emptySet() ); - } - else { - final Set accesses = new HashSet<>(); - for ( var naturalIdAccessConfig : naturalIdCaching ) { - final var navigableRole = naturalIdAccessConfig.getNavigableRole(); - final var naturalIdDataAccess = - naturalIdAccessMap.put( navigableRole, - region.getNaturalIdDataAccess( navigableRole ) ); - accesses.add( naturalIdDataAccess ); - } - legacyNaturalIdAccessesForRegion.put( region.getName(), accesses ); + for ( var naturalIdAccessConfig : regionConfig.getNaturalIdCaching() ) { + final var navigableRole = naturalIdAccessConfig.getNavigableRole(); + naturalIdAccessMap.put( navigableRole, + region.getNaturalIdDataAccess( navigableRole ) ); } @@ -165,7 +147,6 @@ public void prime(Set cacheRegionConfigs) { final var navigableRole = collectionAccessConfig.getNavigableRole(); collectionAccessMap.put( navigableRole, region.getCollectionDataAccess( navigableRole ) ); - legacySecondLevelCacheNames.add( qualifiedRegionName( region ) ); } } @@ -213,7 +194,6 @@ public Region getRegion(String regionName) { } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Entity data @@ -227,11 +207,9 @@ public boolean containsEntity(String entityName, Object identifier) { final var persister = getEntityDescriptor( entityName ); final var cacheAccess = persister.getCacheAccessStrategy(); if ( cacheAccess != null ) { - final Object idValue = - persister.getIdentifierMapping().getJavaType() - .coerce( identifier, sessionFactory::getTypeConfiguration ); final Object key = cacheAccess.generateCacheKey( - idValue, + persister.getIdentifierMapping().getJavaType() + .coerce( identifier ), persister.getRootEntityDescriptor().getEntityPersister(), sessionFactory, null @@ -311,7 +289,6 @@ public void evictEntityData() { } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Natural-id data @@ -343,7 +320,6 @@ private void evictNaturalIdData(NavigableRole rootEntityRole, NaturalIdDataAcces } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Collection data @@ -429,7 +405,7 @@ private void evictQueryResultRegion(QueryResultsCache cache) { public void evictQueryRegions() { LOG.evictingAllQueryRegions(); evictQueryResultRegion( defaultQueryResultsCache ); - for ( QueryResultsCache cache : namedQueryResultsCacheMap.values() ) { + for ( var cache : namedQueryResultsCacheMap.values() ) { evictQueryResultRegion( cache ); } } @@ -472,7 +448,7 @@ else if ( regionName == null || regionName.equals( getDefaultResultCacheName() ) } else { final var existing = namedQueryResultsCacheMap.get( regionName ); - return existing != null ? existing : makeQueryResultsRegionAccess( regionName ); + return existing != null ? existing : makeQueryResultsCache( regionName ); } } @@ -489,21 +465,20 @@ else if ( regionName == null || regionName.equals( getDefaultResultCacheName() ) } } - protected QueryResultsCache makeQueryResultsRegionAccess(String regionName) { - final var regionAccess = new QueryResultsCacheImpl( getQueryResultsRegion( regionName ), timestampsCache ); + protected QueryResultsCache makeQueryResultsCache(String regionName) { + final var regionAccess = new QueryResultsCacheImpl( queryResultsRegion( regionName ), timestampsCache ); namedQueryResultsCacheMap.put( regionName, regionAccess ); - legacySecondLevelCacheNames.add( regionName ); return regionAccess; } - private QueryResultsRegion getQueryResultsRegion(String regionName) { - final Region region = regionsByName.computeIfAbsent( regionName, this::makeQueryResultsRegion ); - return region instanceof QueryResultsRegion queryResultsRegion + private QueryResultsRegion queryResultsRegion(String regionName) { + return regionsByName.computeIfAbsent( regionName, this::buildQueryResultsRegion ) + instanceof QueryResultsRegion queryResultsRegion ? queryResultsRegion // There was already a different type of Region with the same name. - : queryResultsRegionsByDuplicateName.computeIfAbsent( regionName, this::makeQueryResultsRegion ); + : queryResultsRegionsByDuplicateName.computeIfAbsent( regionName, this::buildQueryResultsRegion ); } - protected QueryResultsRegion makeQueryResultsRegion(String regionName) { + protected QueryResultsRegion buildQueryResultsRegion(String regionName) { return regionFactory.buildQueryResultsRegion( regionName, getSessionFactory() ); } @@ -523,13 +498,12 @@ public void evictRegion(String regionName) { } @Override - @SuppressWarnings("unchecked") public T unwrap(Class type) { if ( type.isAssignableFrom( EnabledCaching.class ) ) { - return (T) this; + return type.cast( this ); } else if ( type.isAssignableFrom( RegionFactory.class ) ) { - return (T) regionFactory; + return type.cast( regionFactory ); } else { throw new PersistenceException( "Hibernate cannot unwrap Cache as '" + type.getName() + "'" ); @@ -547,7 +521,6 @@ public void close() { } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // JPA-defined methods @@ -570,8 +543,6 @@ public void evict(Class cls) { } - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Deprecations diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java index f57f61df443d..d12f24cd96ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java @@ -27,7 +27,6 @@ import org.hibernate.boot.MetadataSources; import org.hibernate.boot.SessionFactoryBuilder; import org.hibernate.boot.model.convert.internal.ConverterDescriptors; -import org.hibernate.boot.spi.ClassmateContext; import org.hibernate.boot.jaxb.spi.Binding; import org.hibernate.boot.model.FunctionContributor; import org.hibernate.boot.model.NamedEntityGraphDefinition; @@ -146,7 +145,6 @@ public class Configuration { private final BootstrapServiceRegistry bootstrapServiceRegistry; private final MetadataSources metadataSources; final private StandardServiceRegistryBuilder standardServiceRegistryBuilder; - private final ClassmateContext classmateContext = new ClassmateContext(); // used during processing mappings private ImplicitNamingStrategy implicitNamingStrategy; @@ -1184,7 +1182,7 @@ public Configuration addAuxiliaryDatabaseObject(AuxiliaryDatabaseObject object) * @return {@code this} for method chaining */ public Configuration addAttributeConverter(Class> attributeConverterClass, boolean autoApply) { - addAttributeConverter( ConverterDescriptors.of( attributeConverterClass, autoApply, false, classmateContext ) ); + addAttributeConverter( ConverterDescriptors.of( attributeConverterClass, autoApply, false ) ); return this; } @@ -1196,7 +1194,7 @@ public Configuration addAttributeConverter(Class> attributeConverterClass) { - addAttributeConverter( ConverterDescriptors.of( attributeConverterClass, classmateContext ) ); + addAttributeConverter( ConverterDescriptors.of( attributeConverterClass ) ); return this; } @@ -1210,7 +1208,7 @@ public Configuration addAttributeConverter(Class attributeConverter) { - addAttributeConverter( ConverterDescriptors.of( attributeConverter, classmateContext ) ); + addAttributeConverter( ConverterDescriptors.of( attributeConverter ) ); return this; } @@ -1227,7 +1225,7 @@ public Configuration addAttributeConverter(AttributeConverter attributeConv * @return {@code this} for method chaining */ public Configuration addAttributeConverter(AttributeConverter attributeConverter, boolean autoApply) { - addAttributeConverter( ConverterDescriptors.of( attributeConverter, autoApply, classmateContext ) ); + addAttributeConverter( ConverterDescriptors.of( attributeConverter, autoApply ) ); return this; } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java index d657c9d2cfbe..f813dc7b65e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java @@ -7,7 +7,6 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -22,8 +21,6 @@ import org.hibernate.HibernateException; import org.hibernate.LazyInitializationException; import org.hibernate.engine.spi.CollectionEntry; -import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.engine.spi.TypedValue; @@ -37,6 +34,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; +import static java.util.Collections.emptyIterator; import static java.util.Collections.emptyList; import static org.hibernate.collection.internal.CollectionLogger.COLLECTION_LOGGER; import static org.hibernate.engine.internal.ForeignKeys.getEntityIdentifier; @@ -686,7 +684,8 @@ public final boolean unsetSession(SharedSessionContractImplementor currentSessio if ( allowLoadOutsideTransaction && !initialized && session.getLoadQueryInfluencers().hasEnabledFilters() ) { - COLLECTION_LOGGER.enabledFiltersWhenDetachFromSession( collectionInfoString( getRole(), getKey() ) ); + COLLECTION_LOGGER.enabledFiltersWhenDetachFromSession( + collectionInfoString( getRole(), getKey() ) ); } session = null; } @@ -694,7 +693,8 @@ public final boolean unsetSession(SharedSessionContractImplementor currentSessio } else { if ( session != null ) { - COLLECTION_LOGGER.logCannotUnsetUnexpectedSessionInCollection( unexpectedSessionStateMessage( currentSession ) ); + COLLECTION_LOGGER.logCannotUnsetUnexpectedSessionInCollection( + unexpectedSessionStateMessage( currentSession ) ); } return false; } @@ -704,20 +704,23 @@ private void logDiscardedQueuedOperations() { try { if ( wasTransactionRolledBack() ) { // It was due to a rollback. - if ( COLLECTION_LOGGER.isDebugEnabled()) { - COLLECTION_LOGGER.queuedOperationWhenDetachFromSessionOnRollback( collectionInfoString( getRole(), getKey() ) ); + if ( COLLECTION_LOGGER.isDebugEnabled() ) { + COLLECTION_LOGGER.queuedOperationWhenDetachFromSessionOnRollback( + collectionInfoString( getRole(), getKey() ) ); } } else { // We don't know why the collection is being detached. // Just log the info. - COLLECTION_LOGGER.queuedOperationWhenDetachFromSession( collectionInfoString( getRole(), getKey() ) ); + COLLECTION_LOGGER.queuedOperationWhenDetachFromSession( + collectionInfoString( getRole(), getKey() ) ); } } catch (Exception e) { // We don't know why the collection is being detached. // Just log the info. - COLLECTION_LOGGER.queuedOperationWhenDetachFromSession( collectionInfoString( getRole(), getKey() ) ); + COLLECTION_LOGGER.queuedOperationWhenDetachFromSession( + collectionInfoString( getRole(), getKey() ) ); } } @@ -756,7 +759,8 @@ else if ( this.session != null ) { } } if ( hasQueuedOperations() ) { - COLLECTION_LOGGER.queuedOperationWhenAttachToSession( collectionInfoString( getRole(), getKey() ) ); + COLLECTION_LOGGER.queuedOperationWhenAttachToSession( + collectionInfoString( getRole(), getKey() ) ); } this.session = session; return true; @@ -872,7 +876,7 @@ public void remove() { }; } else { - return Collections.emptyIterator(); + return emptyIterator(); } } @@ -1070,8 +1074,8 @@ public
A[] toArray(A[] array) { public final boolean equals(Object object) { return object == this || object instanceof Set that - && that.size() == this.size() - && containsAll( that ); + && that.size() == this.size() + && containsAll( that ); } @Override @@ -1306,11 +1310,11 @@ protected static Collection getOrphans( // collect EntityIdentifier(s) of the *current* elements - add them into a HashSet for fast access final java.util.Set currentIds = new HashSet<>(); final java.util.Set currentSaving = new IdentitySet<>(); - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + final var persistenceContext = session.getPersistenceContextInternal(); for ( Object current : currentElements ) { if ( current != null && isNotTransient( entityName, current, null, session ) ) { - final EntityEntry ee = persistenceContext.getEntry( current ); - if ( ee != null && ee.getStatus() == Status.SAVING ) { + final var entityEntry = persistenceContext.getEntry( current ); + if ( entityEntry != null && entityEntry.getStatus() == Status.SAVING ) { currentSaving.add( current ); } else { @@ -1335,7 +1339,7 @@ protected static Collection getOrphans( private static boolean mayUseIdDirect(Type idType) { if ( idType instanceof BasicType basicType ) { - final Class javaType = basicType.getJavaType(); + final var javaType = basicType.getJavaType(); return javaType == String.class || javaType == Integer.class || javaType == Long.class diff --git a/hibernate-core/src/main/java/org/hibernate/context/internal/JTASessionContext.java b/hibernate-core/src/main/java/org/hibernate/context/internal/JTASessionContext.java index 142692241536..74a3e420d386 100644 --- a/hibernate-core/src/main/java/org/hibernate/context/internal/JTASessionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/context/internal/JTASessionContext.java @@ -9,6 +9,8 @@ import jakarta.transaction.Synchronization; import jakarta.transaction.Transaction; +import jakarta.transaction.TransactionManager; +import org.checkerframework.checker.nullness.qual.NonNull; import org.hibernate.FlushMode; import org.hibernate.HibernateException; import org.hibernate.Session; @@ -62,18 +64,50 @@ public Session currentSession() throws HibernateException { throw new HibernateException( "No TransactionManagerLookup specified" ); } - Transaction txn; + final var txn = getTransaction( transactionManager ); + final Object txnIdentifier = jtaPlatform.getTransactionIdentifier( txn ); + + Session currentSession = currentSessionMap.get( txnIdentifier ); + if ( currentSession == null ) { + currentSession = buildOrObtainSession(); + registerSynchronization( txn, txnIdentifier, currentSession ); + currentSessionMap.put( txnIdentifier, currentSession ); + } + else { + validateExistingSession( currentSession ); + } + + return currentSession; + } + + private void registerSynchronization(Transaction txn, Object txnIdentifier, Session currentSession) { + try { + txn.registerSynchronization( buildCleanupSynch( txnIdentifier ) ); + } + catch ( Throwable t ) { + try { + currentSession.close(); + } + catch ( Throwable e ) { + CURRENT_SESSION_LOGGER.unableToReleaseGeneratedCurrentSessionOnFailedSynchronizationRegistration(e); + } + throw new HibernateException( "Unable to register cleanup Synchronization with TransactionManager" ); + } + } + + private static @NonNull Transaction getTransaction(TransactionManager transactionManager) { try { - txn = transactionManager.getTransaction(); - if ( txn == null ) { + final var transaction = transactionManager.getTransaction(); + if ( transaction == null ) { throw new HibernateException( "Unable to locate current JTA transaction" ); } - if ( !isActive( txn.getStatus() ) ) { + if ( !isActive( transaction.getStatus() ) ) { // We could register the session against the transaction even though it is // not started, but we'd have no guarantee of ever getting the map // entries cleaned up (aside from spawning threads). throw new HibernateException( "Current transaction is not in progress" ); } + return transaction; } catch ( HibernateException e ) { throw e; @@ -81,34 +115,6 @@ public Session currentSession() throws HibernateException { catch ( Throwable t ) { throw new HibernateException( "Problem locating/validating JTA transaction", t ); } - - final Object txnIdentifier = jtaPlatform.getTransactionIdentifier( txn ); - - Session currentSession = currentSessionMap.get( txnIdentifier ); - - if ( currentSession == null ) { - currentSession = buildOrObtainSession(); - - try { - txn.registerSynchronization( buildCleanupSynch( txnIdentifier ) ); - } - catch ( Throwable t ) { - try { - currentSession.close(); - } - catch ( Throwable e ) { - CURRENT_SESSION_LOGGER.unableToReleaseGeneratedCurrentSessionOnFailedSynchronizationRegistration(e); - } - throw new HibernateException( "Unable to register cleanup Synchronization with TransactionManager" ); - } - - currentSessionMap.put( txnIdentifier, currentSession ); - } - else { - validateExistingSession( currentSession ); - } - - return currentSession; } /** diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java index c40a5cdad6d7..e66aab48b4e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java @@ -51,6 +51,7 @@ import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.persister.entity.mutation.EntityMutationTarget; import org.hibernate.query.SemanticException; import org.hibernate.query.common.TemporalUnit; import org.hibernate.query.sqm.IntervalType; @@ -62,6 +63,9 @@ import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.exec.spi.JdbcOperation; +import org.hibernate.sql.model.MutationOperation; +import org.hibernate.sql.model.internal.OptionalTableUpdate; +import org.hibernate.sql.model.jdbc.OptionalTableUpdateWithUpsertOperation; import org.hibernate.tool.schema.extract.internal.InformationExtractorPostgreSQLImpl; import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; import org.hibernate.tool.schema.extract.spi.ExtractionContext; @@ -136,6 +140,7 @@ * A {@linkplain Dialect SQL dialect} for CockroachDB 23.1 and above. * * @author Gavin King + * @author Yoobin Yoon */ public class CockroachDialect extends Dialect { @@ -477,6 +482,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.arraySlice_operator(); functionFactory.arrayReplace(); functionFactory.arrayTrim_unnest(); + functionFactory.arrayReverse_unnest(); + functionFactory.arraySort_unnest(); functionFactory.arrayFill_cockroachdb(); functionFactory.arrayToString_postgresql(); @@ -676,6 +683,14 @@ protected SqlAstTranslator buildTranslator( }; } + @Override + public MutationOperation createOptionalTableUpdateOperation( + EntityMutationTarget mutationTarget, + OptionalTableUpdate optionalTableUpdate, + SessionFactoryImplementor factory) { + return new OptionalTableUpdateWithUpsertOperation( mutationTarget, optionalTableUpdate, factory ); + } + @Override public NationalizationSupport getNationalizationSupport() { // TEXT / STRING inherently support nationalized data diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index 7601ab4ce695..e5b3f1b05564 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -338,7 +338,7 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun private static final Pattern ESCAPE_CLOSING_COMMENT_PATTERN = Pattern.compile( "\\*/" ); private static final Pattern ESCAPE_OPENING_COMMENT_PATTERN = Pattern.compile( "/\\*" ); private static final Pattern QUERY_PATTERN = Pattern.compile( - "^\\s*(select\\b.+?\\bfrom\\b.+?)(\\b(?:natural )?(?:left |right |full )?(?:inner |outer |cross )?join.+?\\b)?(\\bwhere\\b.+?)$", + "^\\s*(select\\s.+?\\sfrom\\s.+?)(\\s(?:(?:natural)?\\s*(?:left|right|full)?\\s*(?:inner|outer|cross)?\\s*join|straight_join)\\s.+?)?(\\swhere\\s.+?)?(\\sorder\\s+by\\s.+?)?$", Pattern.CASE_INSENSITIVE); //needed for converting precision from decimal to binary digits @@ -5242,14 +5242,8 @@ public String addSqlHintOrComment(String sql, QueryOptions queryOptions, boolean public static String addUseIndexQueryHint(String query, String hints) { final Matcher matcher = QUERY_PATTERN.matcher( query ); if ( matcher.matches() && matcher.groupCount() > 1 ) { - final String startToken = matcher.group(1); - // Null if there is no join in the query - final String joinToken = matcher.group(2); - final String endToken = matcher.group(3); - return startToken - + " use index (" + hints + ") " - + ( joinToken == null ? "" : joinToken ) - + endToken; + final String startToken = matcher.group( 1 ); + return startToken + " use index (" + hints + ")" + query.substring( startToken.length() ); } else { return query; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java index dffce552b7aa..84977844b6ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -126,6 +126,7 @@ * * @author Thomas Mueller * @author Jürgen Kreitler + * @author Yoobin Yoon */ public class H2Dialect extends Dialect { private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 2, 1, 214 ); @@ -342,6 +343,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.arraySlice(); functionFactory.arrayReplace_h2( getMaximumArraySize() ); functionFactory.arrayTrim_trim_array(); + functionFactory.arrayReverse_h2( getMaximumArraySize() ); + functionFactory.arraySort_h2( getMaximumArraySize() ); functionFactory.arrayFill_h2(); functionFactory.arrayToString_h2( getMaximumArraySize() ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HANADialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HANADialect.java index 4ff88f517c78..d23d8eaf73f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HANADialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HANADialect.java @@ -959,6 +959,11 @@ public Identifier toIdentifier(String text, boolean quoted) { return normalizeQuoting( Identifier.toIdentifier( text, quoted ) ); } + @Override + public Identifier toIdentifier(String text, boolean quoted, boolean isExplicit) { + return normalizeQuoting( Identifier.toIdentifier( text, quoted, isExplicit ) ); + } + @Override public Identifier normalizeQuoting(Identifier identifier) { Identifier normalizedIdentifier = this.helper.normalizeQuoting( identifier ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java index 9a636cb29992..08246efdeb17 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -77,6 +77,7 @@ * @author Christoph Sturm * @author Phillip Baird * @author Fred Toussi + * @author Yoobin Yoon */ public class HSQLDialect extends Dialect { @@ -214,6 +215,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.arraySlice_unnest(); functionFactory.arrayReplace_unnest(); functionFactory.arrayTrim_trim_array(); + functionFactory.arrayReverse_unnest(); + functionFactory.arraySort_hsql(); functionFactory.arrayFill_hsql(); functionFactory.arrayToString_hsql(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDeleteOrUpsertOperation.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDeleteOrUpsertOperation.java index 53d4a5cf9f87..06775f55c0f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDeleteOrUpsertOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDeleteOrUpsertOperation.java @@ -22,6 +22,7 @@ /** * @author Jan Schatteman */ +@Deprecated(forRemoval = true) public class MySQLDeleteOrUpsertOperation extends DeleteOrUpsertOperation { private Expectation customExpectation; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 59ca66444fc5..e95d198e3620 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -179,6 +179,7 @@ * @author Steve Ebersole * @author Gavin King * @author Loïc Lefèvre + * @author Yoobin Yoon */ public class OracleDialect extends Dialect { @@ -397,6 +398,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.arraySlice_oracle(); functionFactory.arrayReplace_oracle(); functionFactory.arrayTrim_oracle(); + functionFactory.arrayReverse_oracle(); + functionFactory.arraySort_oracle(); functionFactory.arrayFill_oracle(); functionFactory.arrayToString_oracle(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java index 6621a58cd495..22a35904c745 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -85,6 +85,7 @@ import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.sql.model.MutationOperation; import org.hibernate.sql.model.internal.OptionalTableUpdate; +import org.hibernate.sql.model.jdbc.OptionalTableUpdateWithUpsertOperation; import org.hibernate.tool.schema.extract.internal.InformationExtractorPostgreSQLImpl; import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; import org.hibernate.tool.schema.extract.spi.ExtractionContext; @@ -167,6 +168,7 @@ * PostgreSQL documentation. * * @author Gavin King + * @author Yoobin Yoon */ public class PostgreSQLDialect extends Dialect { protected final static DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 13 ); @@ -628,6 +630,14 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio else { functionFactory.arrayTrim_unnest(); } + if ( getVersion().isSameOrAfter( 18 ) ) { + functionFactory.arrayReverse(); + functionFactory.arraySort(); + } + else { + functionFactory.arrayReverse_unnest(); + functionFactory.arraySort_unnest(); + } functionFactory.arrayFill_postgresql(); functionFactory.arrayToString_postgresql(); @@ -1587,7 +1597,7 @@ public MutationOperation createOptionalTableUpdateOperation( .createMergeOperation( optionalTableUpdate ); } else { - return super.createOptionalTableUpdateOperation( mutationTarget, optionalTableUpdate, factory ); + return new OptionalTableUpdateWithUpsertOperation( mutationTarget, optionalTableUpdate, factory ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java index f1d82629dd10..4fd1c0b67f67 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java @@ -8,6 +8,7 @@ import jakarta.persistence.Timeout; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.ScrollMode; import org.hibernate.StaleObjectStateException; import org.hibernate.boot.Metadata; import org.hibernate.boot.model.FunctionContributions; @@ -25,6 +26,8 @@ import org.hibernate.dialect.sql.ast.SpannerSqlAstTranslator; import org.hibernate.dialect.unique.UniqueDelegate; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; +import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder; import org.hibernate.engine.jdbc.env.spi.SchemaNameResolver; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -50,6 +53,8 @@ import org.hibernate.type.BasicTypeRegistry; import org.hibernate.type.StandardBasicTypes; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; import java.util.Date; import java.util.Map; @@ -502,6 +507,16 @@ public Exporter getTableExporter() { return this.spannerTableExporter; } + @Override + public String getCreateTableString() { + return "create table if not exists"; + } + + @Override + public boolean supportsIfExistsBeforeTableName() { + return true; + } + /* SELECT-related functions */ @Override @@ -895,6 +910,32 @@ public boolean supportsRowValueConstructorSyntaxInInList() { return false; } + @Override + public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() { + return DmlTargetColumnQualifierSupport.TABLE_ALIAS; + } + + @Override + public IdentifierHelper buildIdentifierHelper( + IdentifierHelperBuilder builder, + DatabaseMetaData metadata) throws SQLException { + builder.applyReservedWords( metadata ); + builder.setAutoQuoteKeywords( true ); + builder.setAutoQuoteDollar( true ); + return super.buildIdentifierHelper( builder, metadata ); + } + + @Override + public ScrollMode defaultScrollMode() { + return ScrollMode.FORWARD_ONLY; + } + + @Override + public String getTruncateTableStatement(String tableName) { + // spanner doesn't have truncate command, so we delete + return "delete from " + tableName + " where true"; + } + /* Type conversion and casting */ /** diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialectTableExporter.java b/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialectTableExporter.java index a5e9d0dc339a..225285a124d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialectTableExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialectTableExporter.java @@ -13,12 +13,14 @@ import java.util.stream.StreamSupport; import org.hibernate.boot.Metadata; +import org.hibernate.boot.model.relational.InitCommand; import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.mapping.Column; import org.hibernate.mapping.Index; import org.hibernate.mapping.Table; import org.hibernate.tool.schema.spi.Exporter; +import static java.util.Collections.addAll; import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_STRING_ARRAY; /** @@ -67,7 +69,7 @@ else if ( !table.getForeignKeyCollection().isEmpty() ) { private String[] getTableString(Table table, Metadata metadata, Iterable keyColumns, SqlStringGenerationContext context) { String primaryKeyColNames = StreamSupport.stream( keyColumns.spliterator(), false ) - .map( Column::getName ) + .map( col -> col.getQuotedName( spannerDialect ) ) .collect( Collectors.joining( "," ) ); StringJoiner colsAndTypes = new StringJoiner( "," ); @@ -76,7 +78,7 @@ private String[] getTableString(Table table, Metadata metadata, Iterable for ( Column column : table.getColumns() ) { final String sqlType = column.getSqlType( metadata ); final String columnDeclaration = - column.getName() + column.getQuotedName(spannerDialect) + " " + sqlType + ( column.isNullable() ? this.spannerDialect.getNullColumnString( sqlType ) : " not null" ); colsAndTypes.add( columnDeclaration ); @@ -92,6 +94,10 @@ private String[] getTableString(Table table, Metadata metadata, Iterable ) ); + for ( InitCommand initCommand : table.getInitCommands( context ) ) { + addAll( statements, initCommand.initCommands() ); + } + return statements.toArray(EMPTY_STRING_ARRAY); } @@ -106,7 +112,7 @@ public String[] getSqlDropStrings(Table table, Metadata metadata, SqlStringGener ArrayList dropStrings = new ArrayList<>(); for ( Index index : table.getIndexes().values() ) { - dropStrings.add( "drop index " + index.getName() ); + dropStrings.add( "drop index if exists " + index.getName() ); } dropStrings.add( this.spannerDialect.getDropTableString( context.format( table.getQualifiedTableName() ) ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java index d2f944187055..493639b3abfc 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java @@ -62,6 +62,7 @@ * * @author Steve Ebersole * @author Gavin King + * @author Yoobin Yoon */ public class CommonFunctionFactory { @@ -3321,6 +3322,108 @@ public void arrayTrim_oracle() { functionRegistry.register( "array_trim", new OracleArrayTrimFunction() ); } + /** + * CockroachDB and PostgreSQL array_reverse() function + */ + public void arrayReverse() { + functionRegistry.namedDescriptorBuilder( "array_reverse" ) + .setArgumentsValidator( + StandardArgumentsValidators.composite( + StandardArgumentsValidators.exactly( 1 ), + ArrayArgumentValidator.DEFAULT_INSTANCE + ) ) + .setReturnTypeResolver( ArrayViaArgumentReturnTypeResolver.DEFAULT_INSTANCE ) + .setArgumentTypeResolver( + StandardFunctionArgumentTypeResolvers.composite( + StandardFunctionArgumentTypeResolvers.invariant( ANY ) + ) ) + .setArgumentListSignature( "(ARRAY array)" ) + .register(); + } + + /** + * array_reverse() emulation for PostgreSQL versions before 18 and HSQLDB + */ + public void arrayReverse_unnest() { + functionRegistry.register( "array_reverse", new PostgreSQLArrayReverseEmulation() ); + } + + /** + * Oracle array_reverse() function + */ + public void arrayReverse_oracle() { + functionRegistry.register( "array_reverse", new OracleArrayReverseFunction() ); + } + + /** + * H2 array_reverse() function + */ + public void arrayReverse_h2(int maximumArraySize) { + functionRegistry.register( "array_reverse", new H2ArrayReverseFunction( maximumArraySize ) ); + } + + /** + * CockroachDB and PostgreSQL array_sort() function + */ + public void arraySort() { + functionRegistry.namedDescriptorBuilder( "array_sort" ) + .setArgumentsValidator( + new ArgumentTypesValidator( + StandardArgumentsValidators.composite( + StandardArgumentsValidators.between( 1, 3 ), + ArrayArgumentValidator.DEFAULT_INSTANCE + ), + FunctionParameterType.ANY, + FunctionParameterType.BOOLEAN, + FunctionParameterType.BOOLEAN + ) + ) + .setReturnTypeResolver( ArrayViaArgumentReturnTypeResolver.DEFAULT_INSTANCE ) + .setArgumentTypeResolver( + StandardFunctionArgumentTypeResolvers.composite( + StandardFunctionArgumentTypeResolvers.invariant( ANY ), + StandardFunctionArgumentTypeResolvers.invariant( + typeConfiguration, + FunctionParameterType.BOOLEAN + ), + StandardFunctionArgumentTypeResolvers.invariant( + typeConfiguration, + FunctionParameterType.BOOLEAN + ) + ) + ) + .setArgumentListSignature( "(ARRAY array[, boolean descending[, boolean nulls_first]])" ) + .register(); + } + + /** + * PostgreSQL array_sort() emulation for versions before 18 + */ + public void arraySort_unnest() { + functionRegistry.register( "array_sort", new PostgreSQLArraySortEmulation( typeConfiguration ) ); + } + + /** + * Oracle array_sort() function + */ + public void arraySort_oracle() { + functionRegistry.register( "array_sort", new OracleArraySortFunction( typeConfiguration ) ); + } + + /** + * H2 array_sort() function + */ + public void arraySort_h2(int maximumArraySize) { + functionRegistry.register( "array_sort", new H2ArraySortFunction( maximumArraySize, typeConfiguration ) ); + } + + /** + * HSQL array_sort() function + */ + public void arraySort_hsql() { + functionRegistry.register( "array_sort", new HSQLArraySortFunction( typeConfiguration ) ); + } + /** * H2 array_fill() function */ diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/NumberSeriesGenerateSeriesFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/NumberSeriesGenerateSeriesFunction.java index 2633d0acbd5e..b2054ee942ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/NumberSeriesGenerateSeriesFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/NumberSeriesGenerateSeriesFunction.java @@ -48,7 +48,7 @@ import org.hibernate.sql.ast.tree.predicate.PredicateContainer; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.internal.SqlSelectionImpl; import org.hibernate.type.BasicType; import org.hibernate.type.SqlTypes; @@ -458,10 +458,10 @@ private static String timestampadd(String startExpression, String stepExpression type ) ) ); - final SqlAstTranslator translator = + final SqlAstTranslator translator = creationContext.getDialect().getSqlAstTranslatorFactory() .buildSelectTranslator( creationContext.getSessionFactory(), new SelectStatement( fakeQuery ) ); - final JdbcOperationQuerySelect operation = translator.translate( null, QueryOptions.NONE ); + final JdbcSelect operation = translator.translate( null, QueryOptions.NONE ); final String sqlString = operation.getSqlString(); assert sqlString.startsWith( "select " ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArrayReverseFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArrayReverseFunction.java new file mode 100644 index 000000000000..7e759ed59fb8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArrayReverseFunction.java @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.function.array; + +import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers; + +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.ANY; + +/** + * Encapsulates the validator, return type and argument type resolvers for the array_reverse functions. + * Subclasses only have to implement the rendering. + */ +public abstract class AbstractArrayReverseFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + + public AbstractArrayReverseFunction() { + super( + "array_reverse", + StandardArgumentsValidators.composite( + StandardArgumentsValidators.exactly( 1 ), + ArrayArgumentValidator.DEFAULT_INSTANCE + ), + ArrayViaArgumentReturnTypeResolver.DEFAULT_INSTANCE, + StandardFunctionArgumentTypeResolvers.composite( + StandardFunctionArgumentTypeResolvers.invariant( ANY ) + ) + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArraySortFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArraySortFunction.java new file mode 100644 index 000000000000..ca45dad8288b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/AbstractArraySortFunction.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.function.array; + +import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator; +import org.hibernate.query.sqm.produce.function.FunctionParameterType; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers; +import org.hibernate.type.spi.TypeConfiguration; + +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.ANY; + +/** + * Encapsulates the validator, return type and argument type resolvers for the array_sort functions. + * Subclasses only have to implement the rendering. + */ +public abstract class AbstractArraySortFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + + public AbstractArraySortFunction(TypeConfiguration typeConfiguration) { + super( + "array_sort", + new ArgumentTypesValidator( + StandardArgumentsValidators.composite( + StandardArgumentsValidators.between( 1, 3 ), + ArrayArgumentValidator.DEFAULT_INSTANCE + ), + FunctionParameterType.ANY, + FunctionParameterType.BOOLEAN, + FunctionParameterType.BOOLEAN + ), + ArrayViaArgumentReturnTypeResolver.DEFAULT_INSTANCE, + StandardFunctionArgumentTypeResolvers.composite( + StandardFunctionArgumentTypeResolvers.invariant( ANY ), + StandardFunctionArgumentTypeResolvers.invariant( + typeConfiguration, + FunctionParameterType.BOOLEAN + ), + StandardFunctionArgumentTypeResolvers.invariant( + typeConfiguration, + FunctionParameterType.BOOLEAN + ) + ) + ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/DdlTypeHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/DdlTypeHelper.java index 1e5be6ab186c..d8690ac17055 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/DdlTypeHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/DdlTypeHelper.java @@ -7,9 +7,7 @@ import java.lang.reflect.Type; import java.util.List; -import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.Size; -import org.hibernate.internal.build.AllowReflection; import org.hibernate.metamodel.mapping.JdbcMappingContainer; import org.hibernate.metamodel.mapping.SqlTypedMapping; import org.hibernate.metamodel.model.domain.DomainType; @@ -17,24 +15,20 @@ import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.java.BasicPluralJavaType; -import org.hibernate.type.descriptor.sql.DdlType; -import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; import org.hibernate.type.internal.ParameterizedTypeImpl; import org.hibernate.type.spi.TypeConfiguration; public class DdlTypeHelper { @SuppressWarnings("unchecked") - @AllowReflection +// @AllowReflection public static BasicType resolveArrayType(DomainType elementType, TypeConfiguration typeConfiguration) { - @SuppressWarnings("unchecked") final var arrayJavaType = (BasicPluralJavaType) typeConfiguration.getJavaTypeRegistry() .resolveArrayDescriptor( elementType.getJavaType() ); - final Dialect dialect = typeConfiguration.getCurrentBaseSqlTypeIndicators().getDialect(); return arrayJavaType.resolveType( typeConfiguration, - dialect, + typeConfiguration.getCurrentBaseSqlTypeIndicators().getDialect(), (BasicType) elementType, null, typeConfiguration.getCurrentBaseSqlTypeIndicators() @@ -43,19 +37,17 @@ public static BasicType resolveArrayType(DomainType elementType, TypeConfi @SuppressWarnings("unchecked") public static BasicType resolveListType(DomainType elementType, TypeConfiguration typeConfiguration) { - @SuppressWarnings("unchecked") - final BasicPluralJavaType arrayJavaType = + final var arrayJavaType = (BasicPluralJavaType) typeConfiguration.getJavaTypeRegistry() - .getDescriptor( List.class ) + .resolveDescriptor( List.class ) .createJavaType( - new ParameterizedTypeImpl( List.class, new Type[]{ elementType.getJavaType() }, null ), + new ParameterizedTypeImpl( List.class, new Type[] { elementType.getJavaType() }, null ), typeConfiguration ); - final Dialect dialect = typeConfiguration.getCurrentBaseSqlTypeIndicators().getDialect(); return arrayJavaType.resolveType( typeConfiguration, - dialect, + typeConfiguration.getCurrentBaseSqlTypeIndicators().getDialect(), (BasicType) elementType, null, typeConfiguration.getCurrentBaseSqlTypeIndicators() @@ -79,11 +71,9 @@ public static String getTypeName(JdbcMappingContainer type, Size size, TypeConfi return AbstractSqlAstTranslator.getSqlTypeName( sqlTypedMapping, typeConfiguration ); } else { - final BasicType basicType = (BasicType) type.getSingleJdbcMapping(); - final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); - final DdlType ddlType = ddlTypeRegistry.getDescriptor( - basicType.getJdbcType().getDdlTypeCode() - ); + final var basicType = (BasicType) type.getSingleJdbcMapping(); + final var ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); + final var ddlType = ddlTypeRegistry.getDescriptor( basicType.getJdbcType().getDdlTypeCode() ); return ddlType.getTypeName( size, basicType, ddlTypeRegistry ); } } @@ -97,11 +87,9 @@ public static String getTypeName(ReturnableType type, Size size, TypeConfigur return AbstractSqlAstTranslator.getSqlTypeName( sqlTypedMapping, typeConfiguration ); } else { - final BasicType basicType = (BasicType) ( (JdbcMappingContainer) type ).getSingleJdbcMapping(); - final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); - final DdlType ddlType = ddlTypeRegistry.getDescriptor( - basicType.getJdbcType().getDdlTypeCode() - ); + final var basicType = (BasicType) ( (JdbcMappingContainer) type ).getSingleJdbcMapping(); + final var ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); + final var ddlType = ddlTypeRegistry.getDescriptor( basicType.getJdbcType().getDdlTypeCode() ); return ddlType.getTypeName( size, basicType, ddlTypeRegistry ); } } @@ -123,11 +111,9 @@ public static String getCastTypeName(JdbcMappingContainer type, Size size, TypeC return AbstractSqlAstTranslator.getCastTypeName( sqlTypedMapping, typeConfiguration ); } else { - final BasicType basicType = (BasicType) type.getSingleJdbcMapping(); - final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); - final DdlType ddlType = ddlTypeRegistry.getDescriptor( - basicType.getJdbcType().getDdlTypeCode() - ); + final var basicType = (BasicType) type.getSingleJdbcMapping(); + final var ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); + final var ddlType = ddlTypeRegistry.getDescriptor( basicType.getJdbcType().getDdlTypeCode() ); return ddlType.getCastTypeName( size, basicType, ddlTypeRegistry ); } } @@ -141,11 +127,9 @@ public static String getCastTypeName(ReturnableType type, Size size, TypeConf return AbstractSqlAstTranslator.getCastTypeName( sqlTypedMapping, typeConfiguration ); } else { - final BasicType basicType = (BasicType) ( (JdbcMappingContainer) type ).getSingleJdbcMapping(); - final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); - final DdlType ddlType = ddlTypeRegistry.getDescriptor( - basicType.getJdbcType().getDdlTypeCode() - ); + final var basicType = (BasicType) ( (JdbcMappingContainer) type ).getSingleJdbcMapping(); + final var ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); + final var ddlType = ddlTypeRegistry.getDescriptor( basicType.getJdbcType().getDdlTypeCode() ); return ddlType.getCastTypeName( size, basicType, ddlTypeRegistry ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayReverseFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayReverseFunction.java new file mode 100644 index 000000000000..0572556a0f3a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArrayReverseFunction.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.function.array; + +import java.util.List; + +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; + +/** + * H2 requires a very special emulation, because {@code unnest} is pretty much useless, + * due to https://github.com/h2database/h2database/issues/1815. + * This emulation uses {@code array_get}, {@code cardinality} and {@code system_range} + * functions to achieve array reversal. + */ +public class H2ArrayReverseFunction extends AbstractArrayReverseFunction { + + private final int maximumArraySize; + + public H2ArrayReverseFunction(int maximumArraySize) { + this.maximumArraySize = maximumArraySize; + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + + sqlAppender.append( "case when " ); + arrayExpression.accept( walker ); + sqlAppender.append( " is not null then coalesce((select array_agg(array_get(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ",i.idx) order by i.idx desc) from system_range(1," ); + sqlAppender.append( Integer.toString( maximumArraySize ) ); + sqlAppender.append( ") i(idx) where i.idx<=coalesce(cardinality(" ); + arrayExpression.accept( walker ); + sqlAppender.append( "),0)),array[]) end" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArraySortFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArraySortFunction.java new file mode 100644 index 000000000000..e462c9a52664 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/H2ArraySortFunction.java @@ -0,0 +1,132 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.function.array; + +import java.util.List; + +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.Literal; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * H2 requires a very special emulation, because {@code unnest} is pretty much useless, + * due to https://github.com/h2database/h2database/issues/1815. + * This emulation uses {@code array_get}, {@code cardinality} and {@code system_range} + * functions to achieve array sorting. + */ +public class H2ArraySortFunction extends AbstractArraySortFunction { + + private final int maximumArraySize; + + public H2ArraySortFunction(int maximumArraySize, TypeConfiguration typeConfiguration) { + super( typeConfiguration ); + this.maximumArraySize = maximumArraySize; + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + + final boolean areArgumentsLiterals = + ( sqlAstArguments.size() <= 1 || sqlAstArguments.get( 1 ) instanceof Literal ) && + ( sqlAstArguments.size() <= 2 || sqlAstArguments.get( 2 ) instanceof Literal ); + + if ( areArgumentsLiterals ) { + renderWithLiteralArguments( sqlAppender, sqlAstArguments, walker ); + } + else { + renderWithExpressionArguments( sqlAppender, sqlAstArguments, walker ); + } + } + + private void renderWithLiteralArguments( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + + final boolean descending = sqlAstArguments.size() > 1 + && sqlAstArguments.get( 1 ) instanceof Literal literal + && literal.getLiteralValue() instanceof Boolean boolValue + ? boolValue : false; + + final Boolean nullsFirst = sqlAstArguments.size() > 2 + && sqlAstArguments.get( 2 ) instanceof Literal literal + && literal.getLiteralValue() instanceof Boolean boolValue + ? boolValue : null; + + final boolean actualNullsFirst = nullsFirst != null ? nullsFirst : descending; + + sqlAppender.append( "case when " ); + arrayExpression.accept( walker ); + sqlAppender.append( " is not null then coalesce((select array_agg(array_get(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ",i.idx) order by array_get(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ",i.idx)" ); + + sqlAppender.append( descending + ? ( actualNullsFirst ? " desc nulls first" : " desc nulls last" ) + : ( actualNullsFirst ? " asc nulls first" : " asc nulls last" ) ); + + sqlAppender.append( ") from system_range(1," ); + sqlAppender.append( Integer.toString( maximumArraySize ) ); + sqlAppender.append( ") i(idx) where i.idx<=coalesce(cardinality(" ); + arrayExpression.accept( walker ); + sqlAppender.append( "),0)),array[]) end" ); + } + + private void renderWithExpressionArguments( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + + assert sqlAstArguments.size() >= 2; + + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + final SqlAstNode descendingNode = sqlAstArguments.get( 1 ); + final SqlAstNode nullsFirstNode = sqlAstArguments.size() > 2 + ? sqlAstArguments.get( 2 ) + : descendingNode; + + sqlAppender.append( "case when " ); + arrayExpression.accept( walker ); + sqlAppender.append( " is not null then coalesce((select array_agg(array_get(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ",i.idx) order by " ); + + sqlAppender.append( '(' ); + nullsFirstNode.accept( walker ); + sqlAppender.append( "=(array_get(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ",i.idx) is null)) desc," ); + + sqlAppender.append( "case when " ); + descendingNode.accept( walker ); + sqlAppender.append( " then array_get(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ",i.idx) end desc," ); + + sqlAppender.append( "case when not " ); + descendingNode.accept( walker ); + sqlAppender.append( " then array_get(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ",i.idx) end" ); + + sqlAppender.append( ") from system_range(1," ); + sqlAppender.append( Integer.toString( maximumArraySize ) ); + sqlAppender.append( ") i(idx) where i.idx<=coalesce(cardinality(" ); + arrayExpression.accept( walker ); + sqlAppender.append( "),0)),array[]) end" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArraySortFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArraySortFunction.java new file mode 100644 index 000000000000..f344c0e5326f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArraySortFunction.java @@ -0,0 +1,111 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.function.array; + +import java.util.List; + +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.Literal; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * HSQLDB sort_array function. + */ +public class HSQLArraySortFunction extends AbstractArraySortFunction { + + public HSQLArraySortFunction(TypeConfiguration typeConfiguration) { + super( typeConfiguration ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + + final boolean areArgumentsLiterals = + ( sqlAstArguments.size() <= 1 || sqlAstArguments.get( 1 ) instanceof Literal ) && + ( sqlAstArguments.size() <= 2 || sqlAstArguments.get( 2 ) instanceof Literal ); + + if ( areArgumentsLiterals ) { + renderWithLiteralArguments( sqlAppender, sqlAstArguments, walker ); + } + else { + renderWithExpressionArguments( sqlAppender, sqlAstArguments, walker ); + } + } + + private void renderWithLiteralArguments( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + + final boolean descending = sqlAstArguments.size() > 1 + && sqlAstArguments.get( 1 ) instanceof Literal literal + && literal.getLiteralValue() instanceof Boolean boolValue + ? boolValue : false; + + final Boolean nullsFirst = sqlAstArguments.size() > 2 + && sqlAstArguments.get( 2 ) instanceof Literal literal + && literal.getLiteralValue() instanceof Boolean boolValue + ? boolValue : null; + + final boolean actualNullsFirst = nullsFirst != null ? nullsFirst : descending; + + sqlAppender.append( "sort_array(" ); + arrayExpression.accept( walker ); + + sqlAppender.append( descending + ? ( actualNullsFirst ? " desc nulls first" : " desc nulls last" ) + : ( actualNullsFirst ? " asc nulls first" : " asc nulls last" ) ); + + sqlAppender.append( ')' ); + } + + private void renderWithExpressionArguments( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + + assert sqlAstArguments.size() >= 2; + + final SqlAstNode arrayExpression = sqlAstArguments.get( 0 ); + final SqlAstNode descendingNode = sqlAstArguments.get( 1 ); + final SqlAstNode nullsFirstNode = sqlAstArguments.size() > 2 + ? sqlAstArguments.get( 2 ) + : descendingNode; + + sqlAppender.append( "case when " ); + arrayExpression.accept( walker ); + sqlAppender.append( " is not null then " ); + + sqlAppender.append( "coalesce((select array_agg(t.val order by " ); + + sqlAppender.append( '(' ); + nullsFirstNode.accept( walker ); + sqlAppender.append( "=(t.val is null)) desc," ); + + sqlAppender.append( "case when " ); + descendingNode.accept( walker ); + sqlAppender.append( " then t.val end desc," ); + + sqlAppender.append( "case when not " ); + descendingNode.accept( walker ); + sqlAppender.append( " then t.val end" ); + + sqlAppender.append( ") from unnest(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ") t(val))" ); + + sqlAppender.append( ",array[]) end" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/JsonArrayViaElementArgumentReturnTypeResolver.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/JsonArrayViaElementArgumentReturnTypeResolver.java index ce7ed772650b..4ddb37bf909e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/JsonArrayViaElementArgumentReturnTypeResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/JsonArrayViaElementArgumentReturnTypeResolver.java @@ -7,9 +7,7 @@ import java.util.List; import java.util.function.Supplier; -import org.hibernate.internal.build.AllowReflection; import org.hibernate.metamodel.mapping.BasicValuedMapping; -import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.ReturnableType; import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver; @@ -20,7 +18,6 @@ import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.jdbc.DelegatingJdbcTypeIndicators; -import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.spi.TypeConfiguration; import org.checkerframework.checker.nullness.qual.Nullable; @@ -44,12 +41,12 @@ public ReturnableType resolveFunctionReturnType( TypeConfiguration typeConfiguration) { if ( converter != null ) { if ( converter.isInTypeInference() ) { - // Don't default to a Json array when in type inference mode. + // Don't default to a JSON array when in type inference mode. // Comparing e.g. `array() = (select array_agg() ...)` will trigger this resolver // while inferring the type for `array()`, which we want to avoid. return null; } - final MappingModelExpressible inferredType = converter.resolveFunctionImpliedReturnType(); + final var inferredType = converter.resolveFunctionImpliedReturnType(); if ( inferredType != null ) { if ( inferredType instanceof ReturnableType returnableType ) { return returnableType; @@ -62,8 +59,8 @@ else if ( inferredType instanceof BasicValuedMapping basicValuedMapping ) { if ( impliedType != null ) { return impliedType; } - for ( SqmTypedNode argument : arguments ) { - final DomainType sqmType = argument.getExpressible().getSqmType(); + for ( var argument : arguments ) { + final var sqmType = argument.getExpressible().getSqmType(); if ( sqmType instanceof ReturnableType ) { return resolveJsonArrayType( sqmType, typeConfiguration ); } @@ -78,14 +75,14 @@ public BasicValuedMapping resolveFunctionReturnType( return null; } - @AllowReflection +// @AllowReflection public static BasicType resolveJsonArrayType(DomainType elementType, TypeConfiguration typeConfiguration) { @SuppressWarnings("unchecked") final var arrayJavaType = (BasicPluralJavaType) typeConfiguration.getJavaTypeRegistry() .resolveArrayDescriptor( elementType.getJavaType() ); - final JdbcTypeIndicators currentBaseSqlTypeIndicators = typeConfiguration.getCurrentBaseSqlTypeIndicators(); + final var currentBaseSqlTypeIndicators = typeConfiguration.getCurrentBaseSqlTypeIndicators(); return arrayJavaType.resolveType( typeConfiguration, currentBaseSqlTypeIndicators.getDialect(), diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayReverseFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayReverseFunction.java new file mode 100644 index 000000000000..c00fa75aea00 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayReverseFunction.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.function.array; + +import java.util.List; + +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; + +/** + * Oracle array_reverse function. + */ +public class OracleArrayReverseFunction extends AbstractArrayReverseFunction { + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + final String arrayTypeName = DdlTypeHelper.getTypeName( + arrayExpression.getExpressionType(), + walker.getSessionFactory().getTypeConfiguration() + ); + sqlAppender.append( arrayTypeName ); + sqlAppender.append( "_reverse(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ')' ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArraySortFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArraySortFunction.java new file mode 100644 index 000000000000..e6f9bf7aba99 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArraySortFunction.java @@ -0,0 +1,75 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.function.array; + +import java.util.List; + +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.type.spi.TypeConfiguration; + +import static org.hibernate.internal.util.NullnessUtil.castNonNull; + +/** + * Oracle array_sort function. + */ +public class OracleArraySortFunction extends AbstractArraySortFunction { + + public OracleArraySortFunction(TypeConfiguration typeConfiguration) { + super( typeConfiguration ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + final String arrayTypeName = DdlTypeHelper.getTypeName( + arrayExpression.getExpressionType(), + walker.getSessionFactory().getTypeConfiguration() + ); + + sqlAppender.append( arrayTypeName ); + sqlAppender.append( "_sort(" ); + arrayExpression.accept( walker ); + + if ( sqlAstArguments.size() > 1 ) { + sqlAppender.append( ',' ); + final Expression descNode = (Expression) sqlAstArguments.get( 1 ); + sqlAppender.append( "case when " ); + descNode.accept( walker ); + sqlAppender.append( '=' ); + var sessionFactory = walker.getSessionFactory(); + castNonNull( descNode.getExpressionType() ).getSingleJdbcMapping().getJdbcLiteralFormatter() + .appendJdbcLiteral( + sqlAppender, + true, + sessionFactory.getJdbcServices().getDialect(), + sessionFactory.getWrapperOptions() + ); + sqlAppender.append( " then 1 else 0 end" ); + if ( sqlAstArguments.size() > 2 ) { + sqlAppender.append( ",case when " ); + final Expression nullsNode = (Expression) sqlAstArguments.get( 2 ); + nullsNode.accept( walker ); + sqlAppender.append( '=' ); + castNonNull( nullsNode.getExpressionType() ).getSingleJdbcMapping().getJdbcLiteralFormatter() + .appendJdbcLiteral( + sqlAppender, + true, + sessionFactory.getJdbcServices().getDialect(), + sessionFactory.getWrapperOptions() + ); + sqlAppender.append( " then 1 else 0 end" ); + } + } + sqlAppender.append( ')' ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLArrayReverseEmulation.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLArrayReverseEmulation.java new file mode 100644 index 000000000000..5c0e4adf7581 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLArrayReverseEmulation.java @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.function.array; + +import java.util.List; + +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.type.BasicPluralType; +import org.hibernate.type.BasicType; + +/** + * PostgreSQL array_reverse emulation for versions before 18. + * HSQLDB uses the same approach. + */ +public class PostgreSQLArrayReverseEmulation extends AbstractArrayReverseFunction { + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final SqlAstNode arrayExpression = sqlAstArguments.get( 0 ); + + sqlAppender.append( "case when " ); + arrayExpression.accept( walker ); + sqlAppender.append( " is not null then " ); + sqlAppender.append( "coalesce((select array_agg(t.val order by t.idx desc) from unnest(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ") with ordinality t(val,idx)" ); + + String arrayTypeName = null; + if ( returnType instanceof BasicPluralType pluralType ) { + if ( needsArrayCasting( pluralType.getElementType() ) ) { + arrayTypeName = DdlTypeHelper.getCastTypeName( + returnType, + walker.getSessionFactory().getTypeConfiguration() + ); + } + } + if ( arrayTypeName != null ) { + sqlAppender.append( "),cast(array[] as " ); + sqlAppender.appendSql( arrayTypeName ); + sqlAppender.appendSql( "))" ); + } + else { + sqlAppender.append( "),array[])" ); + } + sqlAppender.append( " end" ); + } + + private static boolean needsArrayCasting(BasicType elementType) { + return elementType.getJdbcType().isString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLArraySortEmulation.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLArraySortEmulation.java new file mode 100644 index 000000000000..b7cfa927563e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLArraySortEmulation.java @@ -0,0 +1,154 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.function.array; + +import java.util.List; + +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Literal; +import org.hibernate.type.BasicPluralType; +import org.hibernate.type.BasicType; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * PostgreSQL array_sort emulation for versions before 18. + */ +public class PostgreSQLArraySortEmulation extends AbstractArraySortFunction { + + public PostgreSQLArraySortEmulation(TypeConfiguration typeConfiguration) { + super( typeConfiguration ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + + final boolean areArgumentsLiterals = + ( sqlAstArguments.size() <= 1 || sqlAstArguments.get( 1 ) instanceof Literal ) && + ( sqlAstArguments.size() <= 2 || sqlAstArguments.get( 2 ) instanceof Literal ); + + if ( areArgumentsLiterals ) { + renderWithLiteralArguments( sqlAppender, sqlAstArguments, returnType, walker ); + } + else { + renderWithExpressionArguments( sqlAppender, sqlAstArguments, returnType, walker ); + } + } + + private void renderWithLiteralArguments( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + + final SqlAstNode arrayExpression = sqlAstArguments.get( 0 ); + + final boolean descending = sqlAstArguments.size() > 1 + && sqlAstArguments.get( 1 ) instanceof Literal literal + && literal.getLiteralValue() instanceof Boolean + ? (Boolean) literal.getLiteralValue() + : false; + + final Boolean nullsFirst = sqlAstArguments.size() > 2 + && sqlAstArguments.get( 2 ) instanceof Literal literal + && literal.getLiteralValue() instanceof Boolean + ? (Boolean) literal.getLiteralValue() + : null; + + final boolean actualNullsFirst = nullsFirst != null ? nullsFirst : descending; + + sqlAppender.append( "case when " ); + arrayExpression.accept( walker ); + sqlAppender.append( " is not null then " ); + + sqlAppender.append( "coalesce((select array_agg(t.val order by t.val" ); + + sqlAppender.append( descending + ? ( actualNullsFirst ? " desc nulls first" : " desc nulls last" ) + : ( actualNullsFirst ? " asc nulls first" : " asc nulls last" ) ); + + sqlAppender.append( ") from unnest(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ") t(val))" ); + + appendEmptyArrayWithCasting( sqlAppender, returnType, walker ); + sqlAppender.append( " end" ); + } + + private void renderWithExpressionArguments( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + + assert sqlAstArguments.size() >= 2; + + final SqlAstNode arrayExpression = sqlAstArguments.get( 0 ); + final SqlAstNode descendingNode = sqlAstArguments.get( 1 ); + final SqlAstNode nullsFirstNode = sqlAstArguments.size() > 2 + ? sqlAstArguments.get( 2 ) + : descendingNode; + + sqlAppender.append( "case when " ); + arrayExpression.accept( walker ); + sqlAppender.append( " is not null then " ); + + sqlAppender.append( "coalesce((select array_agg(t.val order by " ); + + sqlAppender.append( '(' ); + nullsFirstNode.accept( walker ); + sqlAppender.append( "=(t.val is null)) desc," ); + + sqlAppender.append( "case when " ); + descendingNode.accept( walker ); + sqlAppender.append( " then t.val end desc," ); + + sqlAppender.append( "case when not " ); + descendingNode.accept( walker ); + sqlAppender.append( " then t.val end" ); + + sqlAppender.append( ") from unnest(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ") t(val))" ); + + appendEmptyArrayWithCasting( sqlAppender, returnType, walker ); + sqlAppender.append( " end" ); + } + + private void appendEmptyArrayWithCasting( + SqlAppender sqlAppender, + ReturnableType returnType, + SqlAstTranslator walker) { + + String arrayTypeName = null; + if ( returnType instanceof BasicPluralType pluralType ) { + if ( needsArrayCasting( pluralType.getElementType() ) ) { + arrayTypeName = DdlTypeHelper.getCastTypeName( + returnType, + walker.getSessionFactory().getTypeConfiguration() + ); + } + } + + if ( arrayTypeName != null ) { + sqlAppender.append( ",cast(array[] as " ); + sqlAppender.appendSql( arrayTypeName ); + sqlAppender.appendSql( "))" ); + } + else { + sqlAppender.append( ",array[])" ); + } + } + + private static boolean needsArrayCasting(BasicType elementType) { + return elementType.getJdbcType().isString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractPessimisticUpdateLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractPessimisticUpdateLockingStrategy.java index de3ebdbda91e..af1b1c4709d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractPessimisticUpdateLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractPessimisticUpdateLockingStrategy.java @@ -10,12 +10,11 @@ import org.hibernate.StaleObjectStateException; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.pretty.MessageHelper; import org.hibernate.sql.Update; import java.sql.SQLException; -import static org.hibernate.internal.CoreMessageLogger.CORE_LOGGER; +import static org.hibernate.pretty.MessageHelper.infoString; /** * Common implementation of {@link PessimisticReadUpdateLockingStrategy} @@ -39,13 +38,15 @@ public abstract class AbstractPessimisticUpdateLockingStrategy implements Lockin public AbstractPessimisticUpdateLockingStrategy(EntityPersister lockable, LockMode lockMode) { this.lockable = lockable; this.lockMode = lockMode; - if ( !lockable.isVersioned() ) { - CORE_LOGGER.writeLocksNotSupported( lockable.getEntityName() ); - this.sql = null; + if ( lockMode.lessThan( LockMode.PESSIMISTIC_READ ) ) { + throw new HibernateException( "Lock mode " + lockMode + + " not valid for locking via 'update' statement" ); } - else { - this.sql = generateLockString(); + if ( !lockable.isVersioned() ) { + throw new HibernateException( "Entity '" + lockable.getEntityName() + + "' has no version and may not be locked via 'update' statement" ); } + this.sql = generateLockString(); } @Override @@ -54,14 +55,11 @@ public void lock(Object id, Object version, Object object, int timeout, SharedSe doLock( id, version, session ); } catch (JDBCException e) { - throw new PessimisticEntityLockException( object, "could not obtain pessimistic lock", e ); + throw new PessimisticEntityLockException( object, "Could not obtain pessimistic lock", e ); } } void doLock(Object id, Object version, SharedSessionContractImplementor session) { - if ( !lockable.isVersioned() ) { - throw new HibernateException( "write locks via update not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); - } try { final var factory = session.getFactory(); final var jdbcCoordinator = session.getJdbcCoordinator(); @@ -100,7 +98,7 @@ void doLock(Object id, Object version, SharedSessionContractImplementor session) catch ( SQLException e ) { throw session.getJdbcServices().getSqlExceptionHelper().convert( e, - "could not lock: " + MessageHelper.infoString( lockable, id, session.getFactory() ), + "could not lock: " + infoString( lockable, id, session.getFactory() ), sql ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java index 5cf5ab687068..5276bd42a9d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java @@ -34,15 +34,17 @@ public OptimisticForceIncrementLockingStrategy(EntityPersister lockable, LockMod this.lockable = lockable; this.lockMode = lockMode; if ( lockMode.lessThan( LockMode.OPTIMISTIC_FORCE_INCREMENT ) ) { - throw new HibernateException( "[" + lockMode + "] not valid for [" + lockable.getEntityName() + "]" ); + throw new HibernateException( "Entity '" + lockable.getEntityName() + + "' may not be locked at level " + lockMode ); + } + if ( !lockable.isVersioned() ) { + throw new HibernateException( "Entity '" + lockable.getEntityName() + + "' has no version and may not be locked at level " + lockMode); } } @Override public void lock(Object id, Object version, Object object, int timeout, EventSource session) { - if ( !lockable.isVersioned() ) { - throw new HibernateException( "[" + lockMode + "] not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); - } // final EntityEntry entry = session.getPersistenceContextInternal().getEntry( object ); // Register the EntityIncrementVersionProcess action to run just prior to transaction commit. session.getActionQueue().registerCallback( new EntityIncrementVersionProcess( object ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticLockingStrategy.java index 79337885d354..8c8c28260760 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticLockingStrategy.java @@ -33,15 +33,17 @@ public OptimisticLockingStrategy(EntityPersister lockable, LockMode lockMode) { this.lockable = lockable; this.lockMode = lockMode; if ( lockMode.lessThan( LockMode.OPTIMISTIC ) ) { - throw new HibernateException( "[" + lockMode + "] not valid for [" + lockable.getEntityName() + "]" ); + throw new HibernateException( "Entity '" + lockable.getEntityName() + + "' may not be locked at level " + lockMode ); + } + if ( !lockable.isVersioned() ) { + throw new HibernateException( "Entity '" + lockable.getEntityName() + + "' has no version and may not be locked at level " + lockMode); } } @Override public void lock(Object id, Object version, Object object, int timeout, EventSource session) { - if ( !lockable.isVersioned() ) { - throw new HibernateException( "[" + lockMode + "] not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); - } // Register the EntityVerifyVersionProcess action to run just prior to transaction commit. session.getActionQueue().registerCallback( new EntityVerifyVersionProcess( object ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticForceIncrementLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticForceIncrementLockingStrategy.java index cc2df7827e9d..b5e1345455cc 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticForceIncrementLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticForceIncrementLockingStrategy.java @@ -34,15 +34,17 @@ public PessimisticForceIncrementLockingStrategy(EntityPersister lockable, LockMo this.lockMode = lockMode; // ForceIncrement can be used for PESSIMISTIC_READ, PESSIMISTIC_WRITE or PESSIMISTIC_FORCE_INCREMENT if ( lockMode.lessThan( LockMode.PESSIMISTIC_READ ) ) { - throw new HibernateException( "[" + lockMode + "] not valid for [" + lockable.getEntityName() + "]" ); + throw new HibernateException( "Entity '" + lockable.getEntityName() + + "' may not be locked at level " + lockMode ); + } + if ( !lockable.isVersioned() ) { + throw new HibernateException( "Entity '" + lockable.getEntityName() + + "' has no version and may not be locked at level " + lockMode); } } @Override public void lock(Object id, Object version, Object object, int timeout, SharedSessionContractImplementor session) { - if ( !lockable.isVersioned() ) { - throw new HibernateException( "[" + lockMode + "] not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); - } final var entry = session.getPersistenceContextInternal().getEntry( object ); OptimisticLockHelper.forceVersionIncrement( object, entry, session ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java index 717fe17b9b08..51d222654d43 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java @@ -4,7 +4,6 @@ */ package org.hibernate.dialect.lock; -import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.persister.entity.EntityPersister; @@ -30,8 +29,5 @@ public class PessimisticReadUpdateLockingStrategy extends AbstractPessimisticUpd */ public PessimisticReadUpdateLockingStrategy(EntityPersister lockable, LockMode lockMode) { super( lockable, lockMode ); - if ( lockMode.lessThan( LockMode.PESSIMISTIC_READ ) ) { - throw new HibernateException( "[" + lockMode + "] not valid for update statement" ); - } } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java index b809344f1d78..0bfab56ad135 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java @@ -4,7 +4,6 @@ */ package org.hibernate.dialect.lock; -import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.persister.entity.EntityPersister; @@ -29,8 +28,5 @@ public class PessimisticWriteUpdateLockingStrategy extends AbstractPessimisticUp */ public PessimisticWriteUpdateLockingStrategy(EntityPersister lockable, LockMode lockMode) { super( lockable, lockMode ); - if ( lockMode.lessThan( LockMode.PESSIMISTIC_READ ) ) { - throw new HibernateException( "[" + lockMode + "] not valid for update statement" ); - } } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java index cc0e93d1ba20..1e7235629d82 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java @@ -35,7 +35,7 @@ public class UpdateLockingStrategy extends AbstractPessimisticUpdateLockingStrat public UpdateLockingStrategy(EntityPersister lockable, LockMode lockMode) { super( lockable, lockMode ); if ( lockMode.lessThan( LockMode.WRITE ) ) { - throw new HibernateException( "[" + lockMode + "] not valid for update statement" ); + throw new HibernateException( "Lock mode " + lockMode + " not valid for locking via 'update' statement" ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/CockroachSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/CockroachSqlAstTranslator.java index 85bdacbc40ab..43bb87f4e509 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/CockroachSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/CockroachSqlAstTranslator.java @@ -27,6 +27,8 @@ import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl; import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert; +import org.hibernate.sql.model.internal.OptionalTableInsert; +import org.hibernate.sql.model.internal.TableInsertStandard; /** * A SQL AST translator for Cockroach. @@ -47,6 +49,39 @@ public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeti super.visitBinaryArithmeticExpression(arithmeticExpression); } + @Override + public void visitStandardTableInsert(TableInsertStandard tableInsert) { + getCurrentClauseStack().push( Clause.INSERT ); + try { + renderInsertInto( tableInsert ); + if ( tableInsert instanceof OptionalTableInsert optionalTableInsert ) { + appendSql( " on conflict " ); + final String constraintName = optionalTableInsert.getConstraintName(); + if ( constraintName != null ) { + appendSql( " on constraint " ); + appendSql( constraintName ); + } + else { + char separator = '('; + for ( String constraintColumnName : optionalTableInsert.getConstraintColumnNames() ) { + appendSql( separator ); + appendSql( constraintColumnName ); + separator = ','; + } + appendSql( ')' ); + } + appendSql( " do nothing" ); + } + + if ( tableInsert.getNumberOfReturningColumns() > 0 ) { + visitReturningColumns( tableInsert::getReturningColumns ); + } + } + finally { + getCurrentClauseStack().pop(); + } + } + @Override protected JdbcOperationQueryInsert translateInsert(InsertSelectStatement sqlAst) { visitInsertStatement( sqlAst ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MariaDBSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MariaDBSqlAstTranslator.java index 30958f0fe4eb..ffb43eba0725 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MariaDBSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MariaDBSqlAstTranslator.java @@ -416,7 +416,7 @@ INSERT INTO employees (id, name, salary, version) salary = values(salary) */ @Override - protected void renderUpdatevalue(ColumnValueBinding columnValueBinding) { + protected void renderUpdateValue(ColumnValueBinding columnValueBinding) { appendSql( "values(" ); appendSql( columnValueBinding.getColumnReference().getColumnExpression() ); appendSql( ")" ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MySQLSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MySQLSqlAstTranslator.java index f71014d744f8..1a69c8c351a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MySQLSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MySQLSqlAstTranslator.java @@ -449,7 +449,7 @@ protected void renderNewRowAlias() { } @Override - protected void renderUpdatevalue(ColumnValueBinding columnValueBinding) { + protected void renderUpdateValue(ColumnValueBinding columnValueBinding) { renderAlias(); appendSql( "." ); appendSql( columnValueBinding.getColumnReference().getColumnExpression() ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/PostgreSQLSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/PostgreSQLSqlAstTranslator.java index da605ae88d61..f251e7aaded6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/PostgreSQLSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/PostgreSQLSqlAstTranslator.java @@ -32,6 +32,7 @@ import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl; import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert; +import org.hibernate.sql.model.internal.OptionalTableInsert; import org.hibernate.sql.model.internal.TableInsertStandard; import org.hibernate.type.SqlTypes; @@ -59,6 +60,39 @@ protected String getArrayContainsFunction() { return super.getArrayContainsFunction(); } + @Override + public void visitStandardTableInsert(TableInsertStandard tableInsert) { + getCurrentClauseStack().push( Clause.INSERT ); + try { + renderInsertInto( tableInsert ); + if ( tableInsert instanceof OptionalTableInsert optionalTableInsert ) { + appendSql( " on conflict " ); + final String constraintName = optionalTableInsert.getConstraintName(); + if ( constraintName != null ) { + appendSql( " on constraint " ); + appendSql( constraintName ); + } + else { + char separator = '('; + for ( String constraintColumnName : optionalTableInsert.getConstraintColumnNames() ) { + appendSql( separator ); + appendSql( constraintColumnName ); + separator = ','; + } + appendSql( ')' ); + } + appendSql( " do nothing" ); + } + + if ( tableInsert.getNumberOfReturningColumns() > 0 ) { + visitReturningColumns( tableInsert::getReturningColumns ); + } + } + finally { + getCurrentClauseStack().pop(); + } + } + @Override protected void renderInsertIntoNoColumns(TableInsertStandard tableInsert) { renderIntoIntoAndTable( tableInsert ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/SpannerSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/SpannerSqlAstTranslator.java index 2a46627baa6e..cd25879460b8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/SpannerSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/SpannerSqlAstTranslator.java @@ -13,14 +13,17 @@ import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.Literal; import org.hibernate.sql.ast.tree.expression.SqlTuple; import org.hibernate.sql.ast.tree.expression.Summarization; import org.hibernate.sql.ast.tree.from.DerivedTableReference; +import org.hibernate.sql.ast.tree.from.NamedTableReference; import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.ast.tree.update.UpdateStatement; import org.hibernate.sql.exec.spi.JdbcOperation; /** @@ -112,4 +115,39 @@ protected void renderDerivedTableReference(DerivedTableReference tableReference) } } + @Override + protected void visitDeleteStatementOnly(DeleteStatement statement) { + // Spanner requires a WHERE in delete clause so we add "where true" if there is none + if ( !hasWhere( statement.getRestriction() ) ) { + renderDeleteClause( statement ); + appendSql( " where true" ); + visitReturningColumns( statement.getReturningColumns() ); + } + else { + super.visitDeleteStatementOnly( statement ); + } + } + + @Override + protected void visitUpdateStatementOnly(UpdateStatement statement) { + // Spanner requires a WHERE in update clause so we add "where true" if there is none + if ( !hasWhere( statement.getRestriction() ) ) { + renderUpdateClause( statement ); + renderSetClause( statement.getAssignments() ); + appendSql( " where true" ); + visitReturningColumns( statement.getReturningColumns() ); + } + else { + super.visitUpdateStatementOnly( statement ); + } + } + + @Override + protected void renderDmlTargetTableExpression(NamedTableReference tableReference) { + super.renderDmlTargetTableExpression( tableReference ); + if ( getClauseStack().getCurrent() != Clause.INSERT ) { + renderTableReferenceIdentificationVariable( tableReference ); + } + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/SqlAstTranslatorWithOnDuplicateKeyUpdate.java b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/SqlAstTranslatorWithOnDuplicateKeyUpdate.java index 73fab31d26ca..2ad1b55b760f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/SqlAstTranslatorWithOnDuplicateKeyUpdate.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/SqlAstTranslatorWithOnDuplicateKeyUpdate.java @@ -5,8 +5,9 @@ package org.hibernate.dialect.sql.ast; -import org.hibernate.dialect.MySQLDeleteOrUpsertOperation; +import org.hibernate.StaleStateException; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.jdbc.Expectation; import org.hibernate.persister.entity.mutation.EntityTableMapping; import org.hibernate.sql.ast.spi.SqlAstTranslatorWithUpsert; import org.hibernate.sql.ast.tree.Statement; @@ -14,8 +15,10 @@ import org.hibernate.sql.model.MutationOperation; import org.hibernate.sql.model.ast.ColumnValueBinding; import org.hibernate.sql.model.internal.OptionalTableUpdate; +import org.hibernate.sql.model.jdbc.DeleteOrUpsertOperation; import org.hibernate.sql.model.jdbc.UpsertOperation; +import java.sql.PreparedStatement; import java.util.List; /** @@ -37,10 +40,11 @@ public MutationOperation createMergeOperation(OptionalTableUpdate optionalTableU optionalTableUpdate.getMutatingTable().getTableMapping(), optionalTableUpdate.getMutationTarget(), getSql(), + new MySQLRowCountExpectation(), getParameterBinders() ); - return new MySQLDeleteOrUpsertOperation( + return new DeleteOrUpsertOperation( optionalTableUpdate.getMutationTarget(), (EntityTableMapping) optionalTableUpdate.getMutatingTable().getTableMapping(), upsertOperation, @@ -48,6 +52,19 @@ public MutationOperation createMergeOperation(OptionalTableUpdate optionalTableU ); } + private static class MySQLRowCountExpectation implements Expectation { + @Override + public final void verifyOutcome(int rowCount, PreparedStatement statement, int batchPosition, String sql) { + if ( rowCount > 2 ) { + throw new StaleStateException( + "Unexpected row count" + + " (the expected row count for an ON DUPLICATE KEY UPDATE statement should be either 0, 1 or 2 )" + + " [" + sql + "]" + ); + } + } + } + @Override protected void renderUpsertStatement(OptionalTableUpdate optionalTableUpdate) { renderInsertInto( optionalTableUpdate ); @@ -56,34 +73,39 @@ protected void renderUpsertStatement(OptionalTableUpdate optionalTableUpdate) { } protected void renderInsertInto(OptionalTableUpdate optionalTableUpdate) { - appendSql( "insert into " ); + if ( optionalTableUpdate.getValueBindings().isEmpty() ) { + appendSql( "insert ignore into " ); + } + else { + appendSql( "insert into " ); + } appendSql( optionalTableUpdate.getMutatingTable().getTableName() ); - appendSql( " (" ); + appendSql( " " ); final List keyBindings = optionalTableUpdate.getKeyBindings(); + char separator = '('; for ( ColumnValueBinding keyBinding : keyBindings ) { + appendSql( separator ); appendSql( keyBinding.getColumnReference().getColumnExpression() ); - appendSql( ',' ); + separator = ','; } optionalTableUpdate.forEachValueBinding( (columnPosition, columnValueBinding) -> { - appendSql( columnValueBinding.getColumnReference().getColumnExpression() ); - if ( columnPosition != optionalTableUpdate.getValueBindings().size() - 1 ) { - appendSql( ',' ); - } + appendSql( ',' ); + appendSql( columnValueBinding.getColumnReference().getColumnExpression() ); } ); - appendSql( ") values (" ); + appendSql( ") values " ); + separator = '('; for ( ColumnValueBinding keyBinding : keyBindings ) { + appendSql( separator ); keyBinding.getValueExpression().accept( this ); - appendSql( ',' ); + separator = ','; } optionalTableUpdate.forEachValueBinding( (columnPosition, columnValueBinding) -> { - if ( columnPosition > 0 ) { - appendSql( ',' ); - } + appendSql( ',' ); columnValueBinding.getValueExpression().accept( this ); } ); appendSql(") "); @@ -94,19 +116,21 @@ protected void renderNewRowAlias() { } protected void renderOnDuplicateKeyUpdate(OptionalTableUpdate optionalTableUpdate) { - appendSql( "on duplicate key update " ); - optionalTableUpdate.forEachValueBinding( (columnPosition, columnValueBinding) -> { - final String columnName = columnValueBinding.getColumnReference().getColumnExpression(); - if ( columnPosition > 0 ) { - appendSql( ',' ); - } - appendSql( columnName ); - append( " = " ); - renderUpdatevalue( columnValueBinding ); - } ); + if ( !optionalTableUpdate.getValueBindings().isEmpty() ) { + appendSql( "on duplicate key update " ); + optionalTableUpdate.forEachValueBinding( (columnPosition, columnValueBinding) -> { + final String columnName = columnValueBinding.getColumnReference().getColumnExpression(); + if ( columnPosition > 0 ) { + appendSql( ',' ); + } + appendSql( columnName ); + append( " = " ); + renderUpdateValue( columnValueBinding ); + } ); + } } - protected void renderUpdatevalue(ColumnValueBinding columnValueBinding) { + protected void renderUpdateValue(ColumnValueBinding columnValueBinding) { } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/type/AbstractPostgreSQLStructJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/type/AbstractPostgreSQLStructJdbcType.java index 5a28ee9ef16d..d8ad146d8606 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/type/AbstractPostgreSQLStructJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/type/AbstractPostgreSQLStructJdbcType.java @@ -4,27 +4,9 @@ */ package org.hibernate.dialect.type; -import java.lang.reflect.Array; -import java.sql.CallableStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; -import java.time.temporal.ChronoField; -import java.time.temporal.TemporalAccessor; -import java.util.ArrayList; -import java.util.TimeZone; - import org.hibernate.internal.util.CharSequenceHelper; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; -import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.ValuedModelPart; import org.hibernate.sql.ast.spi.SqlAppender; @@ -43,13 +25,31 @@ import org.hibernate.type.descriptor.jdbc.StructuredJdbcType; import org.hibernate.type.spi.TypeConfiguration; -import static org.hibernate.type.descriptor.jdbc.StructHelper.getSubPart; -import static org.hibernate.type.descriptor.jdbc.StructHelper.instantiate; +import java.sql.CallableStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; +import java.util.ArrayList; +import java.util.TimeZone; + +import static java.lang.reflect.Array.get; +import static java.lang.reflect.Array.getLength; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsDate; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsLocalTime; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTime; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMicros; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMillis; +import static org.hibernate.type.descriptor.jdbc.StructHelper.getSubPart; +import static org.hibernate.type.descriptor.jdbc.StructHelper.instantiate; /** * Implementation for serializing/deserializing an embeddable aggregate to/from the PostgreSQL component format. @@ -125,17 +125,13 @@ public EmbeddableMappingType getEmbeddableMappingType() { } @Override - public JavaType getJdbcRecommendedJavaTypeMapping( + public JavaType getRecommendedJavaType( Integer precision, Integer scale, TypeConfiguration typeConfiguration) { - if ( embeddableMappingType == null ) { - return typeConfiguration.getJavaTypeRegistry().getDescriptor( Object[].class ); - } - else { - //noinspection unchecked - return (JavaType) embeddableMappingType.getMappedJavaType(); - } + return embeddableMappingType == null + ? typeConfiguration.getJavaTypeRegistry().resolveDescriptor( Object[].class ) + : embeddableMappingType.getMappedJavaType(); } @Override @@ -189,15 +185,13 @@ protected X fromString(String string, JavaType javaType, WrapperOptions o } assert end == string.length(); if ( returnEmbeddable ) { - final StructAttributeValues attributeValues = getAttributeValues( embeddableMappingType, orderMapping, array, options ); - //noinspection unchecked - return (X) instantiate( embeddableMappingType, attributeValues ); + final var attributeValues = getAttributeValues( embeddableMappingType, orderMapping, array, options ); + return javaType.cast( instantiate( embeddableMappingType, attributeValues ) ); } else if ( inverseOrderMapping != null ) { StructHelper.orderJdbcValues( embeddableMappingType, inverseOrderMapping, array.clone(), array ); } - //noinspection unchecked - return (X) array; + return javaType.cast( array ); } private int deserializeStruct( @@ -336,7 +330,7 @@ private int deserializeStruct( continue; } assert isDoubleQuote( string, i, 1 << quotes ); - final JdbcMapping jdbcMapping = getJdbcValueSelectable( column ).getJdbcMapping(); + final var jdbcMapping = getJdbcValueSelectable( column ).getJdbcMapping(); switch ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() ) { case SqlTypes.DATE: values[column] = fromRawObject( @@ -450,7 +444,7 @@ private int deserializeStruct( i += expectedQuotes - 1; if ( string.charAt( i + 1 ) == '(' ) { // This could be a nested struct - final JdbcMapping jdbcMapping = getJdbcValueSelectable( column ).getJdbcMapping(); + final var jdbcMapping = getJdbcValueSelectable( column ).getJdbcMapping(); if ( jdbcMapping.getJdbcType() instanceof AbstractPostgreSQLStructJdbcType structJdbcType ) { final Object[] subValues = new Object[structJdbcType.embeddableMappingType.getJdbcValueCount()]; final int subEnd = structJdbcType.deserializeStruct( @@ -500,7 +494,7 @@ private int deserializeStruct( } else if ( string.charAt( i + 1 ) == '{' ) { // This could be a quoted array - final JdbcMapping jdbcMapping = getJdbcValueSelectable( column ).getJdbcMapping(); + final var jdbcMapping = getJdbcValueSelectable( column ).getJdbcMapping(); if ( jdbcMapping instanceof BasicPluralType pluralType ) { final ArrayList arrayList = new ArrayList<>(); //noinspection unchecked @@ -543,7 +537,7 @@ else if ( string.charAt( i + 1 ) == '{' ) { values[column] = null; } else { - final JdbcMapping jdbcMapping = getJdbcValueSelectable( column ).getJdbcMapping(); + final var jdbcMapping = getJdbcValueSelectable( column ).getJdbcMapping(); if ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() == SqlTypes.BOOLEAN ) { values[column] = fromRawObject( jdbcMapping, @@ -579,7 +573,7 @@ else if ( jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass().isEnum() values[column] = null; } else { - final JdbcMapping jdbcMapping = getJdbcValueSelectable( column ).getJdbcMapping(); + final var jdbcMapping = getJdbcValueSelectable( column ).getJdbcMapping(); if ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() == SqlTypes.BOOLEAN ) { values[column] = fromRawObject( jdbcMapping, @@ -610,7 +604,7 @@ else if ( jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass().isEnum() break; case '{': if ( !inQuote ) { - final BasicPluralType pluralType = (BasicPluralType) getJdbcValueSelectable( column ).getJdbcMapping(); + final var pluralType = (BasicPluralType) getJdbcValueSelectable( column ).getJdbcMapping(); final ArrayList arrayList = new ArrayList<>(); //noinspection unchecked i = deserializeArray( @@ -645,14 +639,10 @@ private boolean isBinary(int column) { } private static boolean isBinary(JdbcMapping jdbcMapping) { - switch ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() ) { - case SqlTypes.BINARY: - case SqlTypes.VARBINARY: - case SqlTypes.LONGVARBINARY: - case SqlTypes.LONG32VARBINARY: - return true; - } - return false; + return switch ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() ) { + case SqlTypes.BINARY, SqlTypes.VARBINARY, SqlTypes.LONGVARBINARY, SqlTypes.LONG32VARBINARY -> true; + default -> false; + }; } private int deserializeArray( @@ -803,7 +793,7 @@ private int deserializeArray( ); break; default: - if ( escapingSb == null || escapingSb.length() == 0 ) { + if ( escapingSb == null || escapingSb.isEmpty() ) { values.add( fromString( elementType, @@ -1027,19 +1017,6 @@ private static boolean isDoubleQuote(String string, int start, int escapes) { return false; } - private Object fromString( - int selectableIndex, - String string, - int start, - int end) { - return fromString( - getJdbcValueSelectable( selectableIndex ).getJdbcMapping(), - string, - start, - end - ); - } - private static Object fromString(JdbcMapping jdbcMapping, CharSequence charSequence, int start, int end) { return jdbcMapping.getJdbcJavaType().fromEncodedString( charSequence, @@ -1064,22 +1041,19 @@ private Object parseTime(CharSequence subSequence) { } private Object parseTimestamp(CharSequence subSequence, JavaType jdbcJavaType) { - final TemporalAccessor temporalAccessor = LOCAL_DATE_TIME.parse( subSequence ); - final LocalDateTime localDateTime = LocalDateTime.from( temporalAccessor ); - final Timestamp timestamp = Timestamp.valueOf( localDateTime ); + final var temporalAccessor = LOCAL_DATE_TIME.parse( subSequence ); + final var localDateTime = LocalDateTime.from( temporalAccessor ); + final var timestamp = Timestamp.valueOf( localDateTime ); timestamp.setNanos( temporalAccessor.get( ChronoField.NANO_OF_SECOND ) ); return timestamp; } private Object parseTimestampWithTimeZone(CharSequence subSequence, JavaType jdbcJavaType) { - final TemporalAccessor temporalAccessor = LOCAL_DATE_TIME.parse( subSequence ); + final var temporalAccessor = LOCAL_DATE_TIME.parse( subSequence ); if ( temporalAccessor.isSupported( ChronoField.OFFSET_SECONDS ) ) { - if ( jdbcJavaType.getJavaTypeClass() == Instant.class ) { - return Instant.from( temporalAccessor ); - } - else { - return OffsetDateTime.from( temporalAccessor ); - } + return jdbcJavaType.getJavaTypeClass() == Instant.class + ? Instant.from( temporalAccessor ) + : OffsetDateTime.from( temporalAccessor ); } return LocalDateTime.from( temporalAccessor ); } @@ -1129,7 +1103,7 @@ protected String toString(X value, JavaType javaType, WrapperOptions opti if ( value == null ) { return null; } - final StringBuilder sb = new StringBuilder(); + final var sb = new StringBuilder(); serializeStructTo( new PostgreSQLAppender( sb ), value, options ); return sb.toString(); } @@ -1164,10 +1138,10 @@ private void serializeJdbcValuesTo( if ( jdbcValue == null ) { continue; } - final SelectableMapping selectableMapping = orderMapping == null ? + final var selectableMapping = orderMapping == null ? embeddableMappingType.getJdbcValueSelectable( i ) : embeddableMappingType.getJdbcValueSelectable( orderMapping[i] ); - final JdbcMapping jdbcMapping = selectableMapping.getJdbcMapping(); + final var jdbcMapping = selectableMapping.getJdbcMapping(); if ( jdbcMapping.getJdbcType() instanceof AbstractPostgreSQLStructJdbcType structJdbcType ) { appender.quoteStart(); structJdbcType.serializeJdbcValuesTo( @@ -1267,7 +1241,7 @@ private void serializeConvertedBasicTo( break; case SqlTypes.ARRAY: if ( subValue != null ) { - final int length = Array.getLength( subValue ); + final int length = getLength( subValue ); if ( length == 0 ) { appender.append( "{}" ); } @@ -1276,7 +1250,7 @@ private void serializeConvertedBasicTo( final BasicType elementType = ((BasicPluralType) jdbcMapping).getElementType(); appender.quoteStart(); appender.append( '{' ); - Object arrayElement = Array.get( subValue, 0 ); + Object arrayElement = get( subValue, 0 ); if ( arrayElement == null ) { appender.appendNull(); } @@ -1284,7 +1258,7 @@ private void serializeConvertedBasicTo( serializeConvertedBasicTo( appender, options, elementType, arrayElement ); } for ( int i = 1; i < length; i++ ) { - arrayElement = Array.get( subValue, i ); + arrayElement = get( subValue, i ); appender.append( ',' ); if ( arrayElement == null ) { appender.appendNull(); @@ -1301,7 +1275,7 @@ private void serializeConvertedBasicTo( break; case SqlTypes.STRUCT: if ( subValue != null ) { - final AbstractPostgreSQLStructJdbcType structJdbcType = (AbstractPostgreSQLStructJdbcType) jdbcMapping.getJdbcType(); + final var structJdbcType = (AbstractPostgreSQLStructJdbcType) jdbcMapping.getJdbcType(); appender.quoteStart(); structJdbcType.serializeJdbcValuesTo( appender, options, (Object[]) subValue, '(' ); appender.append( ')' ); @@ -1354,7 +1328,7 @@ private int injectAttributeValue( Object[] rawJdbcValues, int jdbcIndex, WrapperOptions options) throws SQLException { - final MappingType mappedType = modelPart.getMappedType(); + final var mappedType = modelPart.getMappedType(); final int jdbcValueCount; final Object rawJdbcValue = rawJdbcValues[jdbcIndex]; if ( mappedType instanceof EmbeddableMappingType embeddableMappingType ) { @@ -1378,7 +1352,7 @@ private int injectAttributeValue( else { assert modelPart.getJdbcTypeCount() == 1; jdbcValueCount = 1; - final JdbcMapping jdbcMapping = modelPart.getSingleJdbcMapping(); + final var jdbcMapping = modelPart.getSingleJdbcMapping(); final Object jdbcValue = jdbcMapping.getJdbcJavaType().wrap( rawJdbcValue, options diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/type/DB2StructJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/type/DB2StructJdbcType.java index 7966480a4de6..e0672eab7bad 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/type/DB2StructJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/type/DB2StructJdbcType.java @@ -93,17 +93,13 @@ public String getStructTypeName() { } @Override - public JavaType getJdbcRecommendedJavaTypeMapping( + public JavaType getRecommendedJavaType( Integer precision, Integer scale, TypeConfiguration typeConfiguration) { - if ( embeddableMappingType == null ) { - return typeConfiguration.getJavaTypeRegistry().getDescriptor( Object[].class ); - } - else { - //noinspection unchecked - return (JavaType) embeddableMappingType.getMappedJavaType(); - } + return embeddableMappingType == null + ? typeConfiguration.getJavaTypeRegistry().resolveDescriptor( Object[].class ) + : embeddableMappingType.getMappedJavaType(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleArrayJdbcType.java index 7712cd7e08d2..9aeb04eaffff 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleArrayJdbcType.java @@ -14,7 +14,6 @@ import org.hibernate.HibernateException; import org.hibernate.boot.model.relational.Database; -import org.hibernate.boot.model.relational.Namespace; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.Size; import org.hibernate.mapping.UserDefinedArrayType; @@ -71,60 +70,68 @@ public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaType javaTypeD @Override public ValueBinder getBinder(final JavaType javaTypeDescriptor) { - @SuppressWarnings("unchecked") - final BasicPluralJavaType pluralJavaType = (BasicPluralJavaType) javaTypeDescriptor; - final ValueBinder elementBinder = getElementJdbcType().getBinder( pluralJavaType.getElementJavaType() ); - return new BasicBinder<>( javaTypeDescriptor, this ) { - private String typeName(WrapperOptions options) { - final BasicPluralJavaType javaType = (BasicPluralJavaType) getJavaType(); - final ArrayJdbcType jdbcType = (ArrayJdbcType) getJdbcType(); + return new Binder<>( javaTypeDescriptor, + (BasicPluralJavaType) javaTypeDescriptor ); + } + + private class Binder extends BasicBinder { + private final BasicPluralJavaType pluralJavaType; + + private Binder(JavaType javaType, BasicPluralJavaType pluralJavaType) { + super( javaType, OracleArrayJdbcType.this ); + this.pluralJavaType = pluralJavaType; + } + + private String typeName(WrapperOptions options) { + final var javaType = (BasicPluralJavaType) getJavaType(); + final var jdbcType = (ArrayJdbcType) getJdbcType(); return upperTypeName == null ? getTypeName( options, javaType, jdbcType ).toUpperCase( Locale.ROOT ) : upperTypeName; - } - @Override - protected void doBindNull(PreparedStatement st, int index, WrapperOptions options) throws SQLException { - st.setNull( index, ARRAY, typeName( options ) ); - } + } - @Override - protected void doBindNull(CallableStatement st, String name, WrapperOptions options) throws SQLException { - st.setNull( name, ARRAY, typeName( options ) ); - } + @Override + protected void doBindNull(PreparedStatement st, int index, WrapperOptions options) throws SQLException { + st.setNull( index, ARRAY, typeName( options ) ); + } - @Override - protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { - st.setArray( index, getBindValue( value, options ) ); - } + @Override + protected void doBindNull(CallableStatement st, String name, WrapperOptions options) throws SQLException { + st.setNull( name, ARRAY, typeName( options ) ); + } - @Override - protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) - throws SQLException { - final java.sql.Array arr = getBindValue( value, options ); - try { - st.setObject( name, arr, ARRAY ); - } - catch (SQLException ex) { - throw new HibernateException( "JDBC driver does not support named parameters for setArray. Use positional.", ex ); - } + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + st.setArray( index, getBindValue( value, options ) ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + final java.sql.Array arr = getBindValue( value, options ); + try { + st.setObject( name, arr, ARRAY ); + } + catch (SQLException ex) { + throw new HibernateException( "JDBC driver does not support named parameters for setArray. Use positional.", ex ); } + } - @Override - public java.sql.Array getBindValue(X value, WrapperOptions options) throws SQLException { - final OracleArrayJdbcType oracleArrayJdbcType = (OracleArrayJdbcType) getJdbcType(); - final Object[] objects = oracleArrayJdbcType.getArray( this, elementBinder, value, options ); - final String arrayTypeName = typeName( options ); - final OracleConnection oracleConnection = - options.getSession().getJdbcCoordinator().getLogicalConnection().getPhysicalConnection() - .unwrap( OracleConnection.class ); - try { - return oracleConnection.createOracleArray( arrayTypeName, objects ); - } - catch (Exception e) { - throw new HibernateException( "Couldn't create a java.sql.Array", e ); - } + @Override + public java.sql.Array getBindValue(X value, WrapperOptions options) throws SQLException { + final var elementBinder = getElementJdbcType().getBinder( pluralJavaType.getElementJavaType() ); + final var objects = convertToArray( this, elementBinder, pluralJavaType, value, options ); + final String arrayTypeName = typeName( options ); + final var oracleConnection = + options.getSession().getJdbcCoordinator().getLogicalConnection().getPhysicalConnection() + .unwrap( OracleConnection.class ); + try { + return oracleConnection.createOracleArray( arrayTypeName, objects ); } - }; + catch (Exception e) { + throw new HibernateException( "Couldn't create a java.sql.Array", e ); + } + } } @Override @@ -153,17 +160,18 @@ static String getTypeName(WrapperOptions options, BasicPluralJavaType contain } static String getTypeName(BasicType elementType, Dialect dialect) { - final BasicValueConverter converter = elementType.getValueConverter(); - if ( converter != null ) { - return dialect.getArrayTypeName( - converterClassName( converter ), - null, // not needed by OracleDialect.getArrayTypeName() - null // not needed by OracleDialect.getArrayTypeName() - ); - } - else { - return getTypeName( elementType.getJavaTypeDescriptor(), elementType.getJdbcType(), dialect ); - } + final var converter = elementType.getValueConverter(); + return converter != null + ? dialect.getArrayTypeName( + converterClassName( converter ), + null, // not needed by OracleDialect.getArrayTypeName() + null // not needed by OracleDialect.getArrayTypeName() + ) + : getTypeName( + elementType.getJavaTypeDescriptor(), + elementType.getJdbcType(), + dialect + ); } private static String converterClassName(BasicValueConverter converter) { @@ -181,7 +189,7 @@ static String getTypeName(JavaType elementJavaType, JdbcType elementJdbcType, } private static String arrayClassName(JavaType elementJavaType, JdbcType elementJdbcType, Dialect dialect) { - final Class javaClass = elementJavaType.getJavaTypeClass(); + final var javaClass = elementJavaType.getJavaTypeClass(); if ( javaClass.isArray() ) { return dialect.getArrayTypeName( javaClass.getComponentType().getSimpleName(), @@ -193,20 +201,23 @@ else if ( elementJdbcType instanceof StructuredJdbcType structJdbcType ) { return structJdbcType.getStructTypeName(); } else { - final Class preferredJavaTypeClass = elementJdbcType.getPreferredJavaTypeClass( null ); + final var preferredJavaTypeClass = + elementJdbcType.getPreferredJavaTypeClass( null ); if ( preferredJavaTypeClass == javaClass) { return javaClass.getSimpleName(); } else { if ( preferredJavaTypeClass.isArray() ) { - return javaClass.getSimpleName() + dialect.getArrayTypeName( - preferredJavaTypeClass.getComponentType().getSimpleName(), - null, - null - ); + return javaClass.getSimpleName() + + dialect.getArrayTypeName( + preferredJavaTypeClass.getComponentType().getSimpleName(), + null, + null + ); } else { - return javaClass.getSimpleName() + preferredJavaTypeClass.getSimpleName(); + return javaClass.getSimpleName() + + preferredJavaTypeClass.getSimpleName(); } } } @@ -219,18 +230,18 @@ public void addAuxiliaryDatabaseObjects( Size columnSize, Database database, JdbcTypeIndicators context) { - final JdbcType elementJdbcType = getElementJdbcType(); - if ( elementJdbcType instanceof StructuredJdbcType ) { - // OracleAggregateSupport will take care of contributing the auxiliary database object - return; + final var elementJdbcType = getElementJdbcType(); + if ( !(elementJdbcType instanceof StructuredJdbcType) ) { + final var dialect = database.getDialect(); + final var pluralJavaType = (BasicPluralJavaType) javaType; + final var elementJavaType = pluralJavaType.getElementJavaType(); + final String elementTypeName = + elementType( elementJavaType, elementJdbcType, columnSize, context.getTypeConfiguration(), + dialect ); + final String arrayTypeName = arrayTypeName( elementJavaType, elementJdbcType, dialect ); + createUserDefinedArrayType( arrayTypeName, elementTypeName, columnSize, elementJdbcType, database ); } - final Dialect dialect = database.getDialect(); - final BasicPluralJavaType pluralJavaType = (BasicPluralJavaType) javaType; - final JavaType elementJavaType = pluralJavaType.getElementJavaType(); - final String elementTypeName = - elementType( elementJavaType, elementJdbcType, columnSize, context.getTypeConfiguration(), dialect ); - final String arrayTypeName = arrayTypeName( elementJavaType, elementJdbcType, dialect ); - createUserDefinedArrayType( arrayTypeName, elementTypeName, columnSize, elementJdbcType, database ); + // else OracleAggregateSupport will take care of contributing the auxiliary database object } private String arrayTypeName(JavaType elementJavaType, JdbcType elementJdbcType, Dialect dialect) { @@ -241,8 +252,8 @@ private String arrayTypeName(JavaType elementJavaType, JdbcType elementJdbcTy private void createUserDefinedArrayType( String arrayTypeName, String elementTypeName, Size columnSize, JdbcType elementJdbcType, Database database) { - final Namespace defaultNamespace = database.getDefaultNamespace(); - final UserDefinedArrayType userDefinedArrayType = + final var defaultNamespace = database.getDefaultNamespace(); + final var userDefinedArrayType = defaultNamespace.createUserDefinedArrayType( toIdentifier( arrayTypeName ), name -> new UserDefinedArrayType( "orm", defaultNamespace, name ) @@ -275,7 +286,7 @@ public void registerOutParameter(CallableStatement callableStatement, int index) @Override public String getExtraCreateTableInfo(JavaType javaType, String columnName, String tableName, Database database) { - final BasicPluralJavaType pluralJavaType = (BasicPluralJavaType) javaType; + final var pluralJavaType = (BasicPluralJavaType) javaType; return getElementJdbcType() .getExtraCreateTableInfo( pluralJavaType.getElementJavaType(), columnName, tableName, database ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleArrayJdbcTypeConstructor.java b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleArrayJdbcTypeConstructor.java index 9b7b358806c6..990b5a929825 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleArrayJdbcTypeConstructor.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleArrayJdbcTypeConstructor.java @@ -54,7 +54,7 @@ public JdbcType resolveType( precision = columnTypeInformation.getColumnSize(); scale = columnTypeInformation.getDecimalDigits(); } - typeName = OracleArrayJdbcType.getTypeName( elementType.getJdbcRecommendedJavaTypeMapping( + typeName = OracleArrayJdbcType.getTypeName( elementType.getRecommendedJavaType( precision, scale, typeConfiguration diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleNestedTableJdbcTypeConstructor.java b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleNestedTableJdbcTypeConstructor.java index 6bbae75b3305..a93e216dca83 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleNestedTableJdbcTypeConstructor.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleNestedTableJdbcTypeConstructor.java @@ -48,7 +48,7 @@ public JdbcType resolveType( precision = columnTypeInformation.getColumnSize(); scale = columnTypeInformation.getDecimalDigits(); } - typeName = OracleArrayJdbcType.getTypeName( elementType.getJdbcRecommendedJavaTypeMapping( + typeName = OracleArrayJdbcType.getTypeName( elementType.getRecommendedJavaType( precision, scale, typeConfiguration diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleUserDefinedTypeExporter.java b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleUserDefinedTypeExporter.java index 4808017f80ce..bb4b4ef2fd4b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleUserDefinedTypeExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleUserDefinedTypeExporter.java @@ -30,6 +30,7 @@ /** * @author Christian Beikov + * @author Yoobin Yoon */ public class OracleUserDefinedTypeExporter extends StandardUserDefinedTypeExporter { @@ -220,6 +221,53 @@ public String[] getSqlCreateStrings( "end loop; " + "return res; " + "end;", + "create or replace function " + arrayTypeName + "_reverse(arr in " + arrayTypeName + + ") return " + arrayTypeName + " deterministic is " + + "res " + arrayTypeName + ":=" + arrayTypeName + "(); begin " + + "if arr is null then return null; end if; " + + "for i in reverse 1 .. arr.count loop " + + "res.extend; " + + "res(res.count) := arr(i); " + + "end loop; " + + "return res; " + + "end;", + "create or replace function " + arrayTypeName + "_sort(" + + "arr in " + arrayTypeName + "," + + "p_descending in number default 0," + + "p_nulls_first in number default null" + + ") return " + arrayTypeName + " deterministic is " + + "v_result " + arrayTypeName + "; " + + "v_nulls_first number; " + + "begin " + + "if arr is null then return null; end if; " + + "if p_nulls_first is null then " + + "v_nulls_first := p_descending; " + + "else " + + "v_nulls_first := p_nulls_first; " + + "end if; " + + "if p_descending = 0 then " + + "if v_nulls_first = 0 then " + + "select cast(multiset(select column_value from table(arr) " + + "order by column_value asc nulls last) as " + arrayTypeName + ") " + + "into v_result from dual; " + + "else " + + "select cast(multiset(select column_value from table(arr) " + + "order by column_value asc nulls first) as " + arrayTypeName + ") " + + "into v_result from dual; " + + "end if; " + + "else " + + "if v_nulls_first = 0 then " + + "select cast(multiset(select column_value from table(arr) " + + "order by column_value desc nulls last) as " + arrayTypeName + ") " + + "into v_result from dual; " + + "else " + + "select cast(multiset(select column_value from table(arr) " + + "order by column_value desc nulls first) as " + arrayTypeName + ") " + + "into v_result from dual; " + + "end if; " + + "end if; " + + "return v_result; " + + "end;", "create or replace function " + arrayTypeName + "_fill(elem in " + getRawTypeName( elementType ) + ", elems number) return " + arrayTypeName + " deterministic is " + "res " + arrayTypeName + ":=" + arrayTypeName + "(); begin " + @@ -303,6 +351,8 @@ public String[] getSqlDropStrings(UserDefinedArrayType userDefinedType, Metadata buildDropFunctionSqlString(arrayTypeName + "_slice"), buildDropFunctionSqlString(arrayTypeName + "_replace"), buildDropFunctionSqlString(arrayTypeName + "_trim"), + buildDropFunctionSqlString(arrayTypeName + "_reverse"), + buildDropFunctionSqlString(arrayTypeName + "_sort"), buildDropFunctionSqlString(arrayTypeName + "_fill"), buildDropFunctionSqlString(arrayTypeName + "_positions"), buildDropFunctionSqlString(arrayTypeName + "_to_string"), diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/type/PostgreSQLArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/type/PostgreSQLArrayJdbcType.java index 4f02999d4d0b..25fd637c1891 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/type/PostgreSQLArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/type/PostgreSQLArrayJdbcType.java @@ -10,7 +10,6 @@ import java.sql.Types; import org.hibernate.HibernateException; -import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BasicPluralJavaType; @@ -31,62 +30,66 @@ public PostgreSQLArrayJdbcType(JdbcType elementJdbcType) { @Override public ValueBinder getBinder(final JavaType javaTypeDescriptor) { - @SuppressWarnings("unchecked") - final BasicPluralJavaType pluralJavaType = (BasicPluralJavaType) javaTypeDescriptor; - final ValueBinder elementBinder = getElementJdbcType().getBinder( pluralJavaType.getElementJavaType() ); - return new BasicBinder<>( javaTypeDescriptor, this ) { + return new Binder<>( javaTypeDescriptor, + (BasicPluralJavaType) javaTypeDescriptor ); + } - @Override - protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { - st.setArray( index, getArray( value, options ) ); - } + private class Binder extends BasicBinder { + private final BasicPluralJavaType pluralJavaType; - @Override - protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) - throws SQLException { - final java.sql.Array arr = getArray( value, options ); - try { - st.setObject( name, arr, java.sql.Types.ARRAY ); - } - catch (SQLException ex) { - throw new HibernateException( "JDBC driver does not support named parameters for setArray. Use positional.", ex ); - } - } + private Binder(JavaType javaType, BasicPluralJavaType pluralJavaType) { + super( javaType, PostgreSQLArrayJdbcType.this ); + this.pluralJavaType = pluralJavaType; + } + + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + st.setArray( index, getArray( value, options ) ); + } - @Override - public Object getBindValue(X value, WrapperOptions options) throws SQLException { - return ( (PostgreSQLArrayJdbcType) getJdbcType() ).getArray( this, elementBinder, value, options ); + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + final java.sql.Array arr = getArray( value, options ); + try { + st.setObject( name, arr, java.sql.Types.ARRAY ); } + catch (SQLException ex) { + throw new HibernateException( "JDBC driver does not support named parameters for setArray. Use positional.", ex ); + } + } + + @Override + public Object[] getBindValue(X value, WrapperOptions options) throws SQLException { + final var elementBinder = getElementJdbcType().getBinder( pluralJavaType.getElementJavaType() ); + return convertToArray( this, elementBinder, pluralJavaType, value, options ); + } - private java.sql.Array getArray(X value, WrapperOptions options) throws SQLException { - final PostgreSQLArrayJdbcType arrayJdbcType = (PostgreSQLArrayJdbcType) getJdbcType(); - final Object[] objects; + private java.sql.Array getArray(X value, WrapperOptions options) throws SQLException { + final var session = options.getSession(); + return session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection() + .createArrayOf( getElementTypeName( getJavaType(), session ), + elements( value, options, PostgreSQLArrayJdbcType.this ) ); + } - final JdbcType elementJdbcType = arrayJdbcType.getElementJdbcType(); - if ( elementJdbcType instanceof AggregateJdbcType aggregateJdbcType ) { - // The PostgreSQL JDBC driver does not support arrays of structs, which contain byte[] - final Object[] domainObjects = getJavaType().unwrap( - value, - Object[].class, - options - ); - objects = new Object[domainObjects.length]; - for ( int i = 0; i < domainObjects.length; i++ ) { - if ( domainObjects[i] != null ) { - objects[i] = aggregateJdbcType.createJdbcValue( domainObjects[i], options ); - } + private Object[] elements(X value, WrapperOptions options, PostgreSQLArrayJdbcType arrayJdbcType) + throws SQLException { + final var elementJdbcType = arrayJdbcType.getElementJdbcType(); + if ( elementJdbcType instanceof AggregateJdbcType aggregateJdbcType ) { + // The PostgreSQL JDBC driver does not support arrays of structs, which contain byte[] + final var domainObjects = getJavaType().unwrap( value, Object[].class, options ); + final var objects = new Object[domainObjects.length]; + for ( int i = 0; i < domainObjects.length; i++ ) { + if ( domainObjects[i] != null ) { + objects[i] = aggregateJdbcType.createJdbcValue( domainObjects[i], options ); } } - else { - objects = arrayJdbcType.getArray( this, elementBinder, value, options ); - } - - final SharedSessionContractImplementor session = options.getSession(); - final String typeName = arrayJdbcType.getElementTypeName( getJavaType(), session ); - return session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection() - .createArrayOf( typeName, objects ); + return objects; + } + else { + return getBindValue( value, options ); } - }; + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/engine/config/internal/ConfigurationServiceImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/config/internal/ConfigurationServiceImpl.java index 005ee176c523..ed0ac5d4fecc 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/config/internal/ConfigurationServiceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/config/internal/ConfigurationServiceImpl.java @@ -66,42 +66,49 @@ public void injectServices(ServiceRegistryImplementor serviceRegistry) { return target !=null ? target : defaultValue; } - @SuppressWarnings("unchecked") public @Nullable T cast(Class expected, @Nullable Object candidate){ - if (candidate == null) { + if ( candidate == null ) { return null; } + else if ( expected.isInstance( candidate ) ) { + return expected.cast( candidate ); + } + else { + final var target = getTargetClass( expected, candidate ); + return target == null ? null : instantiate( expected, target ); - if ( expected.isInstance( candidate ) ) { - return (T) candidate; } + } + + private static @Nullable T instantiate(Class expected, Class target) { + try { + return target.getDeclaredConstructor().newInstance(); + } + catch (Exception e) { + //TODO: should this really be a debug-level + // log instead of a proper error? + CORE_LOGGER.debugf( "Unable to instantiate %s class %s", + expected.getName(), target.getName() ); + return null; + } + } - Class target; - if (candidate instanceof Class) { - target = (Class) candidate; + private @Nullable Class getTargetClass(Class expected, Object candidate) { + if ( candidate instanceof Class candidateClass ) { + return candidateClass.asSubclass( expected ); } else { try { - target = serviceRegistry.requireService( ClassLoaderService.class ) + return serviceRegistry.requireService( ClassLoaderService.class ) .classForName( candidate.toString() ); } - catch ( ClassLoadingException e ) { + catch (ClassLoadingException e) { + //TODO: should this really be a debug-level + // log instead of a proper error? CORE_LOGGER.debugf( "Unable to locate %s implementation class %s", expected.getName(), candidate.toString() ); - target = null; + return null; } } - if ( target != null ) { - try { - return target.newInstance(); - } - catch ( Exception e ) { - CORE_LOGGER.debugf( "Unable to instantiate %s class %s", - expected.getName(), target.getName() ); - } - } - return null; } - - } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/creation/internal/SharedStatelessSessionBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/creation/internal/SharedStatelessSessionBuilderImpl.java index 585579dbf1a8..9af5a6c25f93 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/creation/internal/SharedStatelessSessionBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/creation/internal/SharedStatelessSessionBuilderImpl.java @@ -92,7 +92,7 @@ public SharedStatelessSessionBuilder interceptor() { @Override public SharedStatelessSessionBuilder statementInspector() { - this.statementInspector = original.getJdbcSessionContext().getStatementInspector(); + statementInspector = original.getJdbcSessionContext().getStatementInspector(); return this; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractTransactionCompletionProcessQueue.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractTransactionCompletionProcessQueue.java index a5b706b9c107..ac785e78ceb9 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractTransactionCompletionProcessQueue.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractTransactionCompletionProcessQueue.java @@ -18,8 +18,8 @@ */ abstract class AbstractTransactionCompletionProcessQueue { SharedSessionContractImplementor session; - // Concurrency handling required when transaction completion process is dynamically registered - // inside event listener (HHH-7478). + // Concurrency handling required when the transaction completion process + // is dynamically registered inside an event listener (HHH-7478). ConcurrentLinkedQueue<@NonNull T> processes = new ConcurrentLinkedQueue<>(); AbstractTransactionCompletionProcessQueue(SharedSessionContractImplementor session) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/AfterTransactionCompletionProcessQueue.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/AfterTransactionCompletionProcessQueue.java index ddcd55f70d84..8a9a3f57024c 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/AfterTransactionCompletionProcessQueue.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/AfterTransactionCompletionProcessQueue.java @@ -5,9 +5,8 @@ package org.hibernate.engine.internal; import org.hibernate.HibernateException; -import org.hibernate.action.internal.BulkOperationCleanupAction; +import org.hibernate.action.internal.BulkOperationCleanupAction.BulkOperationCleanUpAfterTransactionCompletionProcess; import org.hibernate.cache.CacheException; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.TransactionCompletionCallbacks.AfterCompletionCallback; @@ -15,6 +14,7 @@ import java.util.Set; import static org.hibernate.internal.CoreMessageLogger.CORE_LOGGER; +import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_STRING_ARRAY; /** * Encapsulates behavior needed for after transaction processing @@ -40,57 +40,54 @@ boolean hasActions() { void afterTransactionCompletion(boolean success) { AfterCompletionCallback process; while ( (process = processes.poll()) != null ) { - try { - process.doAfterTransactionCompletion( success, session ); - } - catch (CacheException ce) { - CORE_LOGGER.unableToReleaseCacheLock( ce ); - // continue loop - } - catch (Exception e) { - throw new HibernateException( - "Unable to perform afterTransactionCompletion callback: " + e.getMessage(), e ); - } + callAfterCompletion( success, process ); } + invalidateCaches(); + } - final SessionFactoryImplementor factory = session.getFactory(); - if ( factory.getSessionFactoryOptions().isQueryCacheEnabled() ) { - factory.getCache().getTimestampsCache() - .invalidate( querySpacesToInvalidate.toArray( new String[0] ), session ); + void executePendingBulkOperationCleanUpActions() { + if ( performBulkOperationCallbacks() ) { + invalidateCaches(); } - querySpacesToInvalidate.clear(); } - void executePendingBulkOperationCleanUpActions() { - AfterCompletionCallback process; + private boolean performBulkOperationCallbacks() { boolean hasPendingBulkOperationCleanUpActions = false; - while ( ( process = processes.poll() ) != null ) { - if ( process instanceof BulkOperationCleanupAction.BulkOperationCleanUpAfterTransactionCompletionProcess ) { - try { - hasPendingBulkOperationCleanUpActions = true; - process.doAfterTransactionCompletion( true, session ); - } - catch (CacheException ce) { - CORE_LOGGER.unableToReleaseCacheLock( ce ); - // continue loop - } - catch (Exception e) { - throw new HibernateException( - "Unable to perform afterTransactionCompletion callback: " + e.getMessage(), - e - ); + var iterator = processes.iterator(); + while ( iterator.hasNext() ) { + var process = iterator.next(); + if ( process instanceof BulkOperationCleanUpAfterTransactionCompletionProcess ) { + hasPendingBulkOperationCleanUpActions = true; + if ( callAfterCompletion( true, process ) ) { + iterator.remove(); } } } + return hasPendingBulkOperationCleanUpActions; + } - if ( hasPendingBulkOperationCleanUpActions ) { - if ( session.getFactory().getSessionFactoryOptions().isQueryCacheEnabled() ) { - session.getFactory().getCache().getTimestampsCache().invalidate( - querySpacesToInvalidate.toArray( new String[0] ), - session - ); - } - querySpacesToInvalidate.clear(); + private boolean callAfterCompletion(boolean success, AfterCompletionCallback process) { + try { + process.doAfterTransactionCompletion( success, session ); + return true; + } + catch (CacheException ce) { + CORE_LOGGER.unableToReleaseCacheLock( ce ); + // continue loop + return false; + } + catch (Exception e) { + throw new HibernateException( + "Unable to perform afterTransactionCompletion callback: " + e.getMessage(), e ); } } + + private void invalidateCaches() { + final var factory = session.getFactory(); + if ( factory.getSessionFactoryOptions().isQueryCacheEnabled() ) { + factory.getCache().getTimestampsCache(). + invalidate( querySpacesToInvalidate.toArray( EMPTY_STRING_ARRAY ), session ); + } + querySpacesToInvalidate.clear(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java index d490df522cc4..cac112458f8e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java @@ -322,8 +322,9 @@ private static void cascadeLogicalOneToOneOrphanRemoval( if ( child == null || loadedValue != null && child != loadedValue ) { EntityEntry valueEntry = persistenceContext.getEntry( loadedValue ); if ( valueEntry == null && isHibernateProxy( loadedValue ) ) { - // un-proxy and re-associate for cascade operation + // unproxy and reassociate for cascade operation // useful for @OneToOne defined as FetchType.LAZY + //TODO: what should really happen here??? loadedValue = persistenceContext.unproxyAndReassociate( loadedValue ); valueEntry = persistenceContext.getEntry( loadedValue ); // HHH-11965 diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java index 535836d0a35e..fcf4decfeff3 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java @@ -161,14 +161,13 @@ private static boolean isReferenceCachingEnabled(EntityPersister persister) { private ManagedEntity getAssociatedManagedEntity(Object entity) { if ( isManagedEntity( entity ) ) { final var managedEntity = asManagedEntity( entity ); - if ( managedEntity.$$_hibernate_getEntityEntry() == null ) { - // it is not associated - return null; - } final var entityEntry = (EntityEntryImpl) managedEntity.$$_hibernate_getEntityEntry(); - + if ( entityEntry == null ) { + // it is not associated + return null; + } if ( entityEntry.getPersister().isMutable() ) { return entityEntry.getPersistenceContext() == persistenceContext ? managedEntity // it is associated @@ -449,12 +448,14 @@ public void serialize(ObjectOutputStream oos) throws IOException { var managedEntity = head; while ( managedEntity != null ) { // so we know whether or not to build a ManagedEntityImpl on deserialize - oos.writeBoolean( managedEntity == managedEntity.$$_hibernate_getEntityInstance() ); - oos.writeObject( managedEntity.$$_hibernate_getEntityInstance() ); + final var instance = managedEntity.$$_hibernate_getEntityInstance(); + oos.writeBoolean( managedEntity == instance ); + oos.writeObject( instance ); // we need to know which implementation of EntityEntry is being serialized - oos.writeInt( managedEntity.$$_hibernate_getEntityEntry().getClass().getName().length() ); - oos.writeChars( managedEntity.$$_hibernate_getEntityEntry().getClass().getName() ); - managedEntity.$$_hibernate_getEntityEntry().serialize( oos ); + final var entry = managedEntity.$$_hibernate_getEntityEntry(); + oos.writeInt( entry.getClass().getName().length() ); + oos.writeChars( entry.getClass().getName() ); + entry.serialize( oos ); managedEntity = managedEntity.$$_hibernate_getNextManagedEntity(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryExtraStateHolder.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryExtraStateHolder.java index 6a53f3f79b0d..95ba26e31fff 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryExtraStateHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryExtraStateHolder.java @@ -36,13 +36,13 @@ public void addExtraState(EntityEntryExtraState extraState) { } } - @Override @SuppressWarnings("unchecked") + @Override public T getExtraState(Class extraStateType) { if ( next == null ) { return null; } - if ( extraStateType.isAssignableFrom( next.getClass() ) ) { - return (T) next; + if ( extraStateType.isInstance( next ) ) { + return extraStateType.cast( next ); } else { return next.getExtraState( extraStateType ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryImpl.java index f32adf748e9b..37fce72bf597 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryImpl.java @@ -548,13 +548,13 @@ public void addExtraState(EntityEntryExtraState extraState) { } } - @Override @SuppressWarnings("unchecked") + @Override public T getExtraState(Class extraStateType) { if ( next == null ) { return null; } - else if ( extraStateType.isAssignableFrom( next.getClass() ) ) { - return (T) next; + else if ( extraStateType.isInstance( next ) ) { + return extraStateType.cast( next ); } else { return next.getExtraState( extraStateType ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdResolutionsImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdResolutionsImpl.java index 16be10ec9430..3aee40e5e061 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdResolutionsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdResolutionsImpl.java @@ -31,7 +31,7 @@ import static org.hibernate.engine.internal.CacheHelper.fromSharedCache; import static org.hibernate.engine.internal.NaturalIdLogging.NATURAL_ID_LOGGER; -class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializable { +public class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializable { private final StatefulPersistenceContext persistenceContext; private final ConcurrentHashMap resolutionsByEntity = new ConcurrentHashMap<>(); @@ -54,6 +54,14 @@ private SharedSessionContractImplementor session() { return persistenceContext.getSession(); } + public EntityResolutions getEntityResolutions(EntityMappingType entityMappingType) { + return resolutionsByEntity.get( entityMappingType ); + } + + public EntityResolutions getEntityResolutions(Class entityType) { + return getEntityResolutions( session().getFactory().getMappingMetamodel().getEntityDescriptor( entityType ) ); + } + @Override public boolean cacheResolution(Object id, Object naturalId, EntityMappingType entityDescriptor) { validateNaturalId( entityDescriptor, naturalId ); @@ -709,7 +717,7 @@ public void clear() { /** * Represents the entity-specific cross-reference cache. */ - private static class EntityResolutions implements Serializable { + public static class EntityResolutions implements Serializable { private final PersistenceContext persistenceContext; private final EntityMappingType entityDescriptor; @@ -732,6 +740,25 @@ public EntityPersister getPersister() { return getEntityDescriptor().getEntityPersister(); } + /** + * Used for testing. + */ + public Resolution getResolutionByPk(Object pk) { + return pkToNaturalIdMap.get( pk ); + } + + /** + * Used for testing. + */ + public Object getIdResolutionByNaturalId(Object naturalId) { + for ( var entry : pkToNaturalIdMap.entrySet() ) { + if ( entry.getValue().getNaturalIdValue().equals( naturalId ) ) { + return entry.getKey(); + } + } + return null; + } + public boolean sameAsCached(Object pk, Object naturalIdValues) { if ( pk == null ) { return false; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ProxyUtil.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ProxyUtil.java new file mode 100644 index 000000000000..8704e10a3c87 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ProxyUtil.java @@ -0,0 +1,107 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.engine.internal; + +import org.hibernate.DetachedObjectException; +import org.hibernate.PersistentObjectException; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; +import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; +import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; + +/** + * @author Gavin King + * @since 7.2 + */ +public class ProxyUtil { + + /** + * Get the entity instance underlying the given proxy, throwing + * an exception if the proxy is uninitialized. If the given + * object is not a proxy, simply return the argument. + */ + public static Object assertInitialized(Object maybeProxy) { + final var lazyInitializer = extractLazyInitializer( maybeProxy ); + if ( lazyInitializer != null ) { + if ( lazyInitializer.isUninitialized() ) { + throw new PersistentObjectException( "Object was an uninitialized proxy for " + + lazyInitializer.getEntityName() ); + } + //unwrap the object and return + return lazyInitializer.getImplementation(); + } + else { + return maybeProxy; + } + } + + /** + * Get the entity instance underlying the given proxy, forcing + * initialization if the proxy is uninitialized. If the given + * object is not a proxy, simply return the argument. + * @throws DetachedObjectException if the given proxy does not + * belong to the given session + */ + public static Object forceInitialize(Object maybeProxy, SharedSessionContractImplementor session) { + final var lazyInitializer = extractLazyInitializer( maybeProxy ); + if ( lazyInitializer != null ) { + if ( lazyInitializer.getSession() != session ) { + throw new DetachedObjectException( "Given proxy does not belong to this persistence context" ); + } + //initialize + unwrap the object and return it + return lazyInitializer.getImplementation(); + } + else if ( isPersistentAttributeInterceptable( maybeProxy ) ) { + final var interceptor = + asPersistentAttributeInterceptable( maybeProxy ) + .$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor lazinessInterceptor ) { + if ( lazinessInterceptor.getLinkedSession() != session ) { + throw new DetachedObjectException( "Given proxy does not belong to this persistence context" ); + } + lazinessInterceptor.forceInitialize( maybeProxy, null ); + } + return maybeProxy; + } + else { + return maybeProxy; + } + } + + /** + * Determine of the given proxy is uninitialized. If the given + * object is not a proxy, simply return false. + * @throws DetachedObjectException if the given proxy does not + * belong to the given session + */ + public static boolean isUninitialized(Object value, SharedSessionContractImplementor session) { + // could be a proxy + final var lazyInitializer = extractLazyInitializer( value ); + if ( lazyInitializer != null ) { + if ( lazyInitializer.getSession() != session ) { + throw new DetachedObjectException( "Given proxy does not belong to this persistence context" ); + } + return lazyInitializer.isUninitialized(); + } + // or an uninitialized enhanced entity ("bytecode proxy") + else if ( isPersistentAttributeInterceptable( value ) ) { + final var interceptor = + (BytecodeLazyAttributeInterceptor) + asPersistentAttributeInterceptable( value ) + .$$_hibernate_getInterceptor(); + if ( interceptor != null && interceptor.getLinkedSession() != session ) { + throw new DetachedObjectException( "Given proxy does not belong to this persistence context" ); + } + return interceptor instanceof EnhancementAsProxyLazinessInterceptor enhancementInterceptor + && !enhancementInterceptor.isInitialized(); + } + else { + return false; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index 1470d7afd928..6a61e8b8e368 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -28,7 +28,6 @@ import org.hibernate.LockMode; import org.hibernate.MappingException; import org.hibernate.NonUniqueObjectException; -import org.hibernate.PersistentObjectException; import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.collection.spi.PersistentCollection; @@ -89,20 +88,15 @@ class StatefulPersistenceContext implements PersistenceContext { private static final int INIT_COLL_SIZE = 8; - /* - Eagerly Initialized Fields - the following fields are used in all circumstances, and are not worth (or not suited) to being converted into lazy - */ + // Eagerly initialized fields. The following fields are used in every circumstance + // and are not worth (or not suited) to being converted to lazy initialization. + private final SharedSessionContractImplementor session; private EntityEntryContext entityEntryContext; - /* - Everything else below should be carefully initialized only on first need; - this optimisation is very effective as null checks are free, while allocation costs - are very often the dominating cost of an application using ORM. - This is not general advice, but it's worth the added maintenance burden in this case - as this is a very central component of our library. - */ + // Everything else below should be carefully initialized only on first need. + // This optimization is very effective as null checks are free, while allocation + // costs are very often the dominating cost of an application using ORM. // Loaded entity instances, by EntityKey private HashMap entitiesByKey; @@ -113,8 +107,7 @@ the following fields are used in all circumstances, and are not worth (or not su // Loaded entity instances, by EntityUniqueKey private HashMap entitiesByUniqueKey; - - // Snapshots of current database state for entities + // Snapshots of the current database state for entities // that have *not* been loaded private HashMap entitySnapshotsByKey; @@ -136,7 +129,7 @@ the following fields are used in all circumstances, and are not worth (or not su // Set of EntityKeys of deleted unloaded proxies private HashSet deletedUnloadedEntityKeys; - // properties that we have tried to load, and not found in the database + // properties that we have tried to load and not found in the database private HashSet nullAssociations; // A list of collection wrappers that were instantiating during result set @@ -209,14 +202,6 @@ public boolean hasLoadContext() { return loadContexts != null; } -// @Override -// public void addUnownedCollection(CollectionKey key, PersistentCollection collection) { -// if ( unownedCollections == null ) { -// unownedCollections = CollectionHelper.mapOfSize( INIT_COLL_SIZE ); -// } -// unownedCollections.put( key, collection ); -// } -// @Override public PersistentCollection useUnownedCollection(CollectionKey key) { return unownedCollections == null ? null : unownedCollections.remove( key ); @@ -691,26 +676,35 @@ public boolean containsProxy(Object entity) { @Override public boolean reassociateIfUninitializedProxy(Object value) throws MappingException { - if ( !Hibernate.isInitialized( value ) ) { - // could be a proxy - final var lazyInitializer = extractLazyInitializer( value ); - if ( lazyInitializer != null ) { + if ( Hibernate.isInitialized( value ) ) { + return false; + } + // could be a proxy + final var lazyInitializer = extractLazyInitializer( value ); + if ( lazyInitializer != null ) { + final boolean uninitialized = lazyInitializer.isUninitialized(); + if ( uninitialized ) { reassociateProxy( lazyInitializer, asHibernateProxy( value ) ); - return true; } - // or an uninitialized enhanced entity ("bytecode proxy") - if ( isPersistentAttributeInterceptable( value ) ) { - final var bytecodeProxy = asPersistentAttributeInterceptable( value ); - final var interceptor = - (BytecodeLazyAttributeInterceptor) - bytecodeProxy.$$_hibernate_getInterceptor(); - if ( interceptor != null ) { - interceptor.setSession( getSession() ); - } - return true; + return uninitialized; + } + // or an uninitialized enhanced entity ("bytecode proxy") + else if ( isPersistentAttributeInterceptable( value ) ) { + final var interceptor = + (BytecodeLazyAttributeInterceptor) + asPersistentAttributeInterceptable( value ) + .$$_hibernate_getInterceptor(); + final boolean uninitialized = + interceptor instanceof EnhancementAsProxyLazinessInterceptor enhancementInterceptor + && !enhancementInterceptor.isInitialized(); + if ( uninitialized ) { + interceptor.setSession( getSession() ); } + return uninitialized; + } + else { + return false; } - return false; } @Override @@ -750,22 +744,6 @@ private void reassociateProxy(LazyInitializer li, HibernateProxy proxy) { } } - @Override - public Object unproxy(Object maybeProxy) throws HibernateException { - final var lazyInitializer = extractLazyInitializer( maybeProxy ); - if ( lazyInitializer != null ) { - if ( lazyInitializer.isUninitialized() ) { - throw new PersistentObjectException( "object was an uninitialized proxy for " - + lazyInitializer.getEntityName() ); - } - //unwrap the object and return - return lazyInitializer.getImplementation(); - } - else { - return maybeProxy; - } - } - @Override public Object unproxyAndReassociate(final Object maybeProxy) throws HibernateException { final var lazyInitializer = extractLazyInitializer( maybeProxy ); @@ -935,7 +913,7 @@ else if ( ownerPersister.isInstance( key ) ) { } else { // b) try by EntityKey, which means we need to resolve owner-key -> collection-key - // IMPL NOTE : yes if we get here this impl is very non-performant, but PersistenceContext + // IMPL NOTE: yes if we get here this impl is very non-performant, but PersistenceContext // was never designed to handle this case; adding that capability for real means splitting // the notions of: // 1) collection key @@ -1228,14 +1206,6 @@ public Object removeProxy(EntityKey key) { return removeProxyByKey( key ); } -// @Override -// public HashSet getNullifiableEntityKeys() { -// if ( nullifiableEntityKeys == null ) { -// nullifiableEntityKeys = new HashSet<>(); -// } -// return nullifiableEntityKeys; -// } - /** * @deprecated this will be removed: it provides too wide access, making it hard to optimise the internals * for specific access needs. Consider using #iterateEntities instead. diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionProviderInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionProviderInitiator.java index e39abf7d9278..ef0112e9ada2 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionProviderInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionProviderInitiator.java @@ -116,9 +116,7 @@ private static Class connectionProviderClass(Class throw new ConnectionProviderConfigurationException( "Class '" + providerClass.getName() + "' does not implement 'ConnectionProvider'" ); } - @SuppressWarnings("unchecked") // Safe, we just checked - final var connectionProviderClass = (Class) providerClass; - return connectionProviderClass; + return providerClass.asSubclass( ConnectionProvider.class ); } private ConnectionProvider instantiateNamedConnectionProvider( diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DataSourceConnectionProvider.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DataSourceConnectionProvider.java index 5fb8e27e552e..3d1d60479286 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DataSourceConnectionProvider.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DataSourceConnectionProvider.java @@ -73,13 +73,12 @@ public boolean isUnwrappableAs(Class unwrapType) { } @Override - @SuppressWarnings("unchecked") public T unwrap(Class unwrapType) { if ( unwrapType.isAssignableFrom( DataSourceConnectionProvider.class ) ) { - return (T) this; + return unwrapType.cast( this ); } else if ( unwrapType.isAssignableFrom( DataSource.class) ) { - return (T) getDataSource(); + return unwrapType.cast( getDataSource() ); } else { throw new UnknownUnwrapTypeException( unwrapType ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProvider.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProvider.java index 3013dd5054d0..600294069c26 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProvider.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProvider.java @@ -322,10 +322,9 @@ public boolean isUnwrappableAs(Class unwrapType) { } @Override - @SuppressWarnings( {"unchecked"}) public T unwrap(Class unwrapType) { if ( unwrapType.isAssignableFrom( DriverManagerConnectionProvider.class ) ) { - return (T) this; + return unwrapType.cast( this ); } else { throw new UnknownUnwrapTypeException( unwrapType ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/UserSuppliedConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/UserSuppliedConnectionProviderImpl.java index f85a9144faac..35c79503eba4 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/UserSuppliedConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/UserSuppliedConnectionProviderImpl.java @@ -25,10 +25,9 @@ public boolean isUnwrappableAs(Class unwrapType) { } @Override - @SuppressWarnings( {"unchecked"}) public T unwrap(Class unwrapType) { if ( unwrapType.isAssignableFrom( UserSuppliedConnectionProviderImpl.class ) ) { - return (T) this; + return unwrapType.cast( this ); } else { throw new UnknownUnwrapTypeException( unwrapType ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractDataSourceBasedMultiTenantConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractDataSourceBasedMultiTenantConnectionProviderImpl.java index e14bafb1729b..0649321fd924 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractDataSourceBasedMultiTenantConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractDataSourceBasedMultiTenantConnectionProviderImpl.java @@ -52,13 +52,12 @@ public boolean isUnwrappableAs(Class unwrapType) { } @Override - @SuppressWarnings("unchecked") - public T unwrap(Class unwrapType) { + public X unwrap(Class unwrapType) { if ( unwrapType.isInstance( this ) ) { - return (T) this; + return unwrapType.cast( this ); } else if ( unwrapType.isAssignableFrom( DataSource.class ) ) { - return (T) selectAnyDataSource(); + return unwrapType.cast( selectAnyDataSource() ); } else { throw new UnknownUnwrapTypeException( unwrapType ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractMultiTenantConnectionProvider.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractMultiTenantConnectionProvider.java index 6117ea3e59d0..55e98ec0b8d1 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractMultiTenantConnectionProvider.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/AbstractMultiTenantConnectionProvider.java @@ -54,13 +54,12 @@ public boolean isUnwrappableAs(Class unwrapType) { } @Override - @SuppressWarnings("unchecked") - public T unwrap(Class unwrapType) { + public X unwrap(Class unwrapType) { if ( unwrapType.isInstance( this ) ) { - return (T) this; + return unwrapType.cast( this ); } else if ( unwrapType.isAssignableFrom( ConnectionProvider.class ) ) { - return (T) getAnyConnectionProvider(); + return unwrapType.cast( getAnyConnectionProvider() ); } else { throw new UnknownUnwrapTypeException( unwrapType ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/NormalizingIdentifierHelperImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/NormalizingIdentifierHelperImpl.java index f4c683fbfeb0..8ec3dcae03c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/NormalizingIdentifierHelperImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/NormalizingIdentifierHelperImpl.java @@ -89,6 +89,11 @@ public Identifier toIdentifier(String text, boolean quoted) { return normalizeQuoting( Identifier.toIdentifier( text, quoted ) ); } + @Override + public Identifier toIdentifier(String text, boolean quoted, boolean isExplicit) { + return normalizeQuoting( Identifier.toIdentifier( text, quoted, true, isExplicit ) ); + } + @Override public Identifier applyGlobalQuoting(String text) { return Identifier.toIdentifier( text, globallyQuoteIdentifiers && !globallyQuoteIdentifiersSkipColumnDefinitions, false ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelper.java index 23cbb6c231dc..d5d91969e22f 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelper.java @@ -51,6 +51,21 @@ public interface IdentifierHelper { */ Identifier toIdentifier(String text, boolean quoted); + /** + * Generate an Identifier instance from its simple name as obtained from mapping + * information. Additionally, this form takes a boolean indicating whether to + * explicitly quote the Identifier. + *

+ * Note that Identifiers returned from here may be implicitly quoted based on + * 'globally quoted identifiers' or based on reserved words. + * + * @param text The text form of a name as obtained from mapping information. + * @param quoted Is the identifier to be quoted explicitly. + * @param isExplicit Whether the name is explicitly set + * @return The identifier form of the name. + */ + Identifier toIdentifier(String text, boolean quoted, boolean isExplicit); + /** * Intended only for use in handling quoting requirements for {@code column-definition} * as defined by {@link jakarta.persistence.Column#columnDefinition()}, diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java index f6981b79b4f3..f3ad3d7c9149 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java @@ -65,11 +65,9 @@ public CollectionEntry(CollectionPersister persister, PersistentCollection co // new collections that get found + wrapped // during flush shouldn't be ignored ignore = false; - // a newly wrapped collection is NOT dirty // (or we get unnecessary version updates) collection.clearDirty(); - snapshot = persister.isMutable() ? collection.getSnapshot( persister ) : null; role = persister.getRole(); collection.setSnapshot( loadedKey, role, snapshot ); @@ -84,16 +82,11 @@ public CollectionEntry( final Object loadedKey, final boolean ignore ) { this.ignore = ignore; - //collection.clearDirty() - this.loadedKey = loadedKey; - this.loadedPersister = loadedPersister; this.role = loadedPersister == null ? null : loadedPersister.getRole(); - collection.setSnapshot( loadedKey, role, null ); - //postInitialize() will be called after initialization } @@ -104,12 +97,10 @@ public CollectionEntry(CollectionPersister loadedPersister, Object loadedKey) { // detached collection wrappers that get found + reattached // during flush shouldn't be ignored ignore = false; - //collection.clearDirty() - this.loadedKey = loadedKey; this.loadedPersister = loadedPersister; - this.role = ( loadedPersister == null ? null : loadedPersister.getRole() ); + this.role = loadedPersister == null ? null : loadedPersister.getRole(); } /** @@ -119,11 +110,11 @@ public CollectionEntry(PersistentCollection collection, SessionFactoryImpleme // detached collections that get found + reattached // during flush shouldn't be ignored ignore = false; - loadedKey = collection.getKey(); role = collection.getRole(); - loadedPersister = factory.getMappingMetamodel().getCollectionDescriptor( castNonNull( role ) ); - + loadedPersister = + factory.getMappingMetamodel() + .getCollectionDescriptor( castNonNull( role ) ); snapshot = collection.getStoredSnapshot(); } @@ -151,53 +142,52 @@ private CollectionEntry( * of the collection elements, if necessary */ private void dirty(PersistentCollection collection) { - - final var loadedPersister = getLoadedPersister(); - final boolean forceDirty = - collection.wasInitialized() - && !collection.isDirty() //optimization - && loadedPersister != null - && loadedPersister.isMutable() //optimization - && ( collection.isDirectlyAccessible() || loadedPersister.getElementType().isMutable() ) //optimization - && !collection.equalsSnapshot( loadedPersister ); - if ( forceDirty ) { + if ( forceDirty( collection ) ) { collection.dirty(); } + } + private boolean forceDirty(PersistentCollection collection) { + final var loadedPersister = this.loadedPersister; + return collection.wasInitialized() + && !collection.isDirty() //optimization + && loadedPersister != null + && loadedPersister.isMutable() //optimization + && ( collection.isDirectlyAccessible() || loadedPersister.getElementType().isMutable() ) //optimization + && !collection.equalsSnapshot( loadedPersister ); } public void preFlush(PersistentCollection collection) { - if ( loadedKey == null && collection.getKey() != null ) { + if ( loadedKey == null ) { loadedKey = collection.getKey(); } - final var loadedPersister = getLoadedPersister(); - final boolean nonMutableChange = - collection.isDirty() - && loadedPersister != null - && !loadedPersister.isMutable(); - if ( nonMutableChange ) { + final var loadedPersister = this.loadedPersister; + if ( collection.isDirty() + && loadedPersister != null + && !loadedPersister.isMutable() ) { throw new HibernateException( "Immutable collection was modified: " + - collectionInfoString( castNonNull( loadedPersister ).getRole(), getLoadedKey() ) ); + collectionInfoString( loadedPersister.getRole(), getLoadedKey() ) ); } dirty( collection ); - if ( CORE_LOGGER.isTraceEnabled() && collection.isDirty() && loadedPersister != null ) { + if ( CORE_LOGGER.isTraceEnabled() + && loadedPersister != null + && collection.isDirty() ) { CORE_LOGGER.collectionDirty( collectionInfoString( loadedPersister.getRole(), getLoadedKey() ) ); } - setReached( false ); - setProcessed( false ); - - setDoupdate( false ); - setDoremove( false ); - setDorecreate( false ); + reached = false; + processed = false; + doupdate = false; + doremove = false; + dorecreate = false; } public void postInitialize(PersistentCollection collection, SharedSessionContractImplementor session) { - final var loadedPersister = getLoadedPersister(); + final var loadedPersister = this.loadedPersister; snapshot = loadedPersister != null && loadedPersister.isMutable() ? collection.getSnapshot( loadedPersister ) @@ -214,13 +204,11 @@ public void postInitialize(PersistentCollection collection, SharedSessionCont * Called after a successful flush */ public void postFlush(PersistentCollection collection) { - if ( isIgnore() ) { - ignore = false; - } - else if ( !isProcessed() ) { + if ( !ignore && !processed ) { throw new HibernateException( "Collection '" + collection.getRole() + "' was not processed by flush" + " (this is likely due to unsafe use of the session, for example, current use in multiple threads, or updates during entity lifecycle callbacks)"); } + ignore = false; collection.setSnapshot( loadedKey, role, snapshot ); } @@ -228,18 +216,17 @@ else if ( !isProcessed() ) { * Called after execution of an action */ public void afterAction(PersistentCollection collection) { - loadedKey = getCurrentKey(); - setLoadedPersister( getCurrentPersister() ); - + loadedKey = currentKey; + loadedPersister = currentPersister; + role = currentPersister == null ? null : currentPersister.getRole(); if ( collection.wasInitialized() - && ( isDoremove() || isDorecreate() || isDoupdate() ) ) { + && ( doremove || dorecreate || doupdate ) ) { // update the snapshot snapshot = loadedPersister != null && loadedPersister.isMutable() ? collection.getSnapshot( castNonNull( loadedPersister ) ) : null; } - collection.postAction(); } @@ -266,7 +253,6 @@ public void afterAction(PersistentCollection collection) { */ public void resetStoredSnapshot(PersistentCollection collection, Serializable storedSnapshot) { CORE_LOGGER.resetStoredSnapshot( storedSnapshot, this ); - if ( !fromMerge ) { snapshot = storedSnapshot; collection.setSnapshot( loadedKey, role, snapshot ); @@ -274,11 +260,6 @@ public void resetStoredSnapshot(PersistentCollection collection, Serializable } } - private void setLoadedPersister(@Nullable CollectionPersister persister) { - loadedPersister = persister; - setRole( persister == null ? null : persister.getRole() ); - } - void afterDeserialize(@Nullable SessionFactoryImplementor factory) { loadedPersister = factory == null ? null : factory.getMappingMetamodel() @@ -286,7 +267,7 @@ void afterDeserialize(@Nullable SessionFactoryImplementor factory) { } public boolean wasDereferenced() { - return getLoadedKey() == null; + return loadedKey == null; } public boolean isReached() { @@ -370,13 +351,13 @@ public void setRole(@Nullable String role) { @Override public String toString() { - final StringBuilder result = + final var result = new StringBuilder( "CollectionEntry" ) .append( collectionInfoString( role, loadedKey ) ); - final CollectionPersister persister = currentPersister; - if ( persister != null ) { + final var currentPersister = this.currentPersister; + if ( currentPersister != null ) { result.append( "->" ) - .append( collectionInfoString( persister.getRole(), currentKey ) ); + .append( collectionInfoString( currentPersister.getRole(), currentKey ) ); } return result.toString(); } @@ -395,11 +376,9 @@ public boolean isSnapshotEmpty(PersistentCollection collection) { //TODO: does this really need to be here? // does the collection already have // it's own up-to-date snapshot? - final var loadedPersister = getLoadedPersister(); - final Serializable snapshot = getSnapshot(); return collection.wasInitialized() && ( loadedPersister == null || loadedPersister.isMutable() ) - && ( snapshot == null || collection.isSnapshotEmpty(snapshot) ); + && ( snapshot == null || collection.isSnapshotEmpty( snapshot ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java index 881dba6098a0..212d16f6679e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java @@ -16,6 +16,7 @@ import org.hibernate.Internal; import org.hibernate.LockMode; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.internal.ProxyUtil; import org.hibernate.internal.util.MarkerObject; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; @@ -293,8 +294,12 @@ EntityEntry addReferenceEntry( * Get the entity instance underlying the given proxy, throwing * an exception if the proxy is uninitialized. If the given object * is not a proxy, simply return the argument. + * @deprecated No longer used */ - Object unproxy(Object maybeProxy); + @Deprecated(since = "7.2", forRemoval = true) + default Object unproxy(Object maybeProxy) { + return ProxyUtil.assertInitialized( maybeProxy ); + } /** * Possibly unproxy the given reference and reassociate it with the current session. diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java index 84831c176e26..a83bf40786a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java @@ -23,6 +23,8 @@ import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EntityCopyObserverFactory; import org.hibernate.event.spi.EventEngine; +import org.hibernate.graph.GraphParser; +import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.event.service.spi.EventListenerGroups; import org.hibernate.metamodel.model.domain.JpaMetamodel; @@ -322,4 +324,18 @@ default JdbcSelectWithActionsBuilder getJdbcSelectWithActionsBuilder(){ return new JdbcSelectWithActions.Builder(); } + @Override + default RootGraph parseEntityGraph(Class rootEntityClass, CharSequence graphText) { + return GraphParser.parse( rootEntityClass, graphText.toString(), unwrap( SessionFactoryImplementor.class ) ); + } + + @Override @Incubating + default RootGraph parseEntityGraph(String rootEntityName, CharSequence graphText) { + return GraphParser.parse( rootEntityName, graphText.toString(), unwrap( SessionFactoryImplementor.class ) ); + } + + @Override @Incubating + default RootGraph parseEntityGraph(CharSequence graphText) { + return GraphParser.parse( graphText.toString(), unwrap( SessionFactoryImplementor.class ) ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java index 19fbf55a7a03..ca102676a444 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java @@ -886,17 +886,15 @@ public boolean isJoinedToTransaction() { } @Override - public T unwrap(Class cls) { - if ( cls.isAssignableFrom( Session.class ) ) { - //noinspection unchecked - return (T) this; - } - return this.lazySession.get().unwrap( cls ); + public T unwrap(Class type) { + return type.isAssignableFrom( Session.class ) + ? type.cast( this ) + : lazySession.get().unwrap( type ); } @Override public Object getDelegate() { - return this.lazySession.get().getDelegate(); + return lazySession.get().getDelegate(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java index 04da87409873..9e8a22ad96b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java @@ -292,8 +292,8 @@ protected Object performSaveOrReplicate( delayIdentityInserts ); - // postpone initializing id in case the insert has non-nullable transient dependencies - // that are not resolved until cascadeAfterSave() is executed + // postpone initializing id in case the insert has non-nullable transient + // dependencies that are not resolved until cascadeAfterSave() is executed cascadeAfterSave( source, persister, entity, context ); final Object finalId = handleGeneratedId( useIdentityColumn, id, insert ); @@ -423,7 +423,7 @@ protected boolean visitCollectionsBeforeSave( * @param persister The entity persister * @param source The originating session * - * @return True if the snapshot state changed such that + * @return true if the snapshot state changed such that * reinjection of the values into the entity is required. */ protected boolean substituteValuesIfNecessary( @@ -460,7 +460,7 @@ protected void cascadeBeforeSave( EntityPersister persister, Object entity, C context) { - // cascade-save to many-to-one BEFORE the parent is saved + // cascade save to many-to-one BEFORE the parent is saved final var persistenceContext = source.getPersistenceContextInternal(); persistenceContext.incrementCascadeLevel(); try { @@ -491,7 +491,7 @@ protected void cascadeAfterSave( EntityPersister persister, Object entity, C context) { - // cascade-save to collections AFTER the collection owner was saved + // cascade save to collections AFTER the collection owner was saved final var persistenceContext = source.getPersistenceContextInternal(); persistenceContext.incrementCascadeLevel(); try { diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java index ee51e8b9ef2f..ab80d10ec677 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java @@ -5,6 +5,7 @@ package org.hibernate.event.internal; import org.hibernate.CacheMode; +import org.hibernate.DetachedObjectException; import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.StaleObjectStateException; @@ -166,7 +167,7 @@ private void deleteUnmanagedInstance(DeleteEvent event, DeleteContext transientE private void deleteDetachedEntity( DeleteEvent event, DeleteContext transientEntities, Object entity, EntityPersister persister, EventSource source) { if ( source.getFactory().getSessionFactoryOptions().isJpaBootstrap() ) { - throw new IllegalArgumentException( "Given entity is not associated with the persistence context" ); + throw new DetachedObjectException( "Given entity is not associated with the persistence context" ); } final Object id = persister.getIdentifier( entity, source ); if ( id == null ) { @@ -182,18 +183,19 @@ private void deleteDetachedEntity( new OnUpdateVisitor( source, id, entity ).process( entity, persister ); - final var persistenceContext = source.getPersistenceContextInternal(); - final var entityEntry = persistenceContext.addEntity( - entity, - persister.isMutable() ? Status.MANAGED : Status.READ_ONLY, - persister.getValues( entity ), - key, - version, - LockMode.NONE, - true, - persister, - false - ); + final var entityEntry = + source.getPersistenceContextInternal() + .addEntity( + entity, + persister.isMutable() ? Status.MANAGED : Status.READ_ONLY, + persister.getValues( entity ), + key, + version, + LockMode.NONE, + true, + persister, + false + ); persister.afterReassociate( entity, source ); delete( event, transientEntities, source, entity, persister, id, version, entityEntry ); @@ -497,7 +499,7 @@ protected void cascadeBeforeDelete( final var persistenceContext = session.getPersistenceContextInternal(); persistenceContext.incrementCascadeLevel(); try { - // cascade-delete to collections BEFORE the collection owner is deleted + // cascade delete to collections BEFORE the collection owner is deleted Cascade.cascade( CascadingActions.REMOVE, CascadePoint.AFTER_INSERT_BEFORE_DELETE, @@ -524,7 +526,7 @@ protected void cascadeAfterDelete( final var persistenceContext = session.getPersistenceContextInternal(); persistenceContext.incrementCascadeLevel(); try { - // cascade-delete to many-to-one AFTER the parent was deleted + // cascade delete to many-to-one AFTER the parent was deleted Cascade.cascade( CascadingActions.REMOVE, CascadePoint.BEFORE_INSERT_AFTER_DELETE, diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultEvictEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultEvictEventListener.java index 515d64532f36..f0195dc11662 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultEvictEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultEvictEventListener.java @@ -13,7 +13,6 @@ import org.hibernate.event.spi.EvictEvent; import org.hibernate.event.spi.EvictEventListener; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.proxy.LazyInitializer; import static org.hibernate.event.internal.EventListenerLogging.EVENT_LISTENER_LOGGER; import static org.hibernate.pretty.MessageHelper.infoString; @@ -43,7 +42,7 @@ public void onEvict(EvictEvent event) throws HibernateException { if ( object == null ) { throw new NullPointerException( "null passed to Session.evict()" ); } - final LazyInitializer lazyInitializer = extractLazyInitializer( object ); + final var lazyInitializer = extractLazyInitializer( object ); if ( lazyInitializer != null ) { final Object id = lazyInitializer.getInternalIdentifier(); if ( id == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java index 50b4a23aaccb..205df311feba 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java @@ -120,6 +120,12 @@ public void onFlushEntity(FlushEntityEvent event) throws HibernateException { final var entry = event.getEntityEntry(); final var session = event.getSession(); + // short-circuit for immutable entities.... + if ( !entry.getPersister().isMutable() && !entry.getPersister().hasCollections() ) { + // nothing to do + return; + } + final boolean mightBeDirty = entry.requiresDirtyCheck( entity ); final Object[] values = getValues( entity, entry, mightBeDirty, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java index 12e95883e97a..01354e2f124a 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java @@ -114,7 +114,7 @@ private boolean handleIdType(EntityPersister persister, LoadEvent event, LoadTyp parentIdTargetIdMapping instanceof CompositeIdentifierMapping compositeMapping ? compositeMapping.getMappedIdEmbeddableTypeDescriptor() : parentIdTargetIdMapping.getMappedType(); - if ( parentIdType.getMappedJavaType().getJavaTypeClass().isInstance( event.getEntityId() ) ) { + if ( parentIdType.getMappedJavaType().isInstance( event.getEntityId() ) ) { // yep that's what we have... loadByDerivedIdentitySimplePkValue( event, diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLockEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLockEventListener.java index 3c8eec03629c..e37b4ff938f1 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLockEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLockEventListener.java @@ -4,27 +4,15 @@ */ package org.hibernate.event.internal; +import org.hibernate.DetachedObjectException; import org.hibernate.HibernateException; import org.hibernate.LockMode; -import org.hibernate.TransientObjectException; -import org.hibernate.engine.internal.Cascade; -import org.hibernate.engine.internal.CascadePoint; -import org.hibernate.engine.internal.ForeignKeys; -import org.hibernate.engine.spi.CascadingActions; -import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.Status; -import org.hibernate.event.spi.AbstractSessionEvent; import org.hibernate.event.spi.LockEvent; import org.hibernate.event.spi.LockEventListener; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.type.TypeHelper; - -import static org.hibernate.engine.internal.Versioning.getVersion; -import static org.hibernate.event.internal.EventListenerLogging.EVENT_LISTENER_LOGGER; +import static org.hibernate.engine.internal.ProxyUtil.forceInitialize; import static org.hibernate.loader.ast.internal.LoaderHelper.upgradeLock; -import static org.hibernate.pretty.MessageHelper.infoString; /** * Defines the default lock event listeners used by hibernate to lock entities @@ -42,105 +30,26 @@ public class DefaultLockEventListener implements LockEventListener { @Override public void onLock(LockEvent event) throws HibernateException { - if ( event.getObject() == null ) { - throw new NullPointerException( "attempted to lock null" ); + final Object instance = event.getObject(); + if ( instance == null ) { + throw new NullPointerException( "Attempted to lock null" ); } final var lockMode = event.getLockMode(); if ( lockMode == LockMode.WRITE || lockMode == LockMode.UPGRADE_SKIPLOCKED ) { - throw new HibernateException( "Invalid lock mode for lock()" ); + throw new IllegalArgumentException( "Invalid lock mode '" + lockMode + "' passed to 'lock()'" ); } final var source = event.getSession(); - final var persistenceContext = source.getPersistenceContextInternal(); - final Object entity = persistenceContext.unproxyAndReassociate( event.getObject() ); - //TODO: if object was an uninitialized proxy, this is inefficient, + final Object entity = forceInitialize( instance, source ); + //TODO: if instance was an uninitialized proxy, this is inefficient, // resulting in two SQL selects - var entry = persistenceContext.getEntry( entity ); - if ( entry == null ) { - final var persister = source.getEntityPersister( event.getEntityName(), entity ); - final Object id = persister.getIdentifier( entity, source ); - if ( !ForeignKeys.isNotTransient( event.getEntityName(), entity, Boolean.FALSE, source ) ) { - throw new TransientObjectException( "Cannot lock unsaved transient instance of entity '" - + persister.getEntityName() + "'" ); - } - entry = reassociate( event, entity, id, persister ); - cascadeOnLock( event, persister, entity ); - } - - upgradeLock( entity, entry, event.getLockOptions(), event.getSession() ); - } - - private void cascadeOnLock(LockEvent event, EntityPersister persister, Object entity) { - final var source = event.getSession(); - final var persistenceContext = source.getPersistenceContextInternal(); - persistenceContext.incrementCascadeLevel(); - try { - Cascade.cascade( - CascadingActions.LOCK, - CascadePoint.AFTER_LOCK, - source, - persister, - entity, - event.getLockOptions() - ); - } - finally { - persistenceContext.decrementCascadeLevel(); - } - } - - /** - * Associates a given entity (either transient or associated with another session) - * to the given session. - * - * @param event The event triggering the re-association - * @param object The entity to be associated - * @param id The id of the entity. - * @param persister The entity's persister instance. - * - * @return An EntityEntry representing the entity within this session. - */ - protected final EntityEntry reassociate(AbstractSessionEvent event, Object object, Object id, EntityPersister persister) { - - if ( EVENT_LISTENER_LOGGER.isTraceEnabled() ) { - EVENT_LISTENER_LOGGER.reassociatingTransientInstance( - infoString( persister, id, event.getFactory() ) ); + final var entry = source.getPersistenceContextInternal().getEntry( entity ); + if ( entry == null && instance == entity ) { + throw new DetachedObjectException( "Given entity is not associated with the persistence context" ); } - final var source = event.getSession(); - final var key = source.generateEntityKey( id, persister ); - final var persistenceContext = source.getPersistenceContext(); - - persistenceContext.checkUniqueness( key, object ); - - //get a snapshot - final Object[] values = persister.getValues( object ); - TypeHelper.deepCopy( - values, - persister.getPropertyTypes(), - persister.getPropertyUpdateability(), - values, - source - ); - - final var newEntry = persistenceContext.addEntity( - object, - persister.isMutable() ? Status.MANAGED : Status.READ_ONLY, - values, - key, - getVersion( values, persister ), - LockMode.NONE, - true, - persister, - false - ); - - new OnLockVisitor( source, id, object ).process( object, persister ); - - persister.afterReassociate( object, source ); - - return newEntry; + upgradeLock( entity, entry, event.getLockOptions(), source ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java index 0d43b7dbf5b1..191f2f5f91ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java @@ -40,6 +40,7 @@ import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isSelfDirtinessTracker; +import static org.hibernate.engine.internal.ProxyUtil.assertInitialized; import static org.hibernate.event.internal.EntityState.getEntityState; import static org.hibernate.event.internal.EventListenerLogging.EVENT_LISTENER_LOGGER; import static org.hibernate.event.internal.EventUtil.getLoggableName; @@ -362,11 +363,12 @@ private static class CollectionVisitor extends WrapVisitor { @Override protected Object processCollection(Object collection, CollectionType collectionType) { if ( collection instanceof PersistentCollection persistentCollection ) { + final var session = getSession(); final var persister = - getSession().getFactory().getMappingMetamodel() + session.getFactory().getMappingMetamodel() .getCollectionDescriptor( collectionType.getRole() ); final var collectionEntry = - getSession().getPersistenceContextInternal() + session.getPersistenceContextInternal() .getCollectionEntry( persistentCollection ); if ( !persistentCollection.equalsSnapshot( persister ) ) { collectionEntry.resetStoredSnapshot( persistentCollection, persistentCollection.getSnapshot( persister ) ); @@ -374,10 +376,6 @@ protected Object processCollection(Object collection, CollectionType collectionT } return null; } - @Override - Object processEntity(Object value, EntityType entityType) { - return null; - } } private void saveTransientEntity( @@ -518,7 +516,7 @@ private static Object unproxyManagedForDetachedMerging( EntityPersister persister, EventSource source) { if ( isHibernateProxy( managed ) ) { - return source.getPersistenceContextInternal().unproxy( managed ); + return assertInitialized( managed ); } if ( isPersistentAttributeInterceptable( incoming ) diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java index 3f8d9ecb7d0e..e942e6842dff 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java @@ -18,8 +18,10 @@ import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; import org.hibernate.persister.entity.EntityPersister; +import static org.hibernate.engine.internal.ProxyUtil.assertInitialized; import static org.hibernate.event.internal.EntityState.getEntityState; import static org.hibernate.event.internal.EventListenerLogging.EVENT_LISTENER_LOGGER; +import static org.hibernate.event.internal.EventUtil.getLoggableName; import static org.hibernate.pretty.MessageHelper.infoString; import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; @@ -62,7 +64,7 @@ public void onPersist(PersistEvent event, PersistContext createCache) throws Hib if ( lazyInitializer != null ) { if ( lazyInitializer.isUninitialized() ) { if ( lazyInitializer.getSession() != event.getSession() ) { - throw new PersistentObjectException( "uninitialized proxy passed to persist()" ); + throw new PersistentObjectException( "Uninitialized proxy passed to persist()" ); } } else { @@ -81,7 +83,7 @@ private void persist(PersistEvent event, PersistContext createCache, Object enti switch ( getEntityState( entity, entityName, entityEntry, source, true ) ) { case DETACHED: throw new PersistentObjectException( "Detached entity passed to persist: " - + EventUtil.getLoggableName( event.getEntityName(), entity) ); + + getLoggableName( event.getEntityName(), entity) ); case PERSISTENT: entityIsPersistent( event, createCache ); break; @@ -95,17 +97,15 @@ private void persist(PersistEvent event, PersistContext createCache, Object enti entityIsDeleted( event, createCache ); break; default: - throw new ObjectDeletedException( - "Deleted entity passed to persist", - null, - EventUtil.getLoggableName( event.getEntityName(), entity ) - ); + throw new ObjectDeletedException( "Deleted entity passed to persist", null, + getLoggableName( entityName, entity ) ); } } private static String entityName(PersistEvent event, Object entity, EntityEntry entityEntry) { - if ( event.getEntityName() != null ) { - return event.getEntityName(); + final String explicitEntityName = event.getEntityName(); + if ( explicitEntityName != null ) { + return explicitEntityName; } else { // changes event.entityName by side effect! @@ -119,14 +119,13 @@ protected void entityIsPersistent(PersistEvent event, PersistContext createCache final var source = event.getSession(); final String entityName = event.getEntityName(); //TODO: check that entry.getIdentifier().equals(requestedId) - final Object entity = source.getPersistenceContextInternal().unproxy( event.getObject() ); + final Object entity = assertInitialized( event.getObject() ); + final var persister = source.getEntityPersister( entityName, entity ); if ( EVENT_LISTENER_LOGGER.isTraceEnabled() ) { - final var persister = source.getEntityPersister( entityName, entity ); EVENT_LISTENER_LOGGER.ignoringPersistentInstance( infoString( entityName, persister.getIdentifier( entity ) ) ); } if ( createCache.add( entity ) ) { - final var persister = source.getEntityPersister( entityName, entity ); justCascade( createCache, source, entity, persister ); } } @@ -139,21 +138,20 @@ private void justCascade(PersistContext createCache, EventSource source, Object protected void entityIsTransient(PersistEvent event, PersistContext createCache) { EVENT_LISTENER_LOGGER.persistingTransientInstance(); - final var source = event.getSession(); - final Object entity = source.getPersistenceContextInternal().unproxy( event.getObject() ); + final Object entity = assertInitialized( event.getObject() ); if ( createCache.add( entity ) ) { + final var source = event.getSession(); saveWithGeneratedId( entity, event.getEntityName(), createCache, source, false ); } } private void entityIsDeleted(PersistEvent event, PersistContext createCache) { final var source = event.getSession(); - final Object entity = source.getPersistenceContextInternal().unproxy( event.getObject() ); + final Object entity = assertInitialized( event.getObject() ); final var persister = source.getEntityPersister( event.getEntityName(), entity ); if ( EVENT_LISTENER_LOGGER.isTraceEnabled() ) { - final Object id = persister.getIdentifier( entity, source ); EVENT_LISTENER_LOGGER.unschedulingEntityDeletion( - infoString( persister, id, source.getFactory() ) ); + infoString( persister, persister.getIdentifier( entity, source ), source.getFactory() ) ); } if ( createCache.add( entity ) ) { justCascade( createCache, source, entity, persister ); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPostLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPostLoadEventListener.java index a929a2362b94..0b6516e45cc1 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPostLoadEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPostLoadEventListener.java @@ -51,16 +51,18 @@ public void onPostLoad(PostLoadEvent event) { OptimisticLockHelper.forceVersionIncrement( entity, entry, session ); break; case OPTIMISTIC_FORCE_INCREMENT: - session.getActionQueue().registerCallback( new EntityIncrementVersionProcess( entity ) ); + session.getActionQueue() + .registerCallback( new EntityIncrementVersionProcess( entity ) ); break; case OPTIMISTIC: - session.getActionQueue().registerCallback( new EntityVerifyVersionProcess( entity ) ); + session.getActionQueue() + .registerCallback( new EntityVerifyVersionProcess( entity ) ); break; } } else { - throw new HibernateException("[" + lockMode - + "] not supported for non-versioned entities [" + persister.getEntityName() + "]"); + throw new HibernateException( "Entity '" + persister.getEntityName() + + "' has no version and may not be locked at level " + lockMode); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java index 0fe45834b391..369dc220c61c 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java @@ -4,11 +4,10 @@ */ package org.hibernate.event.internal; +import org.hibernate.DetachedObjectException; import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; -import org.hibernate.NonUniqueObjectException; -import org.hibernate.TransientObjectException; import org.hibernate.UnresolvableObjectException; import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.engine.internal.Cascade; @@ -27,6 +26,8 @@ import org.hibernate.type.ComponentType; import org.hibernate.type.Type; +import static org.hibernate.engine.internal.ProxyUtil.forceInitialize; +import static org.hibernate.engine.internal.ProxyUtil.isUninitialized; import static org.hibernate.event.internal.EventListenerLogging.EVENT_LISTENER_LOGGER; import static org.hibernate.pretty.MessageHelper.infoString; import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; @@ -51,29 +52,30 @@ public void onRefresh(RefreshEvent event) throws HibernateException { */ @Override public void onRefresh(RefreshEvent event, RefreshContext refreshedAlready) { - final var source = event.getSession(); - final var persistenceContext = source.getPersistenceContextInternal(); final Object object = event.getObject(); - if ( persistenceContext.reassociateIfUninitializedProxy( object ) ) { - handleUninitializedProxy( event, refreshedAlready, source, object, persistenceContext ); + if ( object == null ) { + throw new NullPointerException( "Attempted to refresh null" ); } else { - final Object entity = persistenceContext.unproxyAndReassociate( object ); - if ( refreshedAlready.add( entity) ) { - refresh( event, refreshedAlready, entity ); + final var source = event.getEventSource(); + if ( isUninitialized( object, source ) ) { + handleUninitializedProxy( event, refreshedAlready ); } else { - EVENT_LISTENER_LOGGER.alreadyRefreshed(); + final Object entity = forceInitialize( object, source ); + if ( refreshedAlready.add( entity ) ) { + refresh( event, refreshedAlready, entity ); + } + else { + EVENT_LISTENER_LOGGER.alreadyRefreshed(); + } } } } - private static void handleUninitializedProxy( - RefreshEvent event, - RefreshContext refreshedAlready, - EventSource source, - Object object, - PersistenceContext persistenceContext) { + private static void handleUninitializedProxy(RefreshEvent event, RefreshContext refreshedAlready) { + final var source = event.getEventSource(); + final Object object = event.getObject(); final boolean isTransient = !source.isManaged( object ); // If refreshAlready is nonempty then the refresh is the result of a cascade refresh and the // refresh of the parent will take care of initializing the lazy entity and setting the @@ -88,8 +90,8 @@ private static void handleUninitializedProxy( persister, lazyInitializer, null, - persister.getIdentifier( object, event.getSession() ), - persistenceContext + persister.getIdentifier( object, source ), + source.getPersistenceContextInternal() ); if ( lazyInitializer != null ) { refreshedAlready.add( lazyInitializer.getImplementation() ); @@ -122,37 +124,18 @@ private static void refresh(RefreshEvent event, RefreshContext refreshedAlready, final var persistenceContext = source.getPersistenceContextInternal(); final var entry = persistenceContext.getEntry( object ); - final EntityPersister persister; - final Object id; if ( entry == null ) { - //refresh() does not pass an entityName - persister = source.getEntityPersister( event.getEntityName(), object ); - id = persister.getIdentifier( object, event.getSession() ); - if ( id == null ) { - throw new TransientObjectException( "Cannot refresh instance of entity '" + persister.getEntityName() - + "' because it has a null identifier" ); - } - if ( EVENT_LISTENER_LOGGER.isTraceEnabled() ) { - EVENT_LISTENER_LOGGER.refreshingTransient( - infoString( persister, id, event.getFactory() ) ); - } - if ( persistenceContext.getEntry( source.generateEntityKey( id, persister ) ) != null ) { - throw new NonUniqueObjectException( id, persister.getEntityName() ); - } + throw new DetachedObjectException( "Given entity is not associated with the persistence context" ); } - else { - if ( EVENT_LISTENER_LOGGER.isTraceEnabled() ) { - EVENT_LISTENER_LOGGER.refreshing( - infoString( entry.getPersister(), entry.getId(), event.getFactory() ) ); - } - if ( !entry.isExistsInDatabase() ) { - throw new UnresolvableObjectException( - entry.getId(), - "this instance does not yet exist as a row in the database" - ); - } - persister = entry.getPersister(); - id = entry.getId(); + + final EntityPersister persister = entry.getPersister(); + final Object id = entry.getId(); + if ( EVENT_LISTENER_LOGGER.isTraceEnabled() ) { + EVENT_LISTENER_LOGGER.refreshing( + infoString( persister, id, event.getFactory() ) ); + } + if ( !entry.isExistsInDatabase() ) { + throw new UnresolvableObjectException( id, persister.getEntityName() ); } // cascade the refresh prior to refreshing this entity @@ -165,13 +148,11 @@ private static void refresh(RefreshEvent event, RefreshContext refreshedAlready, refreshedAlready ); - if ( entry != null ) { - persistenceContext.removeEntityHolder( entry.getEntityKey() ); - if ( persister.hasCollections() ) { - new EvictVisitor( source, object ).process( object, persister ); - } - persistenceContext.removeEntry( object ); + persistenceContext.removeEntityHolder( entry.getEntityKey() ); + if ( persister.hasCollections() ) { + new EvictVisitor( source, object ).process( object, persister ); } + persistenceContext.removeEntry( object ); evictEntity( object, persister, id, source ); evictCachedCollections( persister, id, source ); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultReplicateEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultReplicateEventListener.java index 710c4593eccc..c1db00d2a228 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultReplicateEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultReplicateEventListener.java @@ -114,7 +114,17 @@ else if ( EVENT_LISTENER_LOGGER.isTraceEnabled() ) { private static boolean shouldOverwrite( ReplicationMode replicationMode, Object entityVersion, Object realOldVersion, BasicType versionType) { - return replicationMode.shouldOverwriteCurrentVersion( (T) realOldVersion, (T) entityVersion, versionType ); + return replicationMode.shouldOverwriteCurrentVersion( + castVersion( realOldVersion, versionType ), + castVersion( entityVersion, versionType ), + versionType + ); + } + + private static T castVersion(Object realOldVersion, BasicType versionType) { + return versionType == null + ? null + : versionType.getJavaTypeDescriptor().cast( realOldVersion ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/OnLockVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/OnLockVisitor.java deleted file mode 100644 index e9ae9db88778..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/OnLockVisitor.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.event.internal; - -import org.hibernate.HibernateException; -import org.hibernate.collection.spi.PersistentCollection; -import org.hibernate.event.spi.EventSource; -import org.hibernate.type.CollectionType; - -/** - * When a transient entity is passed to lock(), we must inspect all its collections and - *

    - *
  1. associate any uninitialized PersistentCollections with this session - *
  2. associate any initialized PersistentCollections with this session, using the - * existing snapshot - *
  3. throw an exception for each "new" collection - *
- * - * @author Gavin King - */ -public class OnLockVisitor extends ReattachVisitor { - - public OnLockVisitor(EventSource session, Object key, Object owner) { - super( session, key, owner ); - } - - @Override - public Object processCollection(Object collection, CollectionType type) throws HibernateException { - if ( collection == null ) { - return null; - } - - final var session = getSession(); - final var persister = - session.getFactory().getMappingMetamodel() - .getCollectionDescriptor( type.getRole() ); - if ( collection instanceof PersistentCollection persistentCollection ) { - if ( persistentCollection.setCurrentSession( session ) ) { - if ( isOwnerUnchanged( persister, extractCollectionKeyFromOwner( persister ), persistentCollection ) ) { - // a "detached" collection that originally belonged to the same entity - if ( persistentCollection.isDirty() ) { - throw new HibernateException( "reassociated object has dirty collection" ); - } - reattachCollection( persistentCollection, type ); - } - else { - // a "detached" collection that belonged to a different entity - throw new HibernateException( "reassociated object has dirty collection reference" ); - } - } - else { - // a collection loaded in the current session - // cannot possibly be the collection belonging - // to the entity passed to update() - throw new HibernateException( "reassociated object has dirty collection reference" ); - } - } - else { - // brand new collection - //TODO: or an array!! we can't lock objects with arrays now?? - throw new HibernateException( "reassociated object has dirty collection reference (or an array)" ); - } - return null; - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/OnReplicateVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/OnReplicateVisitor.java index a29866e9b476..a25578077b31 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/OnReplicateVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/OnReplicateVisitor.java @@ -10,8 +10,8 @@ import org.hibernate.type.CollectionType; /** - * When an entity is passed to replicate(), and there is an existing row, we must - * inspect all its collections and + * When an entity is passed to {@link org.hibernate.Session#replicate}, and there + * is an existing row, we must inspect all its collections and: *
    *
  1. associate any uninitialized PersistentCollections with this session *
  2. associate any initialized PersistentCollections with this session, using the diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/OnUpdateVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/OnUpdateVisitor.java index 4e083feb66e5..6a716d7cf522 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/OnUpdateVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/OnUpdateVisitor.java @@ -10,12 +10,15 @@ import org.hibernate.type.CollectionType; /** - * When an entity is passed to update(), we must inspect all its collections and - * 1. associate any uninitialized PersistentCollections with this session - * 2. associate any initialized PersistentCollections with this session, using the - * existing snapshot - * 3. execute a collection removal (SQL DELETE) for each null collection property - * or "new" collection + * When a detached entity is passed to {@link org.hibernate.Session#remove(Object)}, + * we must inspect all its collections and: + *
      + *
    1. associate any uninitialized PersistentCollections with this session + *
    2. associate any initialized PersistentCollections with this session, using the + * existing snapshot + *
    3. execute a collection removal (SQL DELETE) for each null collection property + * or "new" collection + *
    * * @author Gavin King */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/ProxyVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/ProxyVisitor.java index 7d08753bb5d4..d0e22cde10de 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/ProxyVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/ProxyVisitor.java @@ -9,10 +9,11 @@ import org.hibernate.event.spi.EventSource; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.type.CollectionType; -import org.hibernate.type.EntityType; /** - * Reassociates uninitialized proxies with the session + * A visitor able to reattach {@linkplain PersistentCollection collections} + * to the current session. + * * @author Gavin King */ public abstract class ProxyVisitor extends AbstractVisitor { @@ -21,15 +22,6 @@ public ProxyVisitor(EventSource session) { super(session); } - Object processEntity(Object value, EntityType entityType) { - if ( value != null ) { - getSession().getPersistenceContext().reassociateIfUninitializedProxy( value ); - // if it is an initialized proxy, let cascade - // handle it later on - } - return null; - } - /** * Has the owner of the collection changed since the collection * was snapshotted and detached? @@ -63,7 +55,7 @@ protected void reattachCollection(PersistentCollection collection, Collection } else { if ( !isCollectionSnapshotValid( collection ) ) { - throw new HibernateException( "could not re-associate uninitialized transient collection" ); + throw new HibernateException( "Could not reassociate uninitialized transient collection" ); } final var persister = metamodel.getCollectionDescriptor( collection.getRole() ); context.addUninitializedDetachedCollection( persister, collection ); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/ReattachVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/ReattachVisitor.java index eb059e50b610..b4fc55cc6fde 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/ReattachVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/ReattachVisitor.java @@ -9,13 +9,15 @@ import org.hibernate.event.spi.EventSource; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.type.CompositeType; +import org.hibernate.type.EntityType; import org.hibernate.type.Type; import static org.hibernate.event.internal.EventListenerLogging.EVENT_LISTENER_LOGGER; import static org.hibernate.pretty.MessageHelper.collectionInfoString; /** - * Abstract superclass of visitors that reattach collections. + * Abstract superclass of visitors that reattach collections + * and reassociate uninitialized proxies with the session. * * @author Gavin King */ @@ -30,6 +32,17 @@ public ReattachVisitor(EventSource session, Object ownerIdentifier, Object owner this.owner = owner; } + @Override + Object processEntity(Object value, EntityType entityType) { + if ( value != null ) { + getSession().getPersistenceContext() + .reassociateIfUninitializedProxy( value ); + // if it is an initialized proxy, + // let cascade handle it later on + } + return null; + } + /** * Retrieve the identifier of the entity being visited. * diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java index 90c6ec8f37d0..1c2d8ff95da5 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java @@ -22,7 +22,7 @@ import static org.hibernate.persister.entity.AbstractEntityPersister.getCollectionKey; /** - * Wrap collections in a Hibernate collection wrapper. + * Wrap collections in {@linkplain PersistentCollection collection wrappers}. * * @author Gavin King */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java index 2bda60e2a49b..445ac91b47c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerRegistryImpl.java @@ -135,10 +135,9 @@ private T[] resolveListenerInstances(EventType type, Class.. } private T resolveListenerInstance(Class listenerClass) { - @SuppressWarnings("unchecked") - final T listenerInstance = (T) listenerClassToInstanceMap.get( listenerClass ); + final T listenerInstance = listenerClass.cast( listenerClassToInstanceMap.get( listenerClass ) ); if ( listenerInstance == null ) { - T newListenerInstance = instantiateListener( listenerClass ); + final T newListenerInstance = instantiateListener( listenerClass ); listenerClassToInstanceMap.put( listenerClass, newListenerInstance ); return newListenerInstance; } diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java index e85ba703c64a..70b209b7d413 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java @@ -4,6 +4,7 @@ */ package org.hibernate.event.spi; +import org.hibernate.Internal; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.Locking; @@ -66,32 +67,36 @@ private LoadEvent( boolean isAssociationFetch, EventSource source, Boolean readOnly) { - super( source ); + this.entityId = entityId; + this.entityClassName = entityClassName; + this.instanceToLoad = instanceToLoad; + this.lockOptions = lockOptions; + this.isAssociationFetch = isAssociationFetch; + this.readOnly = readOnly; + validate(); + } + @Internal + public void validate() { if ( entityId == null ) { - throw new IllegalArgumentException( "id to load is required for loading" ); + throw new IllegalArgumentException( "Identifier may not be null" ); } - if ( lockOptions.getLockMode() == LockMode.WRITE ) { - throw new IllegalArgumentException("Invalid lock mode for loading"); + final var lockMode = lockOptions.getLockMode(); + if ( lockMode == LockMode.WRITE ) { + throw new IllegalArgumentException( "Invalid lock mode: " + LockMode.WRITE ); } - else if ( lockOptions.getLockMode() == null ) { + else if ( lockMode == null ) { lockOptions.setLockMode( LockMode.NONE ); } - - this.entityId = entityId; - this.entityClassName = entityClassName; - this.instanceToLoad = instanceToLoad; - this.lockOptions = lockOptions; - this.isAssociationFetch = isAssociationFetch; - this.readOnly = readOnly; } public Object getEntityId() { return entityId; } + @Internal public void setEntityId(Object entityId) { this.entityId = entityId; } @@ -100,6 +105,7 @@ public String getEntityClassName() { return entityClassName; } + @Internal public void setEntityClassName(String entityClassName) { this.entityClassName = entityClassName; } @@ -108,6 +114,7 @@ public boolean isAssociationFetch() { return isAssociationFetch; } + @Internal public void setAssociationFetch(boolean associationFetch) { isAssociationFetch = associationFetch; } @@ -116,6 +123,7 @@ public Object getInstanceToLoad() { return instanceToLoad; } + @Internal public void setInstanceToLoad(Object instanceToLoad) { this.instanceToLoad = instanceToLoad; } @@ -124,6 +132,7 @@ public LockOptions getLockOptions() { return lockOptions; } + @Internal public void setLockOptions(LockOptions lockOptions) { this.lockOptions = lockOptions; } @@ -140,12 +149,11 @@ public Boolean getReadOnly() { return readOnly; } + @Internal public void setReadOnly(Boolean readOnly) { this.readOnly = readOnly; } - - /** * @deprecated Use {@linkplain #getLockOptions()} instead. */ diff --git a/hibernate-core/src/main/java/org/hibernate/generator/AnnotationBasedGenerator.java b/hibernate-core/src/main/java/org/hibernate/generator/AnnotationBasedGenerator.java index f478b4cf3280..165ca05ba2ad 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/AnnotationBasedGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/AnnotationBasedGenerator.java @@ -37,6 +37,7 @@ * @see org.hibernate.annotations.IdGeneratorType * * @author Gavin King + * @author Yanming Zhou * * @since 6.2 */ @@ -49,8 +50,27 @@ public interface AnnotationBasedGenerator extends Generato * values and store them in fields. * @param member the Java member annotated with the generator annotation. * @param context a {@link GeneratorCreationContext} - * @throws org.hibernate.HibernateException in case an error occurred during initialization, e.g. if - * an implementation can't create a value for the given property type. + * @throws org.hibernate.HibernateException in case an error occurred during initialization, + * for example, if an implementation can't create a value for the given property type. + * @deprecated Use {@link #initialize(Annotation, GeneratorCreationContext)} instead */ - void initialize(A annotation, Member member, GeneratorCreationContext context); + @Deprecated(since = "7.3", forRemoval = true) + default void initialize(A annotation, Member member, GeneratorCreationContext context) { + } + + /** + * Initializes this generation strategy for the given annotation instance. + * + * @param annotation an instance of the strategy's annotation type. Typically, + * implementations will retrieve the annotation's attribute + * values and store them in fields. + * @param context a {@link GeneratorCreationContext} + * @throws org.hibernate.HibernateException in case an error occurred during initialization, + * for example, if an implementation can't create a value for the given property type. + * + * @since 7.3 + */ + default void initialize(A annotation, GeneratorCreationContext context) { + initialize( annotation, context.getMemberDetails().toJavaMember(), context ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/generator/Generator.java b/hibernate-core/src/main/java/org/hibernate/generator/Generator.java index a379444fa85a..8795266262f7 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/Generator.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/Generator.java @@ -48,7 +48,7 @@ * {@link AnnotationBasedGenerator#initialize}, *
  3. declare a constructor with the same signature as {@link AnnotationBasedGenerator#initialize}, *
  4. declare a constructor which accepts just the annotation instance, or - *
  5. declare a only default constructor, in which case it will not receive parameters. + *
  6. declare only a default constructor, in which case it does not receive parameters. * *

    * A {@code Generator} may be a managed bean (for example, a CDI bean) instantiated by the diff --git a/hibernate-core/src/main/java/org/hibernate/generator/GeneratorCreationContext.java b/hibernate-core/src/main/java/org/hibernate/generator/GeneratorCreationContext.java index b8b575f5b579..0d8c8677fa3b 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/GeneratorCreationContext.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/GeneratorCreationContext.java @@ -13,6 +13,7 @@ import org.hibernate.mapping.Property; import org.hibernate.mapping.RootClass; import org.hibernate.mapping.Value; +import org.hibernate.models.spi.MemberDetails; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.Type; @@ -81,7 +82,17 @@ default Type getType() { * The {@link SqlStringGenerationContext} to use when generating SQL. */ default SqlStringGenerationContext getSqlStringGenerationContext() { - final Database database = getDatabase(); + final var database = getDatabase(); return fromExplicit( database.getJdbcEnvironment(), database, getDefaultCatalog(), getDefaultSchema() ); } + + /** + * Access to the {@link MemberDetails}. + * + * @since 7.3 + */ + default MemberDetails getMemberDetails() { + return null; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/generator/internal/CurrentTimestampGeneration.java b/hibernate-core/src/main/java/org/hibernate/generator/internal/CurrentTimestampGeneration.java index a3018f966b85..affa42c76155 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/internal/CurrentTimestampGeneration.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/internal/CurrentTimestampGeneration.java @@ -4,7 +4,6 @@ */ package org.hibernate.generator.internal; -import java.lang.reflect.Member; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.SQLException; @@ -39,8 +38,8 @@ import org.hibernate.generator.EventType; import org.hibernate.generator.GeneratorCreationContext; import org.hibernate.generator.OnExecutionGenerator; -import org.hibernate.internal.util.ReflectHelper; import org.hibernate.mapping.BasicValue; +import org.hibernate.type.Type; import org.hibernate.type.descriptor.java.ClockHelper; import org.checkerframework.checker.nullness.qual.Nullable; @@ -86,29 +85,29 @@ public class CurrentTimestampGeneration implements BeforeExecutionGenerator, OnE private static final ConcurrentHashMap GENERATOR_DELEGATES = new ConcurrentHashMap<>(); - public CurrentTimestampGeneration(CurrentTimestamp annotation, Member member, GeneratorCreationContext context) { - delegate = getGeneratorDelegate( annotation.source(), member, context ); + public CurrentTimestampGeneration(CurrentTimestamp annotation, GeneratorCreationContext context) { + delegate = getGeneratorDelegate( annotation.source(), context.getType(), context ); eventTypes = fromArray( annotation.event() ); propertyType = getPropertyType( context ); } - public CurrentTimestampGeneration(CreationTimestamp annotation, Member member, GeneratorCreationContext context) { - delegate = getGeneratorDelegate( annotation.source(), member, context ); + public CurrentTimestampGeneration(CreationTimestamp annotation, GeneratorCreationContext context) { + delegate = getGeneratorDelegate( annotation.source(), context.getType(), context ); eventTypes = INSERT_ONLY; propertyType = getPropertyType( context ); } - public CurrentTimestampGeneration(UpdateTimestamp annotation, Member member, GeneratorCreationContext context) { - delegate = getGeneratorDelegate( annotation.source(), member, context ); + public CurrentTimestampGeneration(UpdateTimestamp annotation, GeneratorCreationContext context) { + delegate = getGeneratorDelegate( annotation.source(), context.getType(), context ); eventTypes = INSERT_AND_UPDATE; propertyType = getPropertyType( context ); } private static GeneratorDelegate getGeneratorDelegate( SourceType source, - Member member, + Type type, GeneratorCreationContext context) { - return getGeneratorDelegate( source, ReflectHelper.getPropertyType( member ), context ); + return getGeneratorDelegate( source, type.getReturnedClass(), context ); } static GeneratorDelegate getGeneratorDelegate( diff --git a/hibernate-core/src/main/java/org/hibernate/generator/internal/SourceGeneration.java b/hibernate-core/src/main/java/org/hibernate/generator/internal/SourceGeneration.java index 333735e46e1f..a1415ecebcdf 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/internal/SourceGeneration.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/internal/SourceGeneration.java @@ -16,7 +16,6 @@ import org.hibernate.type.descriptor.java.JavaType; -import java.lang.reflect.Member; import java.util.EnumSet; import static org.hibernate.generator.internal.CurrentTimestampGeneration.getCurrentTimestamp; @@ -45,7 +44,7 @@ public class SourceGeneration implements BeforeExecutionGenerator { private final JavaType propertyType; private final CurrentTimestampGeneration.GeneratorDelegate valueGenerator; - public SourceGeneration(Source annotation, Member member, GeneratorCreationContext context) { + public SourceGeneration(Source annotation, GeneratorCreationContext context) { this( annotation.value(), context.getProperty().getType().getReturnedClass(), context ); } diff --git a/hibernate-core/src/main/java/org/hibernate/generator/internal/TenantIdGeneration.java b/hibernate-core/src/main/java/org/hibernate/generator/internal/TenantIdGeneration.java index 074eef45f0a0..367bd56d17e4 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/internal/TenantIdGeneration.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/internal/TenantIdGeneration.java @@ -4,7 +4,6 @@ */ package org.hibernate.generator.internal; -import java.lang.reflect.Member; import java.util.EnumSet; import org.hibernate.PropertyValueException; @@ -28,13 +27,13 @@ public class TenantIdGeneration implements BeforeExecutionGenerator { private final String entityName; private final String propertyName; - public TenantIdGeneration(TenantId annotation, Member member, GeneratorCreationContext context) { + public TenantIdGeneration(TenantId annotation, GeneratorCreationContext context) { final var persistentClass = context.getPersistentClass(); entityName = persistentClass != null ? persistentClass.getEntityName() // it's an attribute of an embeddable - : member.getDeclaringClass().getName(); + : context.getMemberDetails().toJavaMember().getDeclaringClass().getName(); propertyName = context.getProperty().getName(); } diff --git a/hibernate-core/src/main/java/org/hibernate/graph/EntityGraphs.java b/hibernate-core/src/main/java/org/hibernate/graph/EntityGraphs.java index 9d38c57924e1..b59f589b284c 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/EntityGraphs.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/EntityGraphs.java @@ -7,7 +7,6 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Stream; import jakarta.persistence.AttributeNode; @@ -64,13 +63,13 @@ public static EntityGraph createGraph(EntityType rootType) { * @since 7.0 */ public static EntityGraph> createGraphForDynamicEntity(EntityType rootType) { - final EntityDomainType domainType = (EntityDomainType) rootType; + final var domainType = (EntityDomainType) rootType; if ( domainType.getRepresentationMode() != RepresentationMode.MAP ) { throw new IllegalArgumentException( "Entity '" + domainType.getHibernateEntityName() + "' is not a dynamic entity" ); } @SuppressWarnings("unchecked") //Safe, because we just checked - final EntityDomainType> dynamicEntity = (EntityDomainType>) domainType; + final var dynamicEntity = (EntityDomainType>) domainType; return new RootGraphImpl<>( null, dynamicEntity ); } @@ -172,7 +171,7 @@ public static void setFetchGraph(TypedQuery query, EntityGraph graph) } /** - * Allows a treated subgraph to ve created for a {@link Subgraph}, since the + * Allows a treated subgraph to be created for a {@link Subgraph}, since the * JPA-standard operation {@link EntityGraph#addTreatedSubgraph(Class)} is * declared by {@link EntityGraph}. * @@ -310,31 +309,10 @@ public static boolean areEqual(EntityGraph a, EntityGraph b) { if ( a == b ) { return true; } - if ( ( a == null ) || ( b == null ) ) { - return false; - } - - final List> aNodes = a.getAttributeNodes(); - final List> bNodes = b.getAttributeNodes(); - - if ( aNodes.size() != bNodes.size() ) { - return false; - } - for ( AttributeNode aNode : aNodes ) { - final String attributeName = aNode.getAttributeName(); - AttributeNode bNode = null; - for ( AttributeNode bCandidate : bNodes ) { - if ( attributeName.equals( bCandidate.getAttributeName() ) ) { - bNode = bCandidate; - break; - } - } - if ( !areEqual( aNode, bNode ) ) { - return false; - } + else { + return a != null && b != null + && haveSameNodes( a, b ); } - - return true; } /** @@ -345,10 +323,10 @@ public static boolean areEqual(AttributeNode a, AttributeNode b) { if ( a == b ) { return true; } - if ( ( a == null ) || ( b == null ) ) { + else if ( a == null || b == null ) { return false; } - if ( a.getAttributeName().equals( b.getAttributeName() ) ) { + else if ( a.getAttributeName().equals( b.getAttributeName() ) ) { return areEqual( a.getSubgraphs(), b.getSubgraphs() ) && areEqual( a.getKeySubgraphs(), b.getKeySubgraphs() ); } @@ -367,28 +345,23 @@ public static boolean areEqual( if ( a == b ) { return true; } - if ( ( a == null ) || ( b == null ) ) { + else if ( a == null || b == null ) { return false; } - - @SuppressWarnings("rawtypes") - final Set aKeys = a.keySet(); - @SuppressWarnings("rawtypes") - final Set bKeys = b.keySet(); - - if ( aKeys.equals( bKeys ) ) { - for ( Class clazz : aKeys ) { - if ( !bKeys.contains( clazz ) ) { - return false; - } - if ( !areEqual( a.get( clazz ), b.get( clazz ) ) ) { - return false; + else { + final var aKeys = a.keySet(); + final var bKeys = b.keySet(); + if ( aKeys.equals( bKeys ) ) { + for ( var key : aKeys ) { + if ( !areEqual( a.get( key ), b.get( key ) ) ) { + return false; + } } + return true; + } + else { + return false; } - return true; - } - else { - return false; } } @@ -396,42 +369,38 @@ public static boolean areEqual( * Compares two entity subgraphs and returns {@code true} if they are equal, * ignoring attribute order. */ - public static boolean areEqual( - @SuppressWarnings("rawtypes") Subgraph a, - @SuppressWarnings("rawtypes") Subgraph b) { + public static boolean areEqual(Subgraph a, Subgraph b) { if ( a == b ) { return true; } - if ( ( a == null ) || ( b == null ) ) { - return false; - } - if ( a.getClassType() != b.getClassType() ) { - return false; + else { + return a != null && b != null + && a.getClassType() == b.getClassType() + && haveSameNodes( a, b ); } + } - @SuppressWarnings("unchecked") - final List> aNodes = a.getAttributeNodes(); - @SuppressWarnings("unchecked") - final List> bNodes = b.getAttributeNodes(); - + private static boolean haveSameNodes(Graph a, Graph b) { + final var aNodes = a.getAttributeNodes(); + final var bNodes = b.getAttributeNodes(); if ( aNodes.size() != bNodes.size() ) { return false; } - - for ( AttributeNode aNode : aNodes ) { - final String attributeName = aNode.getAttributeName(); - AttributeNode bNode = null; - for ( AttributeNode bCandidate : bNodes ) { - if ( attributeName.equals( bCandidate.getAttributeName() ) ) { - bNode = bCandidate; - break; + else { + for ( var aNode : aNodes ) { + final String attributeName = aNode.getAttributeName(); + AttributeNode bNode = null; + for ( var bCandidate : bNodes ) { + if ( attributeName.equals( bCandidate.getAttributeName() ) ) { + bNode = bCandidate; + break; + } + } + if ( !areEqual( aNode, bNode ) ) { + return false; } } - if ( !areEqual( aNode, bNode ) ) { - return false; - } + return true; } - - return true; } } diff --git a/hibernate-core/src/main/java/org/hibernate/graph/GraphParser.java b/hibernate-core/src/main/java/org/hibernate/graph/GraphParser.java index 0bfcb46624ad..f2386b680453 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/GraphParser.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/GraphParser.java @@ -9,6 +9,7 @@ import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.Subgraph; +import org.hibernate.Incubating; import org.hibernate.SessionFactory; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; @@ -27,7 +28,8 @@ * The {@link #parse} methods all create a root {@link jakarta.persistence.EntityGraph} * based on the passed entity class and parse the graph string into that root graph. *

    - * The {@link #parseInto} methods parse the graph string into a passed graph, which may be a subgraph + * The {@link #parseInto} methods parse the graph string into a passed graph, which may + * be a subgraph. *

    * Multiple graphs for the same entity type can be * {@linkplain EntityGraphs#merge(EntityManager, Class, jakarta.persistence.Graph...) @@ -35,7 +37,6 @@ * * @author asusnjar */ -@SuppressWarnings("unused") public final class GraphParser { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -89,6 +90,7 @@ public static RootGraph parse( * * @since 7.0 */ + @Incubating public static RootGraph parse( final String rootEntityName, final CharSequence graphText, @@ -96,7 +98,8 @@ public static RootGraph parse( if ( graphText == null ) { return null; } - return GraphParsing.parse( + //noinspection unchecked + return (RootGraph) GraphParsing.parse( rootEntityName, graphText.toString(), sessionFactory.unwrap( SessionFactoryImplementor.class ) @@ -117,21 +120,23 @@ public static RootGraph parse( * * @since 7.0 */ - public static RootGraph parse( + @Incubating + public static RootGraph parse( final CharSequence graphText, final SessionFactory sessionFactory) { if ( graphText == null ) { return null; } - return GraphParsing.parse( + //noinspection unchecked + return (RootGraph) GraphParsing.parse( graphText.toString(), sessionFactory.unwrap( SessionFactoryImplementor.class ) - ); + ); } /** - * Creates a root graph based on the passed `rootType` and parses `graphText` into - * the generated root graph + * Creates a root graph based on the passed {@code rootType} and parses {@code graphText} + * into the generated root graph. * * @apiNote The passed EntityManager is expected to be a Hibernate implementation. * Attempting to pass another provider's EntityManager implementation will fail. @@ -153,7 +158,8 @@ public static RootGraph parse( return GraphParsing.parse( rootType, graphText.toString(), - entityManager.getEntityManagerFactory().unwrap( SessionFactoryImplementor.class ) + entityManager.getEntityManagerFactory() + .unwrap( SessionFactoryImplementor.class ) ); } @@ -190,13 +196,12 @@ public static void parseInto( * * @throws InvalidGraphException if the textual representation is invalid. */ - @SuppressWarnings("unchecked") public static void parseInto( - final EntityGraph graph, + final EntityGraph graph, final CharSequence graphText, final EntityManager entityManager) { parseInto( - (GraphImplementor) graph, + (GraphImplementor) graph, graphText, ( (SessionImplementor) entityManager ).getSessionFactory() ); @@ -211,13 +216,12 @@ public static void parseInto( * * @throws InvalidGraphException if the textual representation is invalid. */ - @SuppressWarnings("unchecked") - public static void parseInto( - final Subgraph graph, + public static void parseInto( + final Subgraph graph, final CharSequence graphText, final EntityManager entityManager) { parseInto( - (GraphImplementor) graph, + (GraphImplementor) graph, graphText, ( (SessionImplementor) entityManager ).getSessionFactory() ); @@ -232,12 +236,12 @@ public static void parseInto( * * @throws InvalidGraphException if the textual representation is invalid. */ - public static void parseInto( - final Graph graph, + public static void parseInto( + final Graph graph, final CharSequence graphText, final EntityManagerFactory entityManagerFactory) { parseInto( - (GraphImplementor) graph, + (GraphImplementor) graph, graphText, (SessionFactoryImplementor) entityManagerFactory ); @@ -252,13 +256,12 @@ public static void parseInto( * * @throws InvalidGraphException if the textual representation is invalid. */ - @SuppressWarnings("unchecked") - public static void parseInto( - final EntityGraph graph, + public static void parseInto( + final EntityGraph graph, final CharSequence graphText, final EntityManagerFactory entityManagerFactory) { parseInto( - (GraphImplementor) graph, + (GraphImplementor) graph, graphText, (SessionFactoryImplementor) entityManagerFactory ); @@ -273,13 +276,12 @@ public static void parseInto( * * @throws InvalidGraphException if the textual representation is invalid. */ - @SuppressWarnings("unchecked") - public static void parseInto( - final Subgraph graph, + public static void parseInto( + final Subgraph graph, final CharSequence graphText, final EntityManagerFactory entityManagerFactory) { parseInto( - (GraphImplementor) graph, + (GraphImplementor) graph, graphText, (SessionFactoryImplementor) entityManagerFactory ); @@ -289,8 +291,6 @@ public static void parseInto( * Parses the textual graph representation as {@linkplain GraphParser described above} * into the specified graph. * - * @param The Java type for the ManagedType described by `graph` - * * @param graph The target graph. This is the graph that will be populated * by this process * @param graphText Textual representation of the graph @@ -298,8 +298,8 @@ public static void parseInto( * * @throws InvalidGraphException if the textual representation is invalid. */ - private static void parseInto( - GraphImplementor graph, + private static void parseInto( + GraphImplementor graph, final CharSequence graphText, SessionFactoryImplementor sessionFactory) { if ( graphText != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/AttributeNodeImpl.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/AttributeNodeImpl.java index 6856698ea440..dd25ee52bc47 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/AttributeNodeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/AttributeNodeImpl.java @@ -258,13 +258,13 @@ public SubGraphImplementor makeSubGraph() { @Override @Deprecated public SubGraphImplementor makeSubGraph(Class subtype) { - final ManagedDomainType managedType = asManagedType( valueGraphType ); + final var managedType = asManagedType( valueGraphType ); if ( !managedType.getJavaType().isAssignableFrom( subtype ) ) { throw new IllegalArgumentException( "Not a subtype: " + subtype.getName() ); } @SuppressWarnings("unchecked") - final Class castSuptype = (Class) subtype; - final SubGraphImplementor result = makeSubGraph().addTreatedSubgraph( castSuptype ); + final var castSuptype = (Class) subtype; + final var result = makeSubGraph().addTreatedSubgraph( castSuptype ); //noinspection unchecked return (SubGraphImplementor) result; } @@ -282,13 +282,13 @@ public SubGraphImplementor makeKeySubGraph() { @Override @Deprecated public SubGraphImplementor makeKeySubGraph(Class subtype) { checkMap(); - final ManagedDomainType type = asManagedType( keyGraphType ); + final var type = asManagedType( keyGraphType ); if ( !type.getJavaType().isAssignableFrom( subtype ) ) { throw new IllegalArgumentException( "Not a key subtype: " + subtype.getName() ); } @SuppressWarnings("unchecked") - final Class castType = (Class) subtype; - final SubGraphImplementor result = makeKeySubGraph().addTreatedSubgraph( castType ); + final var castType = (Class) subtype; + final var result = makeKeySubGraph().addTreatedSubgraph( castType ); //noinspection unchecked return (SubGraphImplementor) result; } @@ -323,7 +323,7 @@ public String toString() { public void merge(AttributeNodeImplementor that) { assert that.isMutable() == isMutable(); assert that.getAttributeDescriptor() == attribute; - final SubGraphImplementor otherValueSubgraph = that.getValueSubgraph(); + final var otherValueSubgraph = that.getValueSubgraph(); if ( otherValueSubgraph != null ) { if ( valueSubgraph == null ) { valueSubgraph = otherValueSubgraph.makeCopy( isMutable() ); @@ -333,7 +333,7 @@ public void merge(AttributeNodeImplementor that) { valueSubgraph.mergeInternal( otherValueSubgraph ); } } - final SubGraphImplementor otherKeySubgraph = that.getKeySubgraph(); + final var otherKeySubgraph = that.getKeySubgraph(); if ( otherKeySubgraph != null ) { if ( keySubgraph == null ) { keySubgraph = otherKeySubgraph.makeCopy( isMutable() ); @@ -351,7 +351,8 @@ public Map, SubGraphImplementor> getSubGraphs() { return emptyMap(); } else { - final HashMap, SubGraphImplementor> map = new HashMap<>( valueSubgraph.getTreatedSubgraphs() ); + final HashMap, SubGraphImplementor> map = + new HashMap<>( valueSubgraph.getTreatedSubgraphs() ); map.put( attribute.getValueGraphType().getJavaType(), valueSubgraph ); return map; } @@ -363,7 +364,8 @@ public Map, SubGraphImplementor> getKeySubGraphs() { return emptyMap(); } else { - final HashMap, SubGraphImplementor> map = new HashMap<>( keySubgraph.getTreatedSubgraphs() ); + final HashMap, SubGraphImplementor> map = + new HashMap<>( keySubgraph.getTreatedSubgraphs() ); map.put( attribute.getKeyGraphType().getJavaType(), keySubgraph ); return map; } diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/GraphImpl.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/GraphImpl.java index cd40c149e714..0a78778d88e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/GraphImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/GraphImpl.java @@ -156,19 +156,19 @@ public void mergeInternal(GraphImplementor graph) { } private void mergeNode(PersistentAttribute attribute, AttributeNodeImplementor node) { - final AttributeNodeImplementor existingNode = getNodeForPut( attribute ); + final var existingNode = getNodeForPut( attribute ); if ( existingNode == null ) { attributeNodes.put( attribute, node.makeCopy( isMutable() ) ); } else { - // keep the local one, but merge in the incoming one + // keep the local one but merge in the incoming one mergeNode( node, existingNode ); } } private void mergeGraph(SubGraphImplementor subgraph) { - final Class javaType = subgraph.getClassType(); - final SubGraphImplementor existing = getTreatedSubgraphForPut( javaType ); + final var javaType = subgraph.getClassType(); + final var existing = getTreatedSubgraphForPut( javaType ); if ( existing == null ) { treatedSubgraphs.put( javaType, subgraph.makeCopy( isMutable() ) ); } @@ -182,7 +182,7 @@ private static void mergeNode( AttributeNodeImplementor node, AttributeNodeImplementor existingNode) { if ( existingNode.getAttributeDescriptor() == node.getAttributeDescriptor() ) { @SuppressWarnings("unchecked") // safe, we just checked - final AttributeNodeImplementor castNode = (AttributeNodeImplementor) node; + final var castNode = (AttributeNodeImplementor) node; existingNode.merge( castNode ); } else { @@ -197,12 +197,12 @@ public List> getAttributeNodeList() { @Override public AttributeNodeImplementor findAttributeNode(String attributeName) { - final PersistentAttribute attribute = findAttributeInSupertypes( attributeName ); + final var attribute = findAttributeInSupertypes( attributeName ); @SuppressWarnings("unchecked") // The JPA API is unsafe by nature - final PersistentAttribute persistentAttribute = (PersistentAttribute) attribute; - final AttributeNodeImplementor node = attribute == null ? null : findAttributeNode( persistentAttribute ); + final var persistentAttribute = (PersistentAttribute) attribute; + final var node = attribute == null ? null : findAttributeNode( persistentAttribute ); if ( node == null && treatedSubgraphs != null ) { - for ( SubGraphImplementor subgraph : treatedSubgraphs.values() ) { + for ( var subgraph : treatedSubgraphs.values() ) { final AttributeNodeImplementor subgraphNode = subgraph.findAttributeNode( attributeName ); if ( subgraphNode != null ) { return subgraphNode; @@ -264,7 +264,7 @@ public void addAttributeNodes(String... attributeNames) { @Override @SafeVarargs public final void addAttributeNodes(Attribute... attributes) { - for ( Attribute attribute : attributes ) { + for ( var attribute : attributes ) { addAttributeNode( attribute ); } } @@ -290,9 +290,9 @@ public void removeAttributeNodes(Attribute.PersistentAttributeType nodeType) { @Override public AttributeNodeImplementor findOrCreateAttributeNode(PersistentAttribute attribute) { verifyMutability(); - final AttributeNodeImplementor node = getNodeForPut( attribute ); + final var node = getNodeForPut( attribute ); if ( node == null ) { - final AttributeNodeImplementor newAttrNode = AttributeNodeImpl.create( attribute, isMutable() ); + final var newAttrNode = AttributeNodeImpl.create( attribute, isMutable() ); attributeNodes.put( attribute, newAttrNode ); return newAttrNode; } @@ -303,9 +303,9 @@ public AttributeNodeImplementor findOrCreateAttributeNode(Persisten private AttributeNodeImplementor findOrCreateAttributeNode(PluralPersistentAttribute attribute) { verifyMutability(); - final AttributeNodeImplementor node = getNodeForPut( attribute ); + final var node = getNodeForPut( attribute ); if ( node == null ) { - final AttributeNodeImplementor newAttrNode = AttributeNodeImpl.create( attribute, isMutable() ); + final var newAttrNode = AttributeNodeImpl.create( attribute, isMutable() ); attributeNodes.put( attribute, newAttrNode ); return newAttrNode; } @@ -316,9 +316,9 @@ private AttributeNodeImplementor findOrCreateAttributeNode(PluralPe private AttributeNodeImplementor,V,K> findOrCreateAttributeNode(MapPersistentAttribute attribute) { verifyMutability(); - final AttributeNodeImplementor,V,K> node = getNodeForPut( attribute ); + final var node = getNodeForPut( attribute ); if ( node == null ) { - final AttributeNodeImplementor,V,K> newAttrNode = AttributeNodeImpl.create( attribute, isMutable() ); + final var newAttrNode = AttributeNodeImpl.create( attribute, isMutable() ); attributeNodes.put( attribute, newAttrNode ); return newAttrNode; } @@ -329,21 +329,21 @@ private AttributeNodeImplementor,V,K> findOrCreateAttributeNode(M @Override public AttributeNodeImplementor findOrCreateAttributeNode(String attributeName) { - final PersistentAttribute attribute = getAttribute( attributeName ); + final var attribute = getAttribute( attributeName ); @SuppressWarnings("unchecked") // The JPA API is unsafe by nature - final PersistentAttribute persistentAttribute = (PersistentAttribute) attribute; + final var persistentAttribute = (PersistentAttribute) attribute; return findOrCreateAttributeNode( persistentAttribute ); } private PersistentAttribute findAttributeInSupertypes(String attributeName) { - final PersistentAttribute attribute = managedType.findAttributeInSuperTypes( attributeName ); + final var attribute = managedType.findAttributeInSuperTypes( attributeName ); return attribute instanceof SqmPathSource sqmPathSource && sqmPathSource.isGeneric() ? managedType.findConcreteGenericAttribute( attributeName ) : attribute; } private PersistentAttribute getAttribute(String attributeName) { - final PersistentAttribute attribute = managedType.getAttribute( attributeName ); + final var attribute = managedType.getAttribute( attributeName ); return attribute instanceof SqmPathSource sqmPathSource && sqmPathSource.isGeneric() ? managedType.findConcreteGenericAttribute( attributeName ) : attribute; @@ -470,10 +470,10 @@ public SubGraphImplementor addTreatedSubgraph(ManagedType ty return (SubGraphImplementor) this; } else { - final Class javaType = type.getJavaType(); - final SubGraphImplementor castSubgraph = getTreatedSubgraphForPut( javaType ); + final var javaType = type.getJavaType(); + final var castSubgraph = getTreatedSubgraphForPut( javaType ); if ( castSubgraph == null ) { - final SubGraphImpl subgraph = new SubGraphImpl<>( (ManagedDomainType) type, true ); + final var subgraph = new SubGraphImpl<>( (ManagedDomainType) type, true ); treatedSubgraphs.put( javaType, subgraph ); return subgraph; } @@ -495,7 +495,7 @@ public Map, SubGraphImplementor> getTreatedSubgr @Override public String toString() { - final StringBuilder builder = new StringBuilder( "Graph[" ).append( managedType.getTypeName() ); + final var builder = new StringBuilder( "Graph[" ).append( managedType.getTypeName() ); if ( attributeNodes != null ) { builder.append( ", nodes=" ) .append( attributeNodes.values().stream() diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/EntityNameResolverSessionFactory.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/EntityNameResolverSessionFactory.java deleted file mode 100644 index 88f601c9d6d0..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/EntityNameResolverSessionFactory.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.graph.internal.parse; - -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.model.domain.EntityDomainType; - -/** - * @author Steve Ebersole - */ -public class EntityNameResolverSessionFactory implements EntityNameResolver { - private final SessionFactoryImplementor sessionFactory; - - public EntityNameResolverSessionFactory(SessionFactoryImplementor sessionFactory) { - this.sessionFactory = sessionFactory; - } - - @Override - public EntityDomainType resolveEntityName(String entityName) { - //noinspection unchecked - return (EntityDomainType) sessionFactory.getJpaMetamodel().findEntityType( entityName ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java index e02e5f06ee03..2f69953025c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java @@ -10,13 +10,14 @@ import org.hibernate.graph.GraphNode; import org.hibernate.graph.InvalidGraphException; import org.hibernate.graph.spi.AttributeNodeImplementor; +import org.hibernate.graph.spi.GraphParserEntityNameResolver; import org.hibernate.graph.spi.GraphImplementor; import org.hibernate.graph.spi.SubGraphImplementor; -import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; import static org.hibernate.graph.internal.GraphParserLogging.PARSING_LOGGER; +import static org.hibernate.internal.util.StringHelper.repeat; /** * Unified access to the Antlr parser for Hibernate's "graph language" @@ -24,13 +25,13 @@ * @author Steve Ebersole */ public class GraphParser extends GraphLanguageParserBaseVisitor> { - private final EntityNameResolver entityNameResolver; + private final GraphParserEntityNameResolver entityNameResolver; private final Stack> graphStack = new StandardStack<>(); private final Stack> attributeNodeStack = new StandardStack<>(); private final Stack graphSourceStack = new StandardStack<>(); - public GraphParser(EntityNameResolver entityNameResolver) { + public GraphParser(GraphParserEntityNameResolver entityNameResolver) { this.entityNameResolver = entityNameResolver; } @@ -38,10 +39,10 @@ public GraphParser(EntityNameResolver entityNameResolver) { * @apiNote It is important that this form only be used after the session-factory is fully * initialized, especially the {@linkplain SessionFactoryImplementor#getJpaMetamodel()} JPA metamodel}. * - * @see GraphParser#GraphParser(EntityNameResolver) + * @see GraphParser#GraphParser(GraphParserEntityNameResolver) */ public GraphParser(SessionFactoryImplementor sessionFactory) { - this( new EntityNameResolverSessionFactory( sessionFactory ) ); + this( sessionFactory.getJpaMetamodel()::findEntityType ); } public Stack> getGraphStack() { @@ -50,38 +51,14 @@ public Stack> getGraphStack() { @Override public AttributeNodeImplementor visitAttributeNode(GraphLanguageParser.AttributeNodeContext attributeNodeContext) { - final String attributeName = attributeNodeContext.attributePath().ATTR_NAME().getText(); + final var attributePathContext = attributeNodeContext.attributePath(); + final var attributeQualifierContext = attributePathContext.attributeQualifier(); - final SubGraphGenerator subGraphCreator; + final String attributeName = attributePathContext.ATTR_NAME().getText(); - if ( attributeNodeContext.attributePath().attributeQualifier() == null ) { - if ( PARSING_LOGGER.isTraceEnabled() ) { - PARSING_LOGGER.tracef( - "%s Start attribute : %s", - StringHelper.repeat( ">>", attributeNodeStack.depth() + 1 ), - attributeName - ); - } - - subGraphCreator = PathQualifierType.VALUE.getSubGraphCreator(); - } - else { - final String qualifierName = attributeNodeContext.attributePath().attributeQualifier().ATTR_NAME().getText(); - - if ( PARSING_LOGGER.isTraceEnabled() ) { - PARSING_LOGGER.tracef( - "%s Start qualified attribute : %s.%s", - StringHelper.repeat( ">>", attributeNodeStack.depth() + 1 ), - attributeName, - qualifierName - ); - } - - final PathQualifierType pathQualifierType = resolvePathQualifier( qualifierName ); - subGraphCreator = pathQualifierType.getSubGraphCreator(); - } + final var subGraphCreator = subGraphCreator( attributeQualifierContext, attributeName ); - final AttributeNodeImplementor attributeNode = resolveAttributeNode( attributeName ); + final var attributeNode = resolveAttributeNode( attributeName ); if ( attributeNodeContext.subGraph() != null ) { attributeNodeStack.push( attributeNode ); @@ -100,7 +77,7 @@ public AttributeNodeImplementor visitAttributeNode(GraphLanguageParser.At if ( PARSING_LOGGER.isTraceEnabled() ) { PARSING_LOGGER.tracef( "%s Finished attribute : %s", - StringHelper.repeat( "<<", attributeNodeStack.depth() + 1 ), + repeat( "<<", attributeNodeStack.depth() + 1 ), attributeName ); } @@ -108,11 +85,36 @@ public AttributeNodeImplementor visitAttributeNode(GraphLanguageParser.At return attributeNode; } + private SubGraphGenerator subGraphCreator(GraphLanguageParser.AttributeQualifierContext attributeQualifierContext, String attributeName) { + if ( attributeQualifierContext == null ) { + if ( PARSING_LOGGER.isTraceEnabled() ) { + PARSING_LOGGER.tracef( + "%s Start attribute : %s", + repeat( ">>", attributeNodeStack.depth() + 1 ), + attributeName + ); + } + return PathQualifierType.VALUE.getSubGraphCreator(); + } + else { + final String qualifierName = attributeQualifierContext.ATTR_NAME().getText(); + if ( PARSING_LOGGER.isTraceEnabled() ) { + PARSING_LOGGER.tracef( + "%s Start qualified attribute : %s.%s", + repeat( ">>", attributeNodeStack.depth() + 1 ), + attributeName, + qualifierName + ); + } + return resolvePathQualifier( qualifierName ).getSubGraphCreator(); + } + } + private AttributeNodeImplementor resolveAttributeNode(String attributeName) { - final GraphImplementor currentGraph = graphStack.getCurrent(); + final var currentGraph = graphStack.getCurrent(); assert currentGraph != null; - final AttributeNodeImplementor attributeNode = currentGraph.findOrCreateAttributeNode( attributeName ); + final var attributeNode = currentGraph.findOrCreateAttributeNode( attributeName ); assert attributeNode != null; return attributeNode; @@ -132,20 +134,22 @@ private PathQualifierType resolvePathQualifier(String qualifier) { @Override public SubGraphImplementor visitSubGraph(GraphLanguageParser.SubGraphContext subGraphContext) { - final String subTypeName = subGraphContext.typeIndicator() == null ? null : subGraphContext.typeIndicator().TYPE_NAME().getText(); + final String subTypeName = + subGraphContext.typeIndicator() == null ? null + : subGraphContext.typeIndicator().TYPE_NAME().getText(); if ( PARSING_LOGGER.isTraceEnabled() ) { PARSING_LOGGER.tracef( "%s Starting graph: %s", - StringHelper.repeat( ">>", attributeNodeStack.depth() + 2 ), + repeat( ">>", attributeNodeStack.depth() + 2 ), subTypeName ); } - final AttributeNodeImplementor attributeNode = attributeNodeStack.getCurrent(); - final SubGraphGenerator subGraphCreator = graphSourceStack.getCurrent(); + final var attributeNode = attributeNodeStack.getCurrent(); + final var subGraphCreator = graphSourceStack.getCurrent(); - final SubGraphImplementor subGraph = subGraphCreator.createSubGraph( + final var subGraph = subGraphCreator.createSubGraph( attributeNode, subTypeName, entityNameResolver @@ -163,7 +167,7 @@ public SubGraphImplementor visitSubGraph(GraphLanguageParser.SubGraphContext if ( PARSING_LOGGER.isTraceEnabled() ) { PARSING_LOGGER.tracef( "%s Finished graph : %s", - StringHelper.repeat( "<<", attributeNodeStack.depth() + 2 ), + repeat( "<<", attributeNodeStack.depth() + 2 ), subGraph.getGraphedType().getTypeName() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParsing.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParsing.java index 2c8be74ef601..514b6c3c7316 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParsing.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParsing.java @@ -6,12 +6,15 @@ import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; +import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.grammars.graph.GraphLanguageLexer; import org.hibernate.grammars.graph.GraphLanguageParser; +import org.hibernate.grammars.graph.GraphLanguageParser.GraphContext; import org.hibernate.graph.InvalidGraphException; import org.hibernate.graph.internal.RootGraphImpl; +import org.hibernate.graph.spi.GraphParserEntityNameResolver; import org.hibernate.graph.spi.GraphImplementor; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.metamodel.model.domain.EntityDomainType; @@ -22,126 +25,85 @@ * @author Steve Ebersole */ public class GraphParsing { + public static RootGraphImplementor parse( - Class entityClass, + EntityDomainType entityDomainType, String graphText, SessionFactoryImplementor sessionFactory) { if ( graphText == null ) { return null; } - final GraphLanguageLexer lexer = new GraphLanguageLexer( CharStreams.fromString( graphText ) ); - final GraphLanguageParser parser = new GraphLanguageParser( new CommonTokenStream( lexer ) ); - final GraphLanguageParser.GraphContext graphContext = parser.graph(); - + final var graphContext = parseText( graphText ); if ( graphContext.typeIndicator() != null ) { // todo : an alternative here would be to simply validate that the entity type // from the text matches the passed one... - throw new InvalidGraphException( "Expecting graph text to not include an entity name : " + graphText ); + throw new InvalidGraphException( "Expecting graph text to not include an entity name: " + graphText ); } - final EntityDomainType entityType = sessionFactory.getJpaMetamodel().entity( entityClass ); - return parse( entityType, graphContext.attributeList(), sessionFactory ); + return visit( entityDomainType, graphContext.attributeList(), sessionFactory ); } public static RootGraphImplementor parse( - EntityDomainType entityDomainType, + Class entityClass, String graphText, SessionFactoryImplementor sessionFactory) { - if ( graphText == null ) { - return null; - } - - final GraphLanguageLexer lexer = new GraphLanguageLexer( CharStreams.fromString( graphText ) ); - final GraphLanguageParser parser = new GraphLanguageParser( new CommonTokenStream( lexer ) ); - final GraphLanguageParser.GraphContext graphContext = parser.graph(); - - if ( graphContext.typeIndicator() != null ) { - // todo : an alternative here would be to simply validate that the entity type - // from the text matches the passed one... - throw new InvalidGraphException( "Expecting graph text to not include an entity name : " + graphText ); - } - - return parse( entityDomainType, graphContext.attributeList(), sessionFactory ); + return parse( sessionFactory.getJpaMetamodel().entity( entityClass ), + graphText, sessionFactory ); } - public static RootGraphImplementor parse( + public static RootGraphImplementor parse( String entityName, String graphText, SessionFactoryImplementor sessionFactory) { - if ( graphText == null ) { - return null; - } - - final GraphLanguageLexer lexer = new GraphLanguageLexer( CharStreams.fromString( graphText ) ); - final GraphLanguageParser parser = new GraphLanguageParser( new CommonTokenStream( lexer ) ); - final GraphLanguageParser.GraphContext graphContext = parser.graph(); - - if ( graphContext.typeIndicator() != null ) { - // todo : an alternative here would be to simply validate that the entity type - // from the text matches the passed one... - throw new InvalidGraphException( "Expecting graph text to not include an entity name : " + graphText ); - } - - //noinspection unchecked - final EntityDomainType entityType = (EntityDomainType) sessionFactory.getJpaMetamodel().entity( entityName ); - return parse( entityType, graphContext.attributeList(), sessionFactory ); + return parse( sessionFactory.getJpaMetamodel().entity( entityName ), + graphText, sessionFactory ); } - public static RootGraphImplementor parse( + public static RootGraphImplementor parse( String graphText, SessionFactoryImplementor sessionFactory) { if ( graphText == null ) { return null; } - final GraphLanguageLexer lexer = new GraphLanguageLexer( CharStreams.fromString( graphText ) ); - final GraphLanguageParser parser = new GraphLanguageParser( new CommonTokenStream( lexer ) ); - final GraphLanguageParser.GraphContext graphContext = parser.graph(); - + final var graphContext = parseText( graphText ); if ( graphContext.typeIndicator() == null ) { - throw new InvalidGraphException( "Expecting graph text to include an entity name : " + graphText ); + throw new InvalidGraphException( "Expecting graph text to include an entity name: " + graphText ); } final String entityName = graphContext.typeIndicator().TYPE_NAME().getText(); - - //noinspection unchecked - final EntityDomainType entityType = (EntityDomainType) sessionFactory.getJpaMetamodel().entity( entityName ); - return parse( entityType, graphContext.attributeList(), sessionFactory ); + final var entityType = sessionFactory.getJpaMetamodel().entity( entityName ); + return visit( entityType, graphContext.attributeList(), sessionFactory ); } - public static RootGraphImplementor parse( + public static RootGraphImplementor visit( EntityDomainType rootType, GraphLanguageParser.AttributeListContext attributeListContext, SessionFactoryImplementor sessionFactory) { - return parse( rootType, attributeListContext, new EntityNameResolverSessionFactory( sessionFactory ) ); + return visit( rootType, attributeListContext, sessionFactory.getJpaMetamodel()::findEntityType ); } - public static RootGraphImplementor parse( + public static RootGraphImplementor visit( EntityDomainType rootType, GraphLanguageParser.AttributeListContext attributeListContext, - EntityNameResolver entityNameResolver) { - return parse( null, rootType, attributeListContext, entityNameResolver ); + GraphParserEntityNameResolver entityNameResolver) { + return visit( null, rootType, attributeListContext, entityNameResolver ); } - public static RootGraphImplementor parse( + public static @NonNull GraphContext parseText(String graphText) { + final var lexer = new GraphLanguageLexer( CharStreams.fromString( graphText ) ); + final var parser = new GraphLanguageParser( new CommonTokenStream( lexer ) ); + return parser.graph(); + } + + public static RootGraphImplementor visit( @Nullable String name, EntityDomainType rootType, GraphLanguageParser.AttributeListContext attributeListContext, - EntityNameResolver entityNameResolver) { + GraphParserEntityNameResolver entityNameResolver) { final RootGraphImpl targetGraph = new RootGraphImpl<>( name, rootType ); - - final GraphParser visitor = new GraphParser( entityNameResolver ); - visitor.getGraphStack().push( targetGraph ); - try { - visitor.visitAttributeList( attributeListContext ); - } - finally { - visitor.getGraphStack().pop(); - - assert visitor.getGraphStack().isEmpty(); - } - + visitGraph( targetGraph, entityNameResolver, attributeListContext ); return targetGraph; } @@ -153,25 +115,28 @@ public static void parseInto( GraphImplementor targetGraph, CharSequence graphString, SessionFactoryImplementor sessionFactory) { - final GraphLanguageLexer lexer = new GraphLanguageLexer( CharStreams.fromString( graphString.toString() ) ); - final GraphLanguageParser parser = new GraphLanguageParser( new CommonTokenStream( lexer ) ); - final GraphLanguageParser.GraphContext graphContext = parser.graph(); - + final var graphContext = parseText( graphString.toString() ); if ( graphContext.typeIndicator() != null ) { // todo : throw an exception? Log warning? Ignore? // for now, ignore } + visitGraph( targetGraph, + sessionFactory.getJpaMetamodel()::findEntityType, + graphContext.attributeList() ); + } + private static void visitGraph( + GraphImplementor targetGraph, + GraphParserEntityNameResolver entityNameResolver, + GraphLanguageParser.AttributeListContext attributeList) { // Build an instance of this class as a visitor - final GraphParser visitor = new GraphParser( sessionFactory ); - + final var visitor = new GraphParser( entityNameResolver ); visitor.getGraphStack().push( targetGraph ); try { - visitor.visitAttributeList( graphContext.attributeList() ); + visitor.visitAttributeList( attributeList ); } finally { visitor.getGraphStack().pop(); - assert visitor.getGraphStack().isEmpty(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/PathQualifierType.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/PathQualifierType.java index 3687da5df37f..f9973dcf3e49 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/PathQualifierType.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/PathQualifierType.java @@ -5,7 +5,7 @@ package org.hibernate.graph.internal.parse; -import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.graph.spi.GraphParserEntityNameResolver; import org.hibernate.metamodel.model.domain.ManagedDomainType; /** @@ -23,12 +23,13 @@ public enum PathQualifierType { : attributeNode.addValueSubgraph().addTreatedSubgraph( managedType( subtypeName, entityNameResolver ) ) ); - private static ManagedDomainType managedType(String subtypeName, EntityNameResolver entityNameResolver) { - final EntityDomainType entityDomainType = entityNameResolver.resolveEntityName( subtypeName ); + private static ManagedDomainType managedType(String subtypeName, GraphParserEntityNameResolver resolver) { + final var entityDomainType = resolver.resolveEntityName( subtypeName ); if ( entityDomainType == null ) { throw new IllegalArgumentException( "Unknown managed type: " + subtypeName ); } - return entityDomainType; + //noinspection unchecked + return (ManagedDomainType) entityDomainType; } private final SubGraphGenerator subGraphCreator; diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/SubGraphGenerator.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/SubGraphGenerator.java index f575b2b9a624..295c33d93f64 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/SubGraphGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/SubGraphGenerator.java @@ -5,6 +5,7 @@ package org.hibernate.graph.internal.parse; import org.hibernate.graph.spi.AttributeNodeImplementor; +import org.hibernate.graph.spi.GraphParserEntityNameResolver; import org.hibernate.graph.spi.SubGraphImplementor; /** @@ -15,5 +16,5 @@ public interface SubGraphGenerator { SubGraphImplementor createSubGraph( AttributeNodeImplementor attributeNode, String subTypeName, - EntityNameResolver entityNameResolver); + GraphParserEntityNameResolver entityNameResolver); } diff --git a/hibernate-core/src/main/java/org/hibernate/graph/spi/GraphParserEntityClassResolver.java b/hibernate-core/src/main/java/org/hibernate/graph/spi/GraphParserEntityClassResolver.java new file mode 100644 index 000000000000..7210cddaae5f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/graph/spi/GraphParserEntityClassResolver.java @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.graph.spi; + +import org.hibernate.Incubating; +import org.hibernate.metamodel.model.domain.EntityDomainType; + +/** + * @author Gavin King + * + * @since 7.2 + */ +@Incubating +@FunctionalInterface +public interface GraphParserEntityClassResolver { + EntityDomainType resolveEntityClass(Class entityClass); +} diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/EntityNameResolver.java b/hibernate-core/src/main/java/org/hibernate/graph/spi/GraphParserEntityNameResolver.java similarity index 51% rename from hibernate-core/src/main/java/org/hibernate/graph/internal/parse/EntityNameResolver.java rename to hibernate-core/src/main/java/org/hibernate/graph/spi/GraphParserEntityNameResolver.java index 10073e2468b4..3734bd8b4086 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/EntityNameResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/spi/GraphParserEntityNameResolver.java @@ -2,14 +2,18 @@ * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ -package org.hibernate.graph.internal.parse; +package org.hibernate.graph.spi; +import org.hibernate.Incubating; import org.hibernate.metamodel.model.domain.EntityDomainType; /** * @author Steve Ebersole + * + * @since 7.2 */ +@Incubating @FunctionalInterface -public interface EntityNameResolver { - EntityDomainType resolveEntityName(String entityName); +public interface GraphParserEntityNameResolver { + EntityDomainType resolveEntityName(String entityName); } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/ExportableColumnHelper.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/ExportableColumnHelper.java index aa389af5c179..d275408a15e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/ExportableColumnHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/ExportableColumnHelper.java @@ -47,6 +47,11 @@ public List getColumns() { return List.of( column ); } + @Override + public boolean hasColumns() { + return true; + } + @Override public Type getType() { return type; diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/LegacyNamingStrategy.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/LegacyNamingStrategy.java index d4c22fb40caf..26bed7a155a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/LegacyNamingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/LegacyNamingStrategy.java @@ -79,7 +79,6 @@ public QualifiedName determineSequenceName( private String implicitSequenceName(Map configValues) { final String explicitSuffix = getString( CONFIG_SEQUENCE_PER_ENTITY_SUFFIX, configValues ); - if ( isNotEmpty( explicitSuffix ) ) { // an "implicit name suffix" was specified final String rootTableName = getString( TABLE, configValues ); @@ -102,7 +101,6 @@ public QualifiedName determineTableName( Map configValues, ServiceRegistry serviceRegistry) { final String implicitName = implicitTableName( configValues ); - return implicitName.contains( "." ) ? QualifiedNameParser.INSTANCE.parse( implicitName ) : new QualifiedTableName( diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/OptimizerFactory.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/OptimizerFactory.java index 3c55ec148adb..36409171a4eb 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/OptimizerFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/OptimizerFactory.java @@ -37,8 +37,8 @@ private static Optimizer createOptimizer(OptimizerDescriptor descriptor, Class configValues, ServiceRegistry serviceRegistry) { - final String rootTableName = getString( TABLE, configValues ); - final String implicitName = implicitSequenceName( rootTableName, configValues ); - return qualifiedSequenceName( catalogName, schemaName, serviceRegistry, implicitName ); - + return qualifiedSequenceName( catalogName, schemaName, serviceRegistry, + implicitSequenceName( getString( TABLE, configValues ), configValues ) ); } private static String implicitSequenceName(String rootTableName, Map configValues) { final String explicitSuffix = getString( CONFIG_SEQUENCE_PER_ENTITY_SUFFIX, configValues ); final String base = getString( IMPLICIT_NAME_BASE, configValues, rootTableName ); - - if ( isNotEmpty( explicitSuffix ) ) { + if ( isNotEmpty( explicitSuffix ) && isNotEmpty( base ) ) { // an "implicit name suffix" was specified - if ( isNotEmpty( base ) ) { - return isQuoted( base ) - ? "`" + unQuote( base ) + explicitSuffix + "`" - : base + explicitSuffix; - } - } - - final String annotationGeneratorName = getString( GENERATOR_NAME, configValues ); - if ( isNotEmpty( annotationGeneratorName ) ) { - return annotationGeneratorName; - } - else if ( isNotEmpty( base ) ) { return isQuoted( base ) - ? "`" + unQuote( base ) + DEF_SEQUENCE_SUFFIX + "`" - : base + DEF_SEQUENCE_SUFFIX; + ? "`" + unQuote( base ) + explicitSuffix + "`" + : base + explicitSuffix; } else { - throw new MappingException( "Unable to determine implicit sequence name; target table - " + rootTableName ); + final String annotationGeneratorName = getString( GENERATOR_NAME, configValues ); + if ( isNotEmpty( annotationGeneratorName ) ) { + return annotationGeneratorName; + } + else if ( isNotEmpty( base ) ) { + return isQuoted( base ) + ? "`" + unQuote( base ) + DEF_SEQUENCE_SUFFIX + "`" + : base + DEF_SEQUENCE_SUFFIX; + } + else { + throw new MappingException( "Unable to determine implicit sequence name for target table '" + rootTableName + "'" ); + } } } @@ -113,8 +109,8 @@ public QualifiedName determineTableName( Identifier schemaName, Map configValues, ServiceRegistry serviceRegistry) { - final String implicitName = implicitTableName( configValues ); - return qualifiedTableName( catalogName, schemaName, serviceRegistry, implicitName ); + return qualifiedTableName( catalogName, schemaName, serviceRegistry, + implicitTableName( configValues ) ); } private static String implicitTableName(Map configValues) { diff --git a/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidVersion7Strategy.java b/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidVersion7Strategy.java index 35ff129960b5..65170aba9361 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidVersion7Strategy.java +++ b/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidVersion7Strategy.java @@ -54,6 +54,13 @@ public long millis() { return lastTimestamp.toEpochMilli(); } + /** + * Sub-miliseconds part of timestamp (micro- and nanoseconds) mapped to 12 bit integral value. + * Calculated as nanos / 1000000 * 4096 + * + * @param timestamp + * @return + */ private static long nanos(Instant timestamp) { return (long) ((timestamp.getNano() % 1_000_000L) * 0.004096); } @@ -64,9 +71,17 @@ public State getNextState() { return new State( now, randomSequence() ); } else { - final long nextSequence = lastSequence + Holder.numberGenerator.nextLong( 0xFFFF_FFFFL ); - return nextSequence > MAX_RANDOM_SEQUENCE - ? new State( lastTimestamp.plusNanos( 250 ), randomSequence() ) + final long nextSequence = randomSequence(); + /* + * If next random sequence is less or equal to last one sub-millisecond part + * should be incremented to preserve monotonicity of generated UUIDs. + * To do this smallest number of nanoseconds that will always increase + * sub-millisecond part mapped to 12 bits is + * 1_000_000 (nanons per milli) / 4096 (12 bits) = 244.14... + * So 245 is used as smallest integer larger than this value. + */ + return lastSequence >= nextSequence + ? new State( lastTimestamp.plusNanos( 245 ), nextSequence ) : new State( lastTimestamp, nextSequence ); } } @@ -77,7 +92,7 @@ private boolean lastTimestampEarlierThan(Instant now) { } private static long randomSequence() { - return Holder.numberGenerator.nextLong( MAX_RANDOM_SEQUENCE ); + return Holder.numberGenerator.nextLong( MAX_RANDOM_SEQUENCE + 1 ); } } @@ -118,7 +133,7 @@ public UUID generateUuid(final SharedSessionContractImplementor session) { | state.nanos() & 0xFFFL, // LSB bits 0-1 - variant = 4 0x8000_0000_0000_0000L - // LSB bits 2-15 - pseudorandom counter + // LSB bits 2-63 - pseudorandom counter | state.lastSequence ); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index 2474372bc614..907676485197 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -260,7 +260,8 @@ public SharedStatelessSessionBuilder statelessWithOptions() { @Override protected StatelessSessionImplementor createStatelessSession() { return new StatelessSessionImpl( factory, - new SessionCreationOptionsAdaptor( factory, this, AbstractSharedSessionContract.this ) ); + new SessionCreationOptionsAdaptor( factory, this, + AbstractSharedSessionContract.this ) ); } }; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index 1213a3bb9bb0..13ae7684593b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -234,6 +234,10 @@ void missingArguments( @Message(value = "Unable to mark for rollback on TransientObjectException: ", id = 338) void unableToMarkForRollbackOnTransientObjectException(@Cause Exception e); + @LogMessage(level = ERROR) + @Message(value = "Unable to mark for rollback on DetachedObjectException: ", id = 339) + void unableToMarkForRollbackOnDetachedObjectException(@Cause Exception e); + @LogMessage(level = ERROR) @Message(value = "Could not release a cache lock: %s", id = 353) void unableToReleaseCacheLock(CacheException ce); @@ -270,10 +274,6 @@ void missingArguments( @Message(value = "Warnings creating temp table: %s", id = 413) void warningsCreatingTempTable(SQLWarning warning); - @LogMessage(level = WARN) - @Message(value = "Write locks via update not supported for non-versioned entities [%s]", id = 416) - void writeLocksNotSupported(String entityName); - @LogMessage(level = WARN) @Message( value = """ diff --git a/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java index 40fbce66c837..891014fef873 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java @@ -8,6 +8,7 @@ import java.sql.SQLException; import org.hibernate.AssertionFailure; +import org.hibernate.DetachedObjectException; import org.hibernate.HibernateException; import org.hibernate.JDBCException; import org.hibernate.LockOptions; @@ -141,6 +142,16 @@ else if ( exception instanceof TransientObjectException ) { //Spec 3.2.3 Synchronization rules return new IllegalStateException( exception ); } + else if ( exception instanceof DetachedObjectException ) { + try { + session.markForRollbackOnly(); + } + catch (Exception ne) { + //we do not want the subsequent exception to swallow the original one + CORE_LOGGER.unableToMarkForRollbackOnDetachedObjectException( ne ); + } + throw new IllegalArgumentException( exception ); + } else if ( exception instanceof TransactionSerializationException ) { final var converted = new RollbackException( exception.getMessage(), exception ); rollbackIfNecessary( converted ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/MultiIdentifierLoadAccessImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/MultiIdentifierLoadAccessImpl.java index e2fdedb0f9d1..10b05f7fcc7f 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/MultiIdentifierLoadAccessImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/MultiIdentifierLoadAccessImpl.java @@ -7,11 +7,15 @@ import jakarta.persistence.EntityGraph; import jakarta.persistence.PessimisticLockScope; import jakarta.persistence.Timeout; +import org.hibernate.BatchSize; import org.hibernate.CacheMode; +import org.hibernate.KeyType; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.MultiIdentifierLoadAccess; +import org.hibernate.NaturalIdSynchronization; import org.hibernate.OrderingMode; +import org.hibernate.ReadOnlyMode; import org.hibernate.RemovalsMode; import org.hibernate.SessionCheckMode; import org.hibernate.UnknownProfileException; @@ -19,19 +23,23 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.internal.find.FindMultipleByKeyOperation; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; +import org.hibernate.loader.internal.LoadAccessContext; import org.hibernate.persister.entity.EntityPersister; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.function.Supplier; import static java.util.Collections.emptyList; -/** - * @author Steve Ebersole - */ +/// Implementation of MultiIdentifierLoadAccess. +/// +/// @author Steve Ebersole +/// +/// @deprecated Use [FindMultipleByKeyOperation] instead. +@Deprecated class MultiIdentifierLoadAccessImpl implements MultiIdentifierLoadAccess, MultiIdLoadOptions { private final SharedSessionContractImplementor session; private final EntityPersister entityPersister; @@ -43,7 +51,7 @@ class MultiIdentifierLoadAccessImpl implements MultiIdentifierLoadAccess, private RootGraphImplementor rootGraph; private GraphSemantic graphSemantic; - private Integer batchSize; + private BatchSize batchSize; private SessionCheckMode sessionCheckMode = SessionCheckMode.DISABLED; private RemovalsMode removalsMode = RemovalsMode.REPLACE; protected OrderingMode orderingMode = OrderingMode.ORDERED; @@ -107,12 +115,12 @@ public MultiIdentifierLoadAccess with(EntityGraph graph, GraphSemantic sem @Override public Integer getBatchSize() { - return batchSize; + return batchSize.batchSize(); } @Override public MultiIdentifierLoadAccess withBatchSize(int batchSize) { - this.batchSize = batchSize < 1 ? null : batchSize; + this.batchSize = batchSize < 1 ? null : new BatchSize( batchSize ); return this; } @@ -164,45 +172,25 @@ public Boolean getReadOnly(SessionImplementor session) { @Override @SuppressWarnings( "unchecked" ) public List multiLoad(K... ids) { - return perform( () -> (List) entityPersister.multiLoad( ids, session, this ) ); - } - - public List perform(Supplier> executor) { - final var sessionCacheMode = session.getCacheMode(); - boolean cacheModeChanged = false; - if ( cacheMode != null ) { - // naive check for now... - // todo : account for "conceptually equal" - if ( cacheMode != sessionCacheMode ) { - session.setCacheMode( cacheMode ); - cacheModeChanged = true; - } - } + return buildOperation().performFind( List.of( ids ), graphSemantic, rootGraph, (LoadAccessContext) session ); + } - try { - final var influencers = session.getLoadQueryInfluencers(); - final var fetchProfiles = - influencers.adjustFetchProfiles( disabledFetchProfiles, enabledFetchProfiles ); - final var effectiveEntityGraph = - rootGraph == null - ? null - : influencers.applyEntityGraph( rootGraph, graphSemantic ); - try { - return executor.get(); - } - finally { - if ( effectiveEntityGraph != null ) { - effectiveEntityGraph.clear(); - } - influencers.setEnabledFetchProfileNames( fetchProfiles ); - } - } - finally { - if ( cacheModeChanged ) { - // change it back - session.setCacheMode( sessionCacheMode ); - } - } + private FindMultipleByKeyOperation buildOperation() { + return new FindMultipleByKeyOperation( + entityPersister, + KeyType.IDENTIFIER, + batchSize, + sessionCheckMode, + removalsMode, + orderingMode, + cacheMode, + lockOptions, + readOnly == Boolean.TRUE ? ReadOnlyMode.READ_ONLY : ReadOnlyMode.READ_WRITE, + enabledFetchProfiles, + disabledFetchProfiles, + // irrelevant for load-by-id + NaturalIdSynchronization.DISABLED + ); } @Override @@ -210,7 +198,7 @@ public List perform(Supplier> executor) { public List multiLoad(List ids) { return ids.isEmpty() ? emptyList() - : perform( () -> (List) entityPersister.multiLoad( ids.toArray(), session, this ) ); + : buildOperation().performFind( (List)ids, graphSemantic, rootGraph, (LoadAccessContext) session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdHelper.java index b607a9f59997..b9cde2b804f3 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdHelper.java @@ -43,7 +43,7 @@ public static void performAnyNeededCrossReferenceSynchronizations( // first check if synchronization (this process) was disabled if ( synchronizationEnabled // only mutable natural-ids need this processing - && entityMappingType.getNaturalIdMapping().isMutable() + && entityMappingType.requireNaturalIdMapping().isMutable() // skip synchronization when not in a transaction && session.isTransactionInProgress() ) { final var persister = entityMappingType.getEntityPersister(); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java b/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java index bb84eb30e4e6..ce8b2adca103 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java @@ -4,30 +4,38 @@ */ package org.hibernate.internal; -import java.util.List; - import jakarta.persistence.EntityGraph; - import jakarta.persistence.PessimisticLockScope; import jakarta.persistence.Timeout; +import org.hibernate.BatchSize; import org.hibernate.CacheMode; +import org.hibernate.KeyType; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.Locking; import org.hibernate.NaturalIdMultiLoadAccess; +import org.hibernate.NaturalIdSynchronization; import org.hibernate.OrderingMode; +import org.hibernate.ReadOnlyMode; import org.hibernate.RemovalsMode; +import org.hibernate.SessionCheckMode; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.internal.find.FindMultipleByKeyOperation; import org.hibernate.loader.ast.spi.MultiNaturalIdLoadOptions; +import org.hibernate.loader.internal.LoadAccessContext; import org.hibernate.persister.entity.EntityPersister; -import static org.hibernate.internal.NaturalIdHelper.performAnyNeededCrossReferenceSynchronizations; +import java.util.List; -/** - * @author Steve Ebersole - */ -class NaturalIdMultiLoadAccessStandard implements NaturalIdMultiLoadAccess, MultiNaturalIdLoadOptions { +/// Implementation of NaturalIdMultiLoadAccess. +/// +/// @deprecated Use [FindMultipleByKeyOperation] instead. +/// +/// @author Steve Ebersole +@Deprecated +public class NaturalIdMultiLoadAccessStandard implements NaturalIdMultiLoadAccess, MultiNaturalIdLoadOptions { private final EntityPersister entityDescriptor; private final SharedSessionContractImplementor session; @@ -41,7 +49,7 @@ class NaturalIdMultiLoadAccessStandard implements NaturalIdMultiLoadAccess private RemovalsMode removalsMode = RemovalsMode.REPLACE; private OrderingMode orderingMode = OrderingMode.ORDERED; - NaturalIdMultiLoadAccessStandard(EntityPersister entityDescriptor, SharedSessionContractImplementor session) { + public NaturalIdMultiLoadAccessStandard(EntityPersister entityDescriptor, SharedSessionContractImplementor session) { this.entityDescriptor = entityDescriptor; this.session = session; } @@ -56,6 +64,13 @@ public NaturalIdMultiLoadAccess with(LockMode lockMode, PessimisticLockScope return this; } + public void with(Locking.Scope scope) { + if ( lockOptions == null ) { + lockOptions = new LockOptions(); + } + lockOptions.setScope( scope ); + } + @Override public NaturalIdMultiLoadAccess with(Timeout timeout) { if ( lockOptions == null ) { @@ -96,72 +111,47 @@ public NaturalIdMultiLoadAccess enableReturnOfDeletedEntities(boolean enabled return this; } + public void with(RemovalsMode removalsMode) { + this.removalsMode = removalsMode; + } + @Override public NaturalIdMultiLoadAccess enableOrderedReturn(boolean enabled) { this.orderingMode = enabled ? OrderingMode.ORDERED : OrderingMode.UNORDERED; return this; } + public void with(OrderingMode orderingMode) { + this.orderingMode = orderingMode; + } + @Override - @SuppressWarnings( "unchecked" ) public List multiLoad(Object... ids) { - performAnyNeededCrossReferenceSynchronizations( true, entityDescriptor, session ); - - final CacheMode sessionCacheMode = session.getCacheMode(); - boolean cacheModeChanged = false; - - if ( cacheMode != null ) { - // naive check for now... - // todo : account for "conceptually equal" - if ( cacheMode != sessionCacheMode ) { - session.setCacheMode( cacheMode ); - cacheModeChanged = true; - } - } - - final var loadQueryInfluencers = session.getLoadQueryInfluencers(); - - try { - final var effectiveEntityGraph = loadQueryInfluencers.getEffectiveEntityGraph(); - final var initialGraphSemantic = effectiveEntityGraph.getSemantic(); - final var initialGraph = effectiveEntityGraph.getGraph(); - final boolean hadInitialGraph = initialGraphSemantic != null; - - if ( graphSemantic != null ) { - if ( rootGraph == null ) { - throw new IllegalArgumentException( "Graph semantic specified, but no RootGraph was supplied" ); - } - effectiveEntityGraph.applyGraph( rootGraph, graphSemantic ); - } - - try { - return (List) - entityDescriptor.getMultiNaturalIdLoader() - .multiLoad( ids, this, session ); - } - finally { - if ( graphSemantic != null ) { - if ( hadInitialGraph ) { - effectiveEntityGraph.applyGraph( initialGraph, initialGraphSemantic ); - } - else { - effectiveEntityGraph.clear(); - } - } - } - } - finally { - if ( cacheModeChanged ) { - // change it back - session.setCacheMode( sessionCacheMode ); - } - } - + return buildOperation() + .performFind( List.of( ids ), graphSemantic, rootGraph, (LoadAccessContext) session ); } @Override public List multiLoad(List ids) { - return multiLoad( ids.toArray( new Object[ 0 ] ) ); + return buildOperation() + .performFind( ids, graphSemantic, rootGraph, (LoadAccessContext) session ); + } + + private FindMultipleByKeyOperation buildOperation() { + return new FindMultipleByKeyOperation( + entityDescriptor, + KeyType.NATURAL, + batchSize == null ? null : new BatchSize( batchSize ), + SessionCheckMode.ENABLED, + removalsMode, + orderingMode, + cacheMode, + lockOptions, + session.isDefaultReadOnly() ? ReadOnlyMode.READ_ONLY : ReadOnlyMode.READ_WRITE, + null, + null, + NaturalIdSynchronization.ENABLED + ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index a37ec581e42c..24eaf1848cb0 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -41,8 +41,9 @@ import org.hibernate.event.spi.*; import org.hibernate.event.spi.LoadEventListener.LoadType; import org.hibernate.graph.GraphSemantic; -import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.internal.find.FindByKeyOperation; +import org.hibernate.internal.find.FindMultipleByKeyOperation; import org.hibernate.internal.util.ExceptionHelper; import org.hibernate.jpa.internal.LegacySpecHelper; import org.hibernate.jpa.internal.util.ConfigurationHelper; @@ -86,7 +87,6 @@ import static java.lang.Boolean.parseBoolean; import static java.lang.Integer.parseInt; import static java.lang.System.currentTimeMillis; -import static java.util.Collections.unmodifiableMap; import static org.hibernate.CacheMode.fromJpaModes; import static org.hibernate.Timeouts.WAIT_FOREVER_MILLI; import static org.hibernate.cfg.AvailableSettings.CRITERIA_COPY_TREE; @@ -158,8 +158,8 @@ public class SessionImpl implements Serializable, SharedSessionContractImplementor, JdbcSessionOwner, SessionImplementor, EventSource, TransactionCoordinatorBuilder.Options, WrapperOptions, LoadAccessContext { - // Defaults to null which means the properties are the default - // as defined in FastSessionServices#defaultSessionProperties + // Defaults to null, meaning the properties + // are the default properties of the factory. private Map properties; private transient ActionQueue actionQueue; @@ -191,8 +191,6 @@ public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { actionQueue = createActionQueue(); eventListenerGroups = factory.getEventListenerGroups(); - flushMode = options.getInitialSessionFlushMode(); - autoClear = options.shouldAutoClear(); autoClose = options.shouldAutoClose(); @@ -202,13 +200,10 @@ public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { loadQueryInfluencers = new LoadQueryInfluencers( factory, options ); - // NOTE : pulse() already handles auto-join-ability correctly + // NOTE: pulse() already handles auto-join-ability correctly getTransactionCoordinator().pulse(); - // do not override explicitly set flush mode ( SessionBuilder#flushMode() ) - if ( getHibernateFlushMode() == null ) { - setHibernateFlushMode( getInitialFlushMode() ); - } + flushMode = getInitialFlushMode( options ); setUpMultitenancy( factory, loadQueryInfluencers ); @@ -247,10 +242,16 @@ private static void setUpTransactionCompletionProcesses( } } - private FlushMode getInitialFlushMode() { - return properties == null - ? getSessionFactoryOptions().getInitialSessionFlushMode() - : ConfigurationHelper.getFlushMode( getSessionProperty( HINT_FLUSH_MODE ), FlushMode.AUTO ); + private FlushMode getInitialFlushMode(SessionCreationOptions options) { + final var initialSessionFlushMode = options.getInitialSessionFlushMode(); + if ( initialSessionFlushMode != null ) { + return initialSessionFlushMode; + } + else { + return properties == null + ? getSessionFactoryOptions().getInitialSessionFlushMode() + : ConfigurationHelper.getFlushMode( properties.get( HINT_FLUSH_MODE ), FlushMode.AUTO ); + } } protected PersistenceContext createPersistenceContext(SessionCreationOptions options) { @@ -351,7 +352,6 @@ public void clear() { private void internalClear() { persistenceContext.clear(); actionQueue.clear(); - eventListenerGroups.eventListenerGroup_CLEAR .fireLazyEventOnEachListener( this::createClearEvent, ClearEventListener::onClear ); } @@ -392,7 +392,7 @@ public void closeWithoutOpenChecks() { else { // In the JPA bootstrap, if the session is closed // before the transaction commits, we just mark the - // session as closed, and set waitingForAutoClose. + // session as closed and set waitingForAutoClose. // This method will be called a second time from // afterTransactionCompletion when the transaction // commits, and the session will be closed for real. @@ -405,10 +405,12 @@ public void closeWithoutOpenChecks() { } } finally { - // E.g. when we are in the JTA context the session can get closed while the transaction is still active - // and JTA will call the AfterCompletion itself. Hence, we don't want to clear out the action queue callbacks at this point: - if ( !getTransactionCoordinator().isTransactionActive() && actionQueue.hasAfterTransactionActions() ) { - SESSION_LOGGER.warn( "Closing session with unprocessed clean up bulk operations, forcing their execution" ); + // E.g. When we are in the JTA context, the session can get closed while the + // transaction is still active and JTA will call the AfterCompletion itself. + // Hence, we don't want to clear out the action queue callbacks at this point: + if ( !getTransactionCoordinator().isTransactionActive() + && actionQueue.hasAfterTransactionActions() ) { + SESSION_LOGGER.closingSessionWithUnprocessedBulkOperations(); actionQueue.executePendingBulkOperationCleanUpActions(); } final var statistics = getSessionFactory().getStatistics(); @@ -489,8 +491,8 @@ else if ( isClosed() ) { return false; } else { - // JPA technically requires that this be a PersistentUnityTransactionType#JTA to work, - // but we do not assert that here: + // JPA requires PersistentUnitTransactionType.JTA, + // for this, but we do not assert that here: return isAutoCloseSessionEnabled(); // && getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta(); } @@ -557,7 +559,9 @@ public Object getEntityUsingInterceptor(EntityKey key) { // logically, is PersistentContext the "thing" to which an interceptor gets attached? final Object result = persistenceContext.getEntity( key ); if ( result == null ) { - final Object newObject = getInterceptor().getEntity( key.getEntityName(), key.getIdentifier() ); + final Object newObject = + getInterceptor() + .getEntity( key.getEntityName(), key.getIdentifier() ); if ( newObject != null ) { lock( newObject, LockMode.NONE ); } @@ -624,7 +628,6 @@ public void lock(Object object, LockMode lockMode, LockOption... lockOptions) { private void fireLock(final LockEvent lockEvent) { checkOpen(); - checkEntityManaged( lockEvent.getEntityName(), lockEvent.getObject() ); try { pulseTransactionCoordinator(); checkTransactionNeededForLock( lockEvent.getLockMode() ); @@ -642,7 +645,11 @@ private void fireLock(final LockEvent lockEvent) { private void convertIfJpaBootstrap(RuntimeException exception, LockOptions lockOptions) { if ( !isJpaBootstrap() && exception instanceof HibernateException ) { - throw exception; + throw exception instanceof DetachedObjectException + // convert to IllegalArgumentException for backward compatibility + // TODO: drop this conversion in Hibernate 8 + ? new IllegalArgumentException( exception ) + : exception; } else if ( exception instanceof MappingException ) { // I believe this is now obsolete everywhere we do it, @@ -870,8 +877,8 @@ public void removeOrphanBeforeUpdates(String entityName, Object child) { private void logRemoveOrphanBeforeUpdates(String timing, String entityName, Object entity) { if ( SESSION_LOGGER.isTraceEnabled() ) { final var entityEntry = persistenceContext.getEntry( entity ); - final String entityInfo = entityEntry == null ? entityName : infoString( entityName, entityEntry.getId() ); - SESSION_LOGGER.removeOrphanBeforeUpdates( timing, entityInfo ); + SESSION_LOGGER.removeOrphanBeforeUpdates( timing, + entityEntry == null ? entityName : infoString( entityName, entityEntry.getId() ) ); } } @@ -928,80 +935,46 @@ public void load(Object object, Object id) { fireLoad( new LoadEvent( id, object, this, getReadOnlyFromLoadQueryInfluencers() ), LoadEventListener.RELOAD ); } - private void setMultiIdentifierLoadAccessOptions(FindOption[] options, MultiIdentifierLoadAccess loadAccess) { - CacheStoreMode storeMode = getCacheStoreMode(); - CacheRetrieveMode retrieveMode = getCacheRetrieveMode(); - LockOptions lockOptions = copySessionLockOptions(); - int batchSize = -1; - for ( FindOption option : options ) { - if ( option instanceof CacheStoreMode cacheStoreMode ) { - storeMode = cacheStoreMode; - } - else if ( option instanceof CacheRetrieveMode cacheRetrieveMode ) { - retrieveMode = cacheRetrieveMode; - } - else if ( option instanceof CacheMode cacheMode ) { - storeMode = cacheMode.getJpaStoreMode(); - retrieveMode = cacheMode.getJpaRetrieveMode(); - } - else if ( option instanceof LockModeType lockModeType ) { - lockOptions.setLockMode( LockModeTypeHelper.getLockMode( lockModeType ) ); - } - else if ( option instanceof LockMode lockMode ) { - lockOptions.setLockMode( lockMode ); - } - else if ( option instanceof LockOptions lockOpts ) { - lockOptions = lockOpts; - } - else if ( option instanceof PessimisticLockScope pessimisticLockScope ) { - lockOptions.setLockScope( pessimisticLockScope ); - } - else if ( option instanceof Timeout timeout ) { - lockOptions.setTimeOut( timeout.milliseconds() ); - } - else if ( option instanceof EnabledFetchProfile enabledFetchProfile ) { - loadAccess.enableFetchProfile( enabledFetchProfile.profileName() ); - } - else if ( option instanceof ReadOnlyMode ) { - loadAccess.withReadOnly( option == ReadOnlyMode.READ_ONLY ); - } - else if ( option instanceof BatchSize batchSizeOption ) { - batchSize = batchSizeOption.batchSize(); - } - else if ( option instanceof SessionCheckMode ) { - loadAccess.enableSessionCheck( option == SessionCheckMode.ENABLED ); - } - else if ( option instanceof OrderingMode ) { - loadAccess.enableOrderedReturn( option == OrderingMode.ORDERED ); - } - else if ( option instanceof RemovalsMode ) { - loadAccess.enableReturnOfDeletedEntities( option == RemovalsMode.INCLUDE ); - } - } - loadAccess.with( lockOptions ) - .with( interpretCacheMode( storeMode, retrieveMode ) ) - .withBatchSize( batchSize ); + @Override + public List findMultiple(Class entityType, List keys, FindOption... options) { + //noinspection unchecked + return findMultiple( + requireEntityPersister( entityType ), + loadQueryInfluencers.getEffectiveEntityGraph().getSemantic(), + (RootGraphImplementor) loadQueryInfluencers.getEffectiveEntityGraph().getGraph(), + (List) keys, + options + ); } - @Override - public List findMultiple(Class entityType, List ids, FindOption... options) { - final var loadAccess = byMultipleIds( entityType ); - setMultiIdentifierLoadAccessOptions( options, loadAccess ); - return loadAccess.multiLoad( ids ); + private List findMultiple( + EntityPersister entityDescriptor, + GraphSemantic graphSemantic, + RootGraphImplementor rootGraph, + List keys, + FindOption... options) { + final var operation = new FindMultipleByKeyOperation( + entityDescriptor, + lockOptions, + getCacheMode(), + isDefaultReadOnly(), + getFactory(), + options + ); + return operation.performFind( keys, graphSemantic, rootGraph, this ); } @Override - public List findMultiple(EntityGraph entityGraph, List ids, FindOption... options) { - final var rootGraph = (RootGraph) entityGraph; + public List findMultiple(EntityGraph entityGraph, List keys, FindOption... options) { + final var rootGraph = (RootGraphImplementor) entityGraph; final var type = rootGraph.getGraphedType(); - final MultiIdentifierLoadAccess loadAccess = - switch ( type.getRepresentationMode() ) { - case MAP -> byMultipleIds( type.getTypeName() ); - case POJO -> byMultipleIds( type.getJavaType() ); - }; - loadAccess.withLoadGraph( rootGraph ); - setMultiIdentifierLoadAccessOptions( options, loadAccess ); - return loadAccess.multiLoad( ids ); + final var entityDescriptor = switch ( type.getRepresentationMode() ) { + case POJO -> requireEntityPersister( type.getJavaType() ); + case MAP -> requireEntityPersister( type.getTypeName() ); + }; + + //noinspection unchecked + return findMultiple( entityDescriptor, GraphSemantic.LOAD, rootGraph, (List) keys, options ); } @Override @@ -1128,6 +1101,7 @@ protected LoadEvent makeLoadEvent(String entityName, Object id, Boolean readOnly event.setReadOnly( readOnly ); event.setLockOptions( lockOptions ); event.setAssociationFetch( false ); + event.validate(); return event; } } @@ -1308,7 +1282,6 @@ public void refresh(String entityName, Object object, RefreshContext refreshedAl private void fireRefresh(final RefreshEvent refreshEvent) { checkOpen(); - checkEntityManaged( refreshEvent.getEntityName(), refreshEvent.getObject() ); try { pulseTransactionCoordinator(); checkTransactionNeededForLock( refreshEvent.getLockMode() ); @@ -1327,7 +1300,6 @@ private void fireRefresh(final RefreshEvent refreshEvent) { private void fireRefresh(final RefreshContext refreshedAlready, final RefreshEvent refreshEvent) { // called from cascades checkOpenOrWaitingForAutoClose(); - checkEntityManaged( refreshEvent.getEntityName(), refreshEvent.getObject() ); try { pulseTransactionCoordinator(); eventListenerGroups.eventListenerGroup_REFRESH @@ -1339,12 +1311,6 @@ private void fireRefresh(final RefreshContext refreshedAlready, final RefreshEve } } - private void checkEntityManaged(String entityName, Object entity) { - if ( !isManaged( entity ) ) { - throw new IllegalArgumentException( "Given entity is not associated with the persistence context" ); - } - } - @Override public boolean isManaged(Object entity) { try { @@ -1552,7 +1518,8 @@ public void forceFlush(EntityEntry entityEntry) { @Override public void forceFlush(EntityKey key) { if ( SESSION_LOGGER.isTraceEnabled() ) { - SESSION_LOGGER.flushingToForceDeletion( infoString( key.getPersister(), key.getIdentifier(), getFactory() ) ); + SESSION_LOGGER.flushingToForceDeletion( + infoString( key.getPersister(), key.getIdentifier(), getFactory() ) ); } if ( persistenceContext.getCascadeLevel() > 0 ) { @@ -2123,7 +2090,7 @@ private boolean isTransactionFlushable() { return true; } else { - final TransactionStatus status = currentTransaction.getStatus(); + final var status = currentTransaction.getStatus(); return status == TransactionStatus.ACTIVE || status == TransactionStatus.COMMITTING; } @@ -2189,13 +2156,13 @@ private T find(Class entityClass, Object primaryKey, LockOptions lockOpti .load( primaryKey ); } catch ( FetchNotFoundException e ) { - // This may happen if the entity has an associations mapped with + // This may happen if the entity has an association mapped with // @NotFound(action = NotFoundAction.EXCEPTION) and this associated // entity is not found throw e; } catch ( EntityFilterException e ) { - // This may happen if the entity has an associations which is + // This may happen if the entity has an association which is // filtered by a FilterDef and this associated entity is not found throw e; } @@ -2251,81 +2218,24 @@ protected static void logIgnoringEntityNotFound(Class entityClass, Object } } - private void setLoadAccessOptions(FindOption[] options, IdentifierLoadAccessImpl loadAccess) { - CacheStoreMode storeMode = getCacheStoreMode(); - CacheRetrieveMode retrieveMode = getCacheRetrieveMode(); - LockOptions lockOptions = copySessionLockOptions(); - for ( FindOption option : options ) { - if ( option instanceof CacheStoreMode cacheStoreMode ) { - storeMode = cacheStoreMode; - } - else if ( option instanceof CacheRetrieveMode cacheRetrieveMode ) { - retrieveMode = cacheRetrieveMode; - } - else if ( option instanceof CacheMode cacheMode ) { - storeMode = cacheMode.getJpaStoreMode(); - retrieveMode = cacheMode.getJpaRetrieveMode(); - } - else if ( option instanceof LockModeType lockModeType ) { - lockOptions.setLockMode( LockModeTypeHelper.getLockMode( lockModeType ) ); - } - else if ( option instanceof LockMode lockMode ) { - lockOptions.setLockMode( lockMode ); - } - else if ( option instanceof LockOptions lockOpts ) { - lockOptions = lockOpts; - } - else if ( option instanceof Locking.Scope lockScope ) { - lockOptions.setScope( lockScope ); - } - else if ( option instanceof PessimisticLockScope pessimisticLockScope ) { - lockOptions.setScope( Locking.Scope.fromJpaScope( pessimisticLockScope ) ); - } - else if ( option instanceof Locking.FollowOn followOn ) { - lockOptions.setFollowOnStrategy( followOn ); - } - else if ( option instanceof Timeout timeout ) { - lockOptions.setTimeout( timeout ); - } - else if ( option instanceof EnabledFetchProfile enabledFetchProfile ) { - loadAccess.enableFetchProfile( enabledFetchProfile.profileName() ); - } - else if ( option instanceof ReadOnlyMode ) { - loadAccess.withReadOnly( option == ReadOnlyMode.READ_ONLY ); - } - else if ( option instanceof FindMultipleOption findMultipleOption ) { - throw new IllegalArgumentException( "Option '" + findMultipleOption + "' can only be used in 'findMultiple()'" ); - } - } - if ( lockOptions.getLockMode().isPessimistic() ) { - if ( lockOptions.getTimeOut() == WAIT_FOREVER_MILLI ) { - final Object factoryHint = getFactory().getProperties().get( HINT_SPEC_LOCK_TIMEOUT ); - if ( factoryHint != null ) { - lockOptions.setTimeOut( Timeouts.fromHint( factoryHint ) ); - } - } - } - loadAccess.with( lockOptions ).with( interpretCacheMode( storeMode, retrieveMode ) ); - } - @Override - public T find(Class entityClass, Object primaryKey, FindOption... options) { - final IdentifierLoadAccessImpl loadAccess = byId( entityClass ); - setLoadAccessOptions( options, loadAccess ); - return loadAccess.load( primaryKey ); + public T find(Class entityClass, Object key, FindOption... options) { + //noinspection unchecked + return (T) byKey( requireEntityPersister( entityClass ), options ).performFind( key, this ); } @Override - public T find(EntityGraph entityGraph, Object primaryKey, FindOption... options) { - final var graph = (RootGraph) entityGraph; + public T find(EntityGraph entityGraph, Object key, FindOption... options) { + final var graph = (RootGraphImplementor) entityGraph; final var type = graph.getGraphedType(); - final IdentifierLoadAccessImpl loadAccess = - switch ( type.getRepresentationMode() ) { - case MAP -> byId( type.getTypeName() ); - case POJO -> byId( type.getJavaType() ); - }; - setLoadAccessOptions( options, loadAccess ); - return loadAccess.withLoadGraph( graph ).load( primaryKey ); + + final EntityPersister entityDescriptor = switch ( type.getRepresentationMode() ) { + case POJO -> requireEntityPersister( type.getJavaType() ); + case MAP -> requireEntityPersister( type.getTypeName() ); + }; + + //noinspection unchecked + return (T) byKey( entityDescriptor, GraphSemantic.LOAD, graph, options ).performFind( key, this ); } // Hibernate Reactive may need to use this @@ -2388,16 +2298,34 @@ private void checkTransactionNeededForUpdateOperation() { } @Override - public Object find(String entityName, Object primaryKey) { - final IdentifierLoadAccessImpl loadAccess = byId( entityName ); - return loadAccess.load( primaryKey ); + public Object find(String entityName, Object key) { + return byKey( requireEntityPersister( entityName ) ).performFind( key, this ); } @Override - public Object find(String entityName, Object primaryKey, FindOption... options) { - final IdentifierLoadAccessImpl loadAccess = byId( entityName ); - setLoadAccessOptions( options, loadAccess ); - return loadAccess.load( primaryKey ); + public Object find(String entityName, Object key, FindOption... options) { + return byKey( requireEntityPersister( entityName ), options ).performFind( key, this ); + } + + private FindByKeyOperation byKey(EntityPersister entityDescriptor, FindOption... options) { + return byKey( entityDescriptor, null, null, options ); + } + + private FindByKeyOperation byKey( + EntityPersister entityDescriptor, + GraphSemantic graphSemantic, + RootGraphImplementor rootGraph, + FindOption... options) { + return new FindByKeyOperation<>( + entityDescriptor, + graphSemantic, + rootGraph, + lockOptions, + getCacheMode(), + isReadOnly(), + getFactory(), + options + ); } @Override @@ -2577,25 +2505,22 @@ public LockModeType getLockMode(Object entity) { @Override public void setProperty(String propertyName, Object value) { checkOpen(); - - if ( !( value instanceof Serializable ) ) { - SESSION_LOGGER.nonSerializableProperty( propertyName ); - return; - } - if ( propertyName == null ) { SESSION_LOGGER.nullPropertyKey(); - return; } - - // store property for future reference: - if ( properties == null ) { - properties = computeCurrentProperties(); + else if ( !(value instanceof Serializable) ) { + SESSION_LOGGER.nonSerializableProperty( propertyName ); + } + else { + // store property for future reference + if ( properties == null ) { + properties = getInitialProperties(); + } + properties.put( propertyName, value ); + // now actually update the setting if + // it's one that affects this Session + interpretProperty( propertyName, value ); } - properties.put( propertyName, value ); - - // now actually update the setting, if it's one which affects this Session - interpretProperty( propertyName, value ); } private void interpretProperty(String propertyName, Object value) { @@ -2658,7 +2583,7 @@ private void interpretProperty(String propertyName, Object value) { } } - private Map computeCurrentProperties() { + private Map getInitialProperties() { final var map = new HashMap<>( getDefaultProperties() ); //The FLUSH_MODE is always set at Session creation time, //so it needs special treatment to not eagerly initialize this Map: @@ -2668,10 +2593,15 @@ private Map computeCurrentProperties() { @Override public Map getProperties() { - if ( properties == null ) { - properties = computeCurrentProperties(); - } - return unmodifiableMap( properties ); + // EntityManager Javadoc implies that the + // returned map should be a mutable copy, + // not an unmodifiable map. There's no + // good reason to cache the initial + // properties, since we have to copy them + // each time this method is called. + return properties == null + ? getInitialProperties() + : new HashMap<>( properties ); } @Override @@ -2780,23 +2710,24 @@ public Collection getManagedEntities(String entityName) { public Collection getManagedEntities(Class entityType) { return persistenceContext.getEntityHoldersByKey().entrySet().stream() .filter( entry -> entry.getKey().getPersister().getMappedClass().equals( entityType ) ) - .map( entry -> (E) entry.getValue().getManagedObject() ) + .map( entry -> entityType.cast( entry.getValue().getManagedObject() ) ) .toList(); } @Override public Collection getManagedEntities(EntityType entityType) { - final String entityName = ( (EntityDomainType) entityType ).getHibernateEntityName(); + final var entityDomainType = (EntityDomainType) entityType; + final String entityName = entityDomainType.getHibernateEntityName(); return persistenceContext.getEntityHoldersByKey().entrySet().stream() .filter( entry -> entry.getKey().getEntityName().equals( entityName ) ) - .map( entry -> (E) entry.getValue().getManagedObject() ) + .map( entry -> entityType.getJavaType().cast( entry.getValue().getManagedObject() ) ) .toList(); } /** - * Used by JDK serialization... + * Used by JDK serialization * - * @param oos The output stream to which we are being written... + * @param oos The output stream to which we are being written * * @throws IOException Indicates a general IO stream exception */ @@ -2815,9 +2746,9 @@ private void writeObject(ObjectOutputStream oos) throws IOException { } /** - * Used by JDK serialization... + * Used by JDK serialization * - * @param ois The input stream from which we are being read... + * @param ois The input stream from which we are being read * * @throws IOException Indicates a general IO stream exception * @throws ClassNotFoundException Indicates a class resolution issue @@ -2837,7 +2768,7 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound // LoadQueryInfluencers#getEnabledFilters() tries to validate each enabled // filter, which will fail when called before FilterImpl#afterDeserialize( factory ); - // Instead lookup the filter by name and then call FilterImpl#afterDeserialize( factory ). + // Instead, look up the filter by name and then call FilterImpl#afterDeserialize( factory ). for ( String filterName : loadQueryInfluencers.getEnabledFilterNames() ) { ( (FilterImpl) loadQueryInfluencers.getEnabledFilter( filterName ) ) .afterDeserialize( getFactory() ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionLogging.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionLogging.java index cddf971d6999..a82a6fa12e56 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionLogging.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionLogging.java @@ -145,6 +145,10 @@ public interface SessionLogging extends BasicLogger { @Message(id = 90010107, value = "Exception in interceptor afterTransactionCompletion()") void exceptionInAfterTransactionCompletionInterceptor(@Cause Throwable e); + @LogMessage(level = WARN) + @Message(id = 90010108, value = "Closing session with unprocessed clean up bulk operations, forcing their execution") + void closingSessionWithUnprocessedBulkOperations(); + // StatelessSession-specific @LogMessage(level = TRACE) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java index f38757327a72..778a14a1c543 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java @@ -152,8 +152,8 @@ public StatelessSessionImpl(SessionFactoryImpl factory, SessionCreationOptions o influencers = new LoadQueryInfluencers( getFactory() ); eventListenerGroups = factory.getEventListenerGroups(); setUpMultitenancy( factory, influencers ); - // a nonzero batch size forces use of write-behind - // therefore ignore the value of hibernate.jdbc.batch_size + // A nonzero batch size forces the use of write-behind + // Therefore, ignore the value of hibernate.jdbc.batch_size setJdbcBatchSize( 0 ); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/find/FindByKeyOperation.java b/hibernate-core/src/main/java/org/hibernate/internal/find/FindByKeyOperation.java new file mode 100644 index 000000000000..ab27646b3bf9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/find/FindByKeyOperation.java @@ -0,0 +1,338 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.internal.find; + +import jakarta.persistence.CacheRetrieveMode; +import jakarta.persistence.CacheStoreMode; +import jakarta.persistence.FindOption; +import jakarta.persistence.LockModeType; +import jakarta.persistence.PessimisticLockScope; +import jakarta.persistence.Timeout; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.CacheMode; +import org.hibernate.EnabledFetchProfile; +import org.hibernate.KeyType; +import org.hibernate.FindMultipleOption; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.Locking; +import org.hibernate.NaturalIdSynchronization; +import org.hibernate.ObjectNotFoundException; +import org.hibernate.ReadOnlyMode; +import org.hibernate.Timeouts; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.engine.spi.Status; +import org.hibernate.event.spi.LoadEventListener; +import org.hibernate.graph.GraphSemantic; +import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.jpa.internal.util.LockModeTypeHelper; +import org.hibernate.loader.ast.spi.NaturalIdLoader; +import org.hibernate.loader.internal.LoadAccessContext; +import org.hibernate.metamodel.mapping.NaturalIdMapping; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.proxy.HibernateProxy; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Supplier; + +import static org.hibernate.Timeouts.WAIT_FOREVER; +import static org.hibernate.engine.spi.NaturalIdResolutions.INVALID_NATURAL_ID_REFERENCE; +import static org.hibernate.internal.NaturalIdHelper.performAnyNeededCrossReferenceSynchronizations; +import static org.hibernate.jpa.SpecHints.HINT_SPEC_LOCK_TIMEOUT; +import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; + +/// Support for loading a single entity by key (either [id][KeyType#IDENTIFIER] or [natural-id][KeyType#NATURAL]). +/// +/// @see org.hibernate.Session#find +/// @see KeyType +/// +/// @author Steve Ebersole +public class FindByKeyOperation implements NaturalIdLoader.Options { + private final EntityPersister entityDescriptor; + + private KeyType keyType = KeyType.IDENTIFIER; + + private CacheStoreMode cacheStoreMode; + private CacheRetrieveMode cacheRetrieveMode; + + private LockMode lockMode; + private Locking.Scope lockScope; + private Locking.FollowOn lockFollowOn; + private Timeout lockTimeout = WAIT_FOREVER; + + private ReadOnlyMode readOnlyMode; + + private RootGraphImplementor rootGraph; + private GraphSemantic graphSemantic; + + private Set enabledFetchProfiles; + + private NaturalIdSynchronization naturalIdSynchronization; + + public FindByKeyOperation( + @NonNull EntityPersister entityDescriptor, + @Nullable GraphSemantic graphSemantic, + @Nullable RootGraphImplementor rootGraph, + @Nullable LockOptions defaultLockOptions, + @Nullable CacheMode defaultCacheMode, + boolean defaultReadOnly, + @NonNull SessionFactoryImplementor sessionFactory, + FindOption... findOptions) { + this.entityDescriptor = entityDescriptor; + + this.graphSemantic = graphSemantic; + this.rootGraph = rootGraph; + + if ( defaultCacheMode != null ) { + cacheStoreMode = defaultCacheMode.getJpaStoreMode(); + cacheRetrieveMode = defaultCacheMode.getJpaRetrieveMode(); + } + + if ( defaultLockOptions != null ) { + lockMode = defaultLockOptions.getLockMode(); + lockScope = defaultLockOptions.getScope(); + lockTimeout = defaultLockOptions.getTimeout(); + lockFollowOn = defaultLockOptions.getFollowOnStrategy(); + } + if ( lockTimeout == WAIT_FOREVER ) { + final Object factoryTimeoutHint = sessionFactory.getProperties().get( HINT_SPEC_LOCK_TIMEOUT ); + if ( factoryTimeoutHint != null ) { + lockTimeout = Timeouts.fromHintTimeout( factoryTimeoutHint ); + } + } + + readOnlyMode = defaultReadOnly ? ReadOnlyMode.READ_ONLY : ReadOnlyMode.READ_WRITE; + + for ( FindOption option : findOptions ) { + if ( option instanceof KeyType keyType ) { + this.keyType = keyType; + } + else if ( option instanceof CacheStoreMode cacheStoreMode ) { + this.cacheStoreMode = cacheStoreMode; + } + else if ( option instanceof CacheRetrieveMode cacheRetrieveMode ) { + this.cacheRetrieveMode = cacheRetrieveMode; + } + else if ( option instanceof CacheMode cacheMode ) { + this.cacheStoreMode = cacheMode.getJpaStoreMode(); + this.cacheRetrieveMode = cacheMode.getJpaRetrieveMode(); + } + else if ( option instanceof LockModeType lockModeType ) { + this.lockMode = LockModeTypeHelper.getLockMode( lockModeType ); + } + else if ( option instanceof LockMode lockMode ) { + this.lockMode = lockMode; + } + else if ( option instanceof Locking.Scope lockScope ) { + this.lockScope = lockScope; + } + else if ( option instanceof PessimisticLockScope pessimisticLockScope ) { + this.lockScope = Locking.Scope.fromJpaScope( pessimisticLockScope ); + } + else if ( option instanceof Locking.FollowOn followOn ) { + this.lockFollowOn = followOn; + } + else if ( option instanceof Timeout timeout ) { + this.lockTimeout = timeout; + } + else if ( option instanceof ReadOnlyMode readOnlyMode) { + this.readOnlyMode = readOnlyMode; + } + else if ( option instanceof EnabledFetchProfile enabledFetchProfile ) { + this.enabledFetchProfile( enabledFetchProfile.profileName() ); + } + else if ( option instanceof NaturalIdSynchronization naturalIdSynchronization ) { + this.naturalIdSynchronization = naturalIdSynchronization; + } + else if ( option instanceof FindMultipleOption findMultipleOption ) { + throw new IllegalArgumentException( "Option '" + findMultipleOption + "' can only be used in 'findMultiple()'" ); + } + } + } + + private void enabledFetchProfile(String profileName) { + if ( enabledFetchProfiles == null ) { + enabledFetchProfiles = new HashSet<>(); + } + enabledFetchProfiles.add( profileName ); + } + + public T performFind(Object key, LoadAccessContext loadAccessContext) { + if ( keyType == KeyType.NATURAL ) { + return findByNaturalId( key, loadAccessContext ); + } + else { + return findById( key, loadAccessContext ); + } + + } + + private T findByNaturalId(Object key, LoadAccessContext loadAccessContext) { + final NaturalIdMapping naturalIdMapping = entityDescriptor.requireNaturalIdMapping(); + final SessionImplementor session = loadAccessContext.getSession(); + + performAnyNeededCrossReferenceSynchronizations( + naturalIdSynchronization != NaturalIdSynchronization.DISABLED, + entityDescriptor, + session + ); + + final var normalizedKey = naturalIdMapping.normalizeInput( key ); + + final Object cachedResolution = getCachedNaturalIdResolution( normalizedKey, loadAccessContext ); + if ( cachedResolution == INVALID_NATURAL_ID_REFERENCE ) { + return null; + } + + if ( cachedResolution != null ) { + return findById( cachedResolution, loadAccessContext ); + } + + return withOptions( loadAccessContext, () -> { + @SuppressWarnings("unchecked") + final T loaded = (T) entityDescriptor.getNaturalIdLoader() + .load( normalizedKey, this, session ); + if ( loaded != null ) { + final var persistenceContext = session.getPersistenceContextInternal(); + final var lazyInitializer = HibernateProxy.extractLazyInitializer( loaded ); + final var entity = lazyInitializer != null ? lazyInitializer.getImplementation() : loaded; + final var entry = persistenceContext.getEntry( entity ); + assert entry != null; + if ( entry.getStatus() == Status.DELETED ) { + return null; + } + } + return loaded; + } ); + } + + private T withOptions(LoadAccessContext loadAccessContext, Supplier action) { + final var session = loadAccessContext.getSession(); + final var influencers = session.getLoadQueryInfluencers(); + final var fetchProfiles = influencers.adjustFetchProfiles( null, enabledFetchProfiles ); + final var effectiveEntityGraph = rootGraph == null + ? null + : influencers.applyEntityGraph( rootGraph, graphSemantic ); + + final var readOnly = session.isDefaultReadOnly(); + session.setDefaultReadOnly( readOnlyMode == ReadOnlyMode.READ_ONLY ); + + final var cacheMode = session.getCacheMode(); + session.setCacheMode( CacheMode.fromJpaModes( cacheRetrieveMode, cacheStoreMode ) ); + + try { + return action.get(); + } + finally { + loadAccessContext.delayedAfterCompletion(); + if ( effectiveEntityGraph != null ) { + effectiveEntityGraph.clear(); + } + influencers.setEnabledFetchProfileNames( fetchProfiles ); + session.setDefaultReadOnly( readOnly ); + session.setCacheMode( cacheMode ); + } + } + + private Object getCachedNaturalIdResolution( + Object normalizedNaturalIdValue, + LoadAccessContext loadAccessContext) { + loadAccessContext.checkOpenOrWaitingForAutoClose(); + loadAccessContext.pulseTransactionCoordinator(); + + return loadAccessContext + .getSession() + .getPersistenceContextInternal() + .getNaturalIdResolutions() + .findCachedIdByNaturalId( normalizedNaturalIdValue, entityDescriptor ); + } + + private T findById(Object key, LoadAccessContext loadAccessContext) { + return withOptions( loadAccessContext, () -> { + final var session = loadAccessContext.getSession(); + Object result; + try { + result = loadAccessContext.load( + LoadEventListener.GET, + coerceId( key, session.getFactory() ), + entityDescriptor.getEntityName(), + makeLockOptions(), + readOnlyMode == ReadOnlyMode.READ_ONLY + ); + } + catch (ObjectNotFoundException notFoundException) { + // if session cache contains proxy for nonexisting object + result = null; + } + initializeIfNecessary( result ); + //noinspection unchecked + return (T) result; + } ); + } + + private LockOptions makeLockOptions() { + return Helper.makeLockOptions( lockMode, lockScope, lockTimeout, lockFollowOn ); + } + + // Used by Hibernate Reactive + protected Object coerceId(Object id, SessionFactoryImplementor factory) { + if ( factory.getSessionFactoryOptions().getJpaCompliance().isLoadByIdComplianceEnabled() ) { + return id; + } + + try { + return entityDescriptor.getIdentifierMapping().getJavaType().coerce( id ); + } + catch ( Exception e ) { + throw new IllegalArgumentException( "Argument '" + id + + "' could not be converted to the identifier type of entity '" + + entityDescriptor.getEntityName() + "'" + + " [" + e.getMessage() + "]", e ); + } + } + + private void initializeIfNecessary(Object result) { + if ( result != null ) { + final var lazyInitializer = extractLazyInitializer( result ); + if ( lazyInitializer != null ) { + if ( lazyInitializer.isUninitialized() ) { + lazyInitializer.initialize(); + } + } + else { + final var enhancementMetadata = entityDescriptor.getBytecodeEnhancementMetadata(); + if ( enhancementMetadata.isEnhancedForLazyLoading() + && enhancementMetadata.extractLazyInterceptor( result ) + instanceof EnhancementAsProxyLazinessInterceptor lazinessInterceptor ) { + lazinessInterceptor.forceInitialize( result, null ); + } + } + } + } + + @Override + public LockMode getLockMode() { + return lockMode; + } + + @Override + public Timeout getLockTimeout() { + return lockTimeout; + } + + @Override + public Locking.Scope getLockScope() { + return lockScope; + } + + @Override + public Locking.FollowOn getLockFollowOn() { + return lockFollowOn; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/find/FindMultipleByKeyOperation.java b/hibernate-core/src/main/java/org/hibernate/internal/find/FindMultipleByKeyOperation.java new file mode 100644 index 000000000000..76c073b66d8a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/find/FindMultipleByKeyOperation.java @@ -0,0 +1,344 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.internal.find; + +import jakarta.persistence.CacheRetrieveMode; +import jakarta.persistence.CacheStoreMode; +import jakarta.persistence.FindOption; +import jakarta.persistence.LockModeType; +import jakarta.persistence.PessimisticLockScope; +import jakarta.persistence.Timeout; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.BatchSize; +import org.hibernate.CacheMode; +import org.hibernate.EnabledFetchProfile; +import org.hibernate.KeyType; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.Locking; +import org.hibernate.NaturalIdSynchronization; +import org.hibernate.OrderingMode; +import org.hibernate.ReadOnlyMode; +import org.hibernate.RemovalsMode; +import org.hibernate.SessionCheckMode; +import org.hibernate.Timeouts; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.graph.GraphSemantic; +import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.jpa.internal.util.LockModeTypeHelper; +import org.hibernate.loader.ast.spi.MultiIdLoadOptions; +import org.hibernate.loader.ast.spi.MultiNaturalIdLoadOptions; +import org.hibernate.loader.internal.LoadAccessContext; +import org.hibernate.persister.entity.EntityPersister; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; + +import static org.hibernate.Timeouts.WAIT_FOREVER; +import static org.hibernate.internal.NaturalIdHelper.performAnyNeededCrossReferenceSynchronizations; +import static org.hibernate.jpa.SpecHints.HINT_SPEC_LOCK_TIMEOUT; + +/// Support for loading multiple entities (of a type) by key (either [id][KeyType#IDENTIFIER] or [natural-id][KeyType#NATURAL]). +/// +/// @see org.hibernate.Session#findMultiple +/// @see KeyType +/// +/// @author Steve Ebersole +public class FindMultipleByKeyOperation implements MultiIdLoadOptions, MultiNaturalIdLoadOptions { + private final EntityPersister entityDescriptor; + + private KeyType keyType = KeyType.IDENTIFIER; + + private BatchSize batchSize; + private SessionCheckMode sessionCheckMode = SessionCheckMode.DISABLED; + private RemovalsMode removalsMode = RemovalsMode.REPLACE; + private OrderingMode orderingMode = OrderingMode.ORDERED; + + private CacheStoreMode cacheStoreMode; + private CacheRetrieveMode cacheRetrieveMode; + + private LockMode lockMode; + private Locking.Scope lockScope; + private Locking.FollowOn lockFollowOn; + private Timeout lockTimeout = WAIT_FOREVER; + + private ReadOnlyMode readOnlyMode; + + private Set enabledFetchProfiles; + private Set disabledFetchProfiles; + + private NaturalIdSynchronization naturalIdSynchronization; + + @SuppressWarnings("PatternVariableHidesField") + public FindMultipleByKeyOperation( + @NonNull EntityPersister entityDescriptor, + @Nullable LockOptions defaultLockOptions, + @Nullable CacheMode defaultCacheMode, + boolean defaultReadOnly, + @NonNull SessionFactoryImplementor sessionFactory, + FindOption... findOptions) { + this.entityDescriptor = entityDescriptor; + + if ( defaultCacheMode != null ) { + cacheStoreMode = defaultCacheMode.getJpaStoreMode(); + cacheRetrieveMode = defaultCacheMode.getJpaRetrieveMode(); + } + + if ( defaultLockOptions != null ) { + lockMode = defaultLockOptions.getLockMode(); + lockScope = defaultLockOptions.getScope(); + lockTimeout = defaultLockOptions.getTimeout(); + lockFollowOn = defaultLockOptions.getFollowOnStrategy(); + } + if ( lockTimeout == WAIT_FOREVER ) { + final Object factoryTimeoutHint = sessionFactory.getProperties().get( HINT_SPEC_LOCK_TIMEOUT ); + if ( factoryTimeoutHint != null ) { + lockTimeout = Timeouts.fromHintTimeout( factoryTimeoutHint ); + } + } + + readOnlyMode = defaultReadOnly ? ReadOnlyMode.READ_ONLY : ReadOnlyMode.READ_WRITE; + + for ( FindOption option : findOptions ) { + if ( option instanceof KeyType keyType ) { + this.keyType = keyType; + } + else if ( option instanceof BatchSize batchSize ) { + this.batchSize = batchSize; + } + else if ( option instanceof SessionCheckMode sessionCheckMode ) { + this.sessionCheckMode = sessionCheckMode; + } + else if ( option instanceof RemovalsMode removalsMode ) { + this.removalsMode = removalsMode; + } + else if ( option instanceof OrderingMode orderingMode ) { + this.orderingMode = orderingMode; + } + else if ( option instanceof CacheStoreMode cacheStoreMode ) { + this.cacheStoreMode = cacheStoreMode; + } + else if ( option instanceof CacheRetrieveMode cacheRetrieveMode ) { + this.cacheRetrieveMode = cacheRetrieveMode; + } + else if ( option instanceof CacheMode cacheMode ) { + this.cacheStoreMode = cacheMode.getJpaStoreMode(); + this.cacheRetrieveMode = cacheMode.getJpaRetrieveMode(); + } + else if ( option instanceof LockModeType lockModeType ) { + this.lockMode = LockModeTypeHelper.getLockMode( lockModeType ); + } + else if ( option instanceof LockMode lockMode ) { + this.lockMode = lockMode; + } + else if ( option instanceof Locking.Scope lockScope ) { + this.lockScope = lockScope; + } + else if ( option instanceof PessimisticLockScope pessimisticLockScope ) { + this.lockScope = Locking.Scope.fromJpaScope( pessimisticLockScope ); + } + else if ( option instanceof Locking.FollowOn followOn ) { + this.lockFollowOn = followOn; + } + else if ( option instanceof Timeout timeout ) { + this.lockTimeout = timeout; + } + else if ( option instanceof ReadOnlyMode readOnlyMode) { + this.readOnlyMode = readOnlyMode; + } + else if ( option instanceof EnabledFetchProfile enabledFetchProfile ) { + this.enabledFetchProfile( enabledFetchProfile.profileName() ); + } + else if ( option instanceof NaturalIdSynchronization naturalIdSynchronization ) { + this.naturalIdSynchronization = naturalIdSynchronization; + } + } + } + + private void enabledFetchProfile(String profileName) { + if ( enabledFetchProfiles == null ) { + enabledFetchProfiles = new HashSet<>(); + } + enabledFetchProfiles.add( profileName ); + } + + public List performFind( + List keys, + @Nullable GraphSemantic graphSemantic, + @Nullable RootGraphImplementor rootGraph, + LoadAccessContext loadAccessContext) { + // todo (natural-id-class) : these impls are temporary + // longer term, move the logic here as much of it can be shared + return keyType == KeyType.NATURAL + ? findByNaturalIds( keys, graphSemantic, rootGraph, loadAccessContext ) + : findByIds( keys, graphSemantic, rootGraph, loadAccessContext ); + } + + private List findByNaturalIds(List keys, GraphSemantic graphSemantic, RootGraphImplementor rootGraph, LoadAccessContext loadAccessContext) { + final var naturalIdMapping = entityDescriptor.requireNaturalIdMapping(); + final var session = loadAccessContext.getSession(); + + performAnyNeededCrossReferenceSynchronizations( + naturalIdSynchronization != NaturalIdSynchronization.DISABLED, + entityDescriptor, + session + ); + + return withOptions( loadAccessContext, graphSemantic, rootGraph, () -> { + // normalize the incoming natural-id values and get them in array form as needed + // by MultiNaturalIdLoader + final int size = keys.size(); + final var naturalIds = new Object[size]; + for ( int i = 0; i < size; i++ ) { + naturalIds[i] = naturalIdMapping.normalizeInput( keys.get( i ) ); + } + //noinspection unchecked + return (List) + entityDescriptor.getMultiNaturalIdLoader() + .multiLoad( naturalIds, this, session ); + } ); + } + + private List withOptions( + LoadAccessContext loadAccessContext, + GraphSemantic graphSemantic, + RootGraphImplementor rootGraph, + Supplier> action) { + final var session = loadAccessContext.getSession(); + final var influencers = session.getLoadQueryInfluencers(); + final var fetchProfiles = + influencers.adjustFetchProfiles( disabledFetchProfiles, enabledFetchProfiles ); + final var effectiveEntityGraph = + rootGraph == null + ? null + : influencers.applyEntityGraph( rootGraph, graphSemantic ); + + final var readOnly = session.isDefaultReadOnly(); + session.setDefaultReadOnly( readOnlyMode == ReadOnlyMode.READ_ONLY ); + + final var cacheMode = session.getCacheMode(); + session.setCacheMode( CacheMode.fromJpaModes( cacheRetrieveMode, cacheStoreMode ) ); + + try { + return action.get(); + } + finally { + loadAccessContext.delayedAfterCompletion(); + if ( effectiveEntityGraph != null ) { + effectiveEntityGraph.clear(); + } + influencers.setEnabledFetchProfileNames( fetchProfiles ); + session.setDefaultReadOnly( readOnly ); + session.setCacheMode( cacheMode ); + } + } + +// private Object getCachedNaturalIdResolution( +// Object normalizedNaturalIdValue, +// LoadAccessContext loadAccessContext) { +// loadAccessContext.checkOpenOrWaitingForAutoClose(); +// loadAccessContext.pulseTransactionCoordinator(); +// +// return loadAccessContext +// .getSession() +// .getPersistenceContextInternal() +// .getNaturalIdResolutions() +// .findCachedIdByNaturalId( normalizedNaturalIdValue, entityDescriptor ); +// } + + private List findByIds(List keys, GraphSemantic graphSemantic, RootGraphImplementor rootGraph, LoadAccessContext loadAccessContext) { + final var ids = keys.toArray( new Object[0] ); + //noinspection unchecked + return withOptions( loadAccessContext, graphSemantic, rootGraph, + () -> (List) entityDescriptor.multiLoad( ids, loadAccessContext.getSession(), this ) ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // MultiIdLoadOptions & MultiNaturalIdLoadOptions + + @Override + public SessionCheckMode getSessionCheckMode() { + return sessionCheckMode; + } + + @Override + public boolean isSecondLevelCacheCheckingEnabled() { + return cacheRetrieveMode == CacheRetrieveMode.USE; + } + + @Override + public Boolean getReadOnly(SessionImplementor session) { + return readOnlyMode == null ? null : readOnlyMode == ReadOnlyMode.READ_ONLY; + } + + @Override + public RemovalsMode getRemovalsMode() { + return removalsMode; + } + + @Override + public OrderingMode getOrderingMode() { + return orderingMode; + } + + @Override + public LockOptions getLockOptions() { + return Helper.makeLockOptions( lockMode, lockScope, lockTimeout, lockFollowOn ); + } + + @Override + public Integer getBatchSize() { + return batchSize == null ? null : batchSize.batchSize(); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + /// Temporarily defined full constructor in support of + /// [org.hibernate.MultiIdentifierLoadAccess] and [org.hibernate.MultiIdentifierLoadAccess]. + /// + /// @deprecated [org.hibernate.MultiIdentifierLoadAccess] and [org.hibernate.MultiIdentifierLoadAccess] + /// are both also deprecated. + @Deprecated + public FindMultipleByKeyOperation( + EntityPersister entityDescriptor, + KeyType keyType, + BatchSize batchSize, + SessionCheckMode sessionCheckMode, + RemovalsMode removalsMode, + OrderingMode orderingMode, + CacheMode cacheMode, + LockOptions lockOptions, + ReadOnlyMode readOnlyMode, + Set enabledFetchProfiles, + Set disabledFetchProfiles, + NaturalIdSynchronization naturalIdSynchronization) { + if ( cacheMode == null ) { + cacheMode = CacheMode.NORMAL; + } + if ( lockOptions == null ) { + lockOptions = LockOptions.NONE; + } + this.entityDescriptor = entityDescriptor; + this.keyType = keyType; + this.batchSize = batchSize; + this.sessionCheckMode = sessionCheckMode; + this.removalsMode = removalsMode; + this.orderingMode = orderingMode; + this.cacheStoreMode = cacheMode.getJpaStoreMode(); + this.cacheRetrieveMode = cacheMode.getJpaRetrieveMode(); + this.lockMode = lockOptions.getLockMode(); + this.lockScope = lockOptions.getScope(); + this.lockFollowOn = lockOptions.getFollowOnStrategy(); + this.lockTimeout = lockOptions.getTimeout(); + this.readOnlyMode = readOnlyMode; + this.enabledFetchProfiles = enabledFetchProfiles; + this.disabledFetchProfiles = disabledFetchProfiles; + this.naturalIdSynchronization = naturalIdSynchronization; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/find/Helper.java b/hibernate-core/src/main/java/org/hibernate/internal/find/Helper.java new file mode 100644 index 000000000000..7a43743efbf5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/find/Helper.java @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.internal.find; + +import jakarta.persistence.Timeout; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.Locking; +import org.hibernate.Timeouts; + +/** + * @author Steve Ebersole + */ +public class Helper { + public static LockOptions makeLockOptions(LockMode lockMode, Locking.Scope lockScope, Timeout lockTimeout, Locking.FollowOn lockFollowOn) { + if ( lockMode == null || lockMode == LockMode.NONE ) { + return LockOptions.NONE; + } + if ( lockMode == LockMode.READ ) { + return LockOptions.READ; + } + + final var lockOptions = new LockOptions( lockMode ); + lockOptions.setScope( lockScope != null ? lockScope : Locking.Scope.ROOT_ONLY ); + lockOptions.setTimeout( lockTimeout != null ? lockTimeout : Timeouts.WAIT_FOREVER ); + lockOptions.setFollowOnStrategy( lockFollowOn != null ? lockFollowOn : Locking.FollowOn.ALLOW ); + return lockOptions; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/GenericAssignability.java b/hibernate-core/src/main/java/org/hibernate/internal/util/GenericAssignability.java new file mode 100644 index 000000000000..1da0cb05cce2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/GenericAssignability.java @@ -0,0 +1,204 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.internal.util; + +import java.lang.reflect.*; + +/** + * @author Gavin King + */ +public final class GenericAssignability { + + public static boolean isAssignableFrom(Type to, Type from) { + return isAssignable( from, to ); + } + + public static boolean isAssignable(Type from, Type to) { + if ( from.equals( to ) ) { + return true; + } + + // Wildcards (target side) + if ( to instanceof WildcardType wt ) { + return isAssignableToWildcard( from, wt ); + } + + // Type variables (target side) + if ( to instanceof TypeVariable tv ) { + return isAssignableToTypeVariable( from, tv ); + } + + // Generic arrays + if ( to instanceof GenericArrayType gaTo ) { + return isAssignableToGenericArray( from, gaTo ); + } + + // From-side wildcard + if ( from instanceof WildcardType wf ) { + return isAssignableFromWildcard( wf, to ); + } + + // Parameterized types + if ( from instanceof ParameterizedType pf && + to instanceof ParameterizedType pt ) { + return isAssignableParameterized( pf, pt ); + } + + // Raw class or array class + final var fromRaw = rawClass( from ); + final var toRaw = rawClass( to ); + + if ( fromRaw != null && toRaw != null ) { + return toRaw.isAssignableFrom( fromRaw ) + || isAssignableViaInheritance( from, toRaw ); + } + + return false; + } + + private static boolean isAssignableToGenericArray(Type from, GenericArrayType to) { + final var toComponent = to.getGenericComponentType(); + if ( from instanceof GenericArrayType gaFrom ) { + return isAssignable( gaFrom.getGenericComponentType(), toComponent ); + } + else if ( from instanceof Class c && c.isArray() ) { + return isAssignable( c.getComponentType(), toComponent ); + } + else { + return false; + } + } + + private static boolean isAssignableParameterized(ParameterizedType from, ParameterizedType to) { + + final var fromRaw = (Class) from.getRawType(); + final var toRaw = (Class) to.getRawType(); + + if ( toRaw.isAssignableFrom( fromRaw ) ) { + final var superType = findGenericSuperType( from, toRaw ); + if ( !( superType instanceof ParameterizedType ps ) ) { + return false; + } + else { + final var fromArgs = ps.getActualTypeArguments(); + final var toArgs = to.getActualTypeArguments(); + if ( fromArgs.length != toArgs.length ) { + return false; + } + for ( int i = 0; i < fromArgs.length; i++ ) { + if ( !isTypeArgumentAssignable( fromArgs[i], toArgs[i] ) ) { + return false; + } + } + return true; + } + } + else { + return false; + } + + } + + private static boolean isTypeArgumentAssignable(Type from, Type to) { + return to instanceof WildcardType wt + ? isAssignableToWildcard( from, wt ) + : isAssignable( from, to ); + } + + private static boolean isAssignableViaInheritance(Type from, Class toRaw) { + + final var raw = rawClass( from ); + if ( raw == null ) { + return false; + } + + // Check interfaces + for ( var iface : raw.getGenericInterfaces() ) { + if ( isAssignable( iface, toRaw ) ) { + return true; + } + } + + // Check superclass + final var superclass = raw.getGenericSuperclass(); + if ( superclass != null ) { + return isAssignable( superclass, toRaw ); + } + + return false; + } + + private static Type findGenericSuperType(Type from, Class target) { + final var raw = rawClass( from ); + if ( raw == null ) { + return null; + } + + if ( raw == target ) { + return from; + } + + for ( var iface : raw.getGenericInterfaces() ) { + final var found = findGenericSuperType( iface, target ); + if ( found != null ) { + return found; + } + } + + final var superclass = raw.getGenericSuperclass(); + if ( superclass != null ) { + return findGenericSuperType( superclass, target ); + } + + return null; + } + + private static boolean isAssignableToWildcard(Type from, WildcardType to) { + for ( var lower : to.getLowerBounds() ) { + if ( !isAssignable( lower, from ) ) { + return false; + } + } + for ( var upper : to.getUpperBounds() ) { + if ( !isAssignable( from, upper ) ) { + return false; + } + } + return true; + } + + private static boolean isAssignableFromWildcard(WildcardType from, Type to) { + for ( var upper : from.getUpperBounds() ) { + if ( isAssignable( upper, to ) ) { + return true; + } + } + return false; + } + + private static boolean isAssignableToTypeVariable(Type from, TypeVariable tv) { + for ( var bound : tv.getBounds() ) { + if ( !isAssignable( from, bound ) ) { + return false; + } + } + return true; + } + + private static Class rawClass(Type type) { + if ( type instanceof Class c ) { + return c; + } + else if ( type instanceof ParameterizedType pt ) { + return (Class) pt.getRawType(); + } + else if ( type instanceof GenericArrayType ga ) { + return rawClass( ga.getGenericComponentType() ).arrayType(); + } + else { + return null; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/GenericsHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/GenericsHelper.java index da448b313a6e..b66b9f6f6731 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/GenericsHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/GenericsHelper.java @@ -4,38 +4,213 @@ */ package org.hibernate.internal.util; +import org.hibernate.models.spi.MemberDetails; + +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Member; +import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.HashMap; +import java.util.Map; +import java.util.StringJoiner; + +/** + * @author Gavin King + */ +public final class GenericsHelper { + + /** + * The type of the member inherited by the subclass from the supertype, + * as viewed from within the subclass. + * @param memberDetails The member, represented in the subclass + * @return The type of the member as it would be seen in the subclass + */ + public static Type actualMemberType(MemberDetails memberDetails) { + return actualInheritedMemberType( + memberDetails.getDeclaringType().toJavaClass(), + memberDetails.toJavaMember() + ); + } + + /** + * The type of the member inherited by the subclass from the supertype, + * as viewed from within the subclass. + * @param subclass The inheriting subclass + * @param superMember The member declared in the supertype + * @return The type of the member as it would be seen in the subclass + */ + public static Type actualInheritedMemberType(Class subclass, Member superMember) { + return substituteTypeVariables( getMemberType( superMember ), + collectTypeArguments( subclass, superMember ) ); + } + + private static Map, Type> collectTypeArguments( + Class subclass, Member superMember) { + final var superclass = superMember.getDeclaringClass(); + final var typeArguments = typeArguments( superclass, subclass ); + final var typeParameters = superclass.getTypeParameters(); + final Map, Type> typeMap = + new HashMap<>( typeParameters.length ); + for ( int i = 0; i < typeParameters.length; i++ ) { + typeMap.put( typeParameters[i], typeArguments[i] ); + } + return typeMap; + } + + private static Type getMemberType(Member superMember) { + if ( superMember instanceof Field field ) { + return field.getGenericType(); + } + else if ( superMember instanceof Method method ) { + return method.getGenericReturnType(); + } + else { + throw new IllegalArgumentException( "Unsupported member: " + superMember ); + } + } + + private static Type substituteTypeVariables(Type type, Map, Type> typeMap) { + + if ( type instanceof TypeVariable typeVariable ) { + final var substituted = typeMap.get( typeVariable ); + return substituted == null + ? Object.class + : substituteTypeVariables( substituted, typeMap ); + } + else if ( type instanceof ParameterizedType parameterizedType ) { + final var args = parameterizedType.getActualTypeArguments(); + final var resolved = new Type[args.length]; + for ( int i = 0; i < args.length; i++ ) { + resolved[i] = substituteTypeVariables( args[i], typeMap ); + } + return new SimpleParameterizedType( + (Class) parameterizedType.getRawType(), + resolved, + parameterizedType.getOwnerType() + ); + } + else if ( type instanceof GenericArrayType genericArrayType ) { + final var elementType = + substituteTypeVariables( genericArrayType.getGenericComponentType(), typeMap ); + return new GenericArrayType() { + @Override + public Type getGenericComponentType() { + return elementType; + } + + @Override + public String toString() { + return elementType.getTypeName() + "[]"; + } + }; + } + else { + return type; + } + } + + /** + * The erased type of the given type. + * @param type A type, possibly with type arguments + * @return The erased type + */ + public static Class erasedType(Type type) { + if ( type instanceof Class clazz ) { + return clazz; + } + else if ( type instanceof ParameterizedType parameterizedType ) { + return erasedType( parameterizedType.getRawType() ); + } + else if ( type instanceof TypeVariable typeVariable ) { + return erasedType( typeVariable.getBounds()[0] ); + } + else if ( type instanceof GenericArrayType genericArrayType ) { + return genericArrayType.getGenericComponentType() instanceof Class elementClass + ? elementClass.arrayType() + : Object[].class; + } + else { + throw new IllegalArgumentException( "Cannot erase type: " + type ); + } + } -public class GenericsHelper { + /** + * Get the type argument of the instantiation of the given generic + * type constructor which is a supertype of the given type expression. + * @param genericType A generic type constructor + * @param implementingType A type expression + * @return The type arguments assigned to parameters of the generic type constructor + */ + public static Type[] typeArguments(Class genericType, Type implementingType) { + if ( genericType.getTypeParameters().length == 0 ) { + return EMPTY_TYPE_ARRAY; + } + else { + final var instantiation = + supertypeInstantiation( genericType, implementingType ); + if ( instantiation == null ) { + throw new IllegalArgumentException( + implementingType.getTypeName() + + " is is not a subtype of " + + genericType.getName() ); + } + return instantiation.getActualTypeArguments(); + } + } - public static ParameterizedType extractParameterizedType(Type base, Class genericType) { + private static final Type[] EMPTY_TYPE_ARRAY = new Type[0]; + + /** + * A supertype of the given type which is an instantiation of the + * given generic type constructor. + * @param genericType A generic type constructor + * @param base A type which is assignable to some instantiation of + * the given generic type constructor + * @return An instantiation of the generic type constructor which + * is a supertype of the given type, or null if none exists + */ + public static ParameterizedType supertypeInstantiation(Class genericType, Type base) { if ( base == null ) { return null; } - final Class clazz = extractClass( base ); + final var clazz = erasedType( base ); if ( clazz == null ) { return null; } - final List types = new ArrayList<>(); - types.add( clazz.getGenericSuperclass() ); - types.addAll( Arrays.asList( clazz.getGenericInterfaces() ) ); + if ( clazz == genericType + && base instanceof ParameterizedType result ) { + return result; + } - for ( Type type : types ) { - type = resolveType( type, base ); - if ( type instanceof ParameterizedType parameterizedType ) { - if ( genericType.equals( parameterizedType.getRawType() ) ) { - return parameterizedType; - } + final var superclass = clazz.getGenericSuperclass(); + if ( superclass != null ) { + final var type = substituteTypeArguments( superclass, base ); + if ( type instanceof ParameterizedType parameterizedType + && genericType.equals( parameterizedType.getRawType() ) ) { + return parameterizedType; + } + + final var parameterizedType = + supertypeInstantiation( genericType, type ); + if ( parameterizedType != null ) { + return parameterizedType; + } + } + + for ( var iface : clazz.getGenericInterfaces() ) { + final var type = substituteTypeArguments( iface, base ); + if ( type instanceof ParameterizedType parameterizedType + && genericType.equals( parameterizedType.getRawType() ) ) { + return parameterizedType; } - final ParameterizedType parameterizedType = extractParameterizedType( type, genericType ); + final var parameterizedType = + supertypeInstantiation( genericType, type ); if ( parameterizedType != null ) { return parameterizedType; } @@ -44,66 +219,75 @@ public static ParameterizedType extractParameterizedType(Type base, Class gen return null; } - private static Type resolveTypeVariable(TypeVariable typeVariable, ParameterizedType context) { - final Class clazz = extractClass( context.getRawType() ); + private static Type replaceTypeVariableWithArgument( + TypeVariable typeVariable, ParameterizedType context) { + final var clazz = erasedType( context.getRawType() ); if ( clazz == null ) { return null; } - final TypeVariable[] typeParameters = clazz.getTypeParameters(); + final var typeArguments = context.getActualTypeArguments(); + final var typeParameters = clazz.getTypeParameters(); for ( int idx = 0; idx < typeParameters.length; idx++ ) { if ( typeVariable.getName().equals( typeParameters[idx].getName() ) ) { - return resolveType( context.getActualTypeArguments()[idx], context ); + return substituteTypeArguments( typeArguments[idx], context ); } } return typeVariable; } - public static Class extractClass(Type type) { - if ( type instanceof Class clazz ) { - return clazz; + private static Type substituteTypeArguments(Type target, Type context) { + if ( target instanceof ParameterizedType parameterizedType ) { + return replaceTypeVariablesWithArguments( parameterizedType, context ); } - else if ( type instanceof ParameterizedType parameterizedType ) { - return extractClass( parameterizedType.getRawType() ); + else if ( target instanceof TypeVariable typeVariable + && context instanceof ParameterizedType parameterizedContext ) { + return replaceTypeVariableWithArgument( typeVariable, parameterizedContext ); + } + else { + return target; } - return null; } - private static Type resolveType(Type target, Type context) { - if ( target instanceof ParameterizedType parameterizedType ) { - return resolveParameterizedType( parameterizedType, context ); + private static ParameterizedType replaceTypeVariablesWithArguments( + ParameterizedType parameterizedType, Type context) { + final var typeArguments = parameterizedType.getActualTypeArguments(); + final var resolvedTypeArguments = new Type[typeArguments.length]; + for ( int idx = 0; idx < typeArguments.length; idx++ ) { + resolvedTypeArguments[idx] = substituteTypeArguments( typeArguments[idx], context ); } - else if ( target instanceof TypeVariable typeVariable ) { - return resolveTypeVariable( typeVariable, (ParameterizedType) context ); - } - return target; + return new SimpleParameterizedType( + erasedType( parameterizedType ), + resolvedTypeArguments, + parameterizedType.getOwnerType() + ); } - private static ParameterizedType resolveParameterizedType(final ParameterizedType parameterizedType, Type context) { - final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); - - final Type[] resolvedTypeArguments = new Type[actualTypeArguments.length]; - for ( int idx = 0; idx < actualTypeArguments.length; idx++ ) { - resolvedTypeArguments[idx] = resolveType( actualTypeArguments[idx], context ); + private record SimpleParameterizedType(Class raw, Type[] args, Type owner) + implements ParameterizedType { + @Override + public Type[] getActualTypeArguments() { + return args; } - return new ParameterizedType() { - @Override - public Type[] getActualTypeArguments() { - return resolvedTypeArguments; - } + @Override + public Type getRawType() { + return raw; + } - @Override - public Type getRawType() { - return parameterizedType.getRawType(); - } + @Override + public Type getOwnerType() { + return owner; + } - @Override - public Type getOwnerType() { - return parameterizedType.getOwnerType(); + @Override + public String toString() { + final var joiner = new StringJoiner( ", ", "<", ">" ); + for ( var type : args ) { + joiner.add( type.getTypeName() ); } - - }; + return raw.getName() + joiner; + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/type/PrimitiveWrapperHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/type/PrimitiveWrapperHelper.java deleted file mode 100644 index e9c2de5052d0..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/type/PrimitiveWrapperHelper.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.internal.util.type; - -/** - * Helper for primitive/wrapper utilities. - * - * @author Steve Ebersole - */ -public final class PrimitiveWrapperHelper { - private PrimitiveWrapperHelper() { - } - - /** - * Describes a particular primitive/wrapper combo - */ - public interface PrimitiveWrapperDescriptor { - Class getPrimitiveClass(); - Class getWrapperClass(); - } - - public static class BooleanDescriptor implements PrimitiveWrapperDescriptor { - public static final BooleanDescriptor INSTANCE = new BooleanDescriptor(); - - private BooleanDescriptor() { - } - - @Override - public Class getPrimitiveClass() { - return boolean.class; - } - - @Override - public Class getWrapperClass() { - return Boolean.class; - } - } - - public static class CharacterDescriptor implements PrimitiveWrapperDescriptor { - public static final CharacterDescriptor INSTANCE = new CharacterDescriptor(); - - private CharacterDescriptor() { - } - - @Override - public Class getPrimitiveClass() { - return char.class; - } - - @Override - public Class getWrapperClass() { - return Character.class; - } - } - - public static class ByteDescriptor implements PrimitiveWrapperDescriptor { - public static final ByteDescriptor INSTANCE = new ByteDescriptor(); - - private ByteDescriptor() { - } - - @Override - public Class getPrimitiveClass() { - return byte.class; - } - - @Override - public Class getWrapperClass() { - return Byte.class; - } - } - - public static class ShortDescriptor implements PrimitiveWrapperDescriptor { - public static final ShortDescriptor INSTANCE = new ShortDescriptor(); - - private ShortDescriptor() { - } - - @Override - public Class getPrimitiveClass() { - return short.class; - } - - @Override - public Class getWrapperClass() { - return Short.class; - } - } - - public static class IntegerDescriptor implements PrimitiveWrapperDescriptor { - public static final IntegerDescriptor INSTANCE = new IntegerDescriptor(); - - private IntegerDescriptor() { - } - - @Override - public Class getPrimitiveClass() { - return int.class; - } - - @Override - public Class getWrapperClass() { - return Integer.class; - } - } - - public static class LongDescriptor implements PrimitiveWrapperDescriptor { - public static final LongDescriptor INSTANCE = new LongDescriptor(); - - private LongDescriptor() { - } - - @Override - public Class getPrimitiveClass() { - return long.class; - } - - @Override - public Class getWrapperClass() { - return Long.class; - } - } - - public static class FloatDescriptor implements PrimitiveWrapperDescriptor { - public static final FloatDescriptor INSTANCE = new FloatDescriptor(); - - private FloatDescriptor() { - } - - @Override - public Class getPrimitiveClass() { - return float.class; - } - - @Override - public Class getWrapperClass() { - return Float.class; - } - } - - public static class DoubleDescriptor implements PrimitiveWrapperDescriptor { - public static final DoubleDescriptor INSTANCE = new DoubleDescriptor(); - - private DoubleDescriptor() { - } - - @Override - public Class getPrimitiveClass() { - return double.class; - } - - @Override - public Class getWrapperClass() { - return Double.class; - } - } - - @SuppressWarnings("unchecked") - public static PrimitiveWrapperDescriptor getDescriptorByPrimitiveType(Class primitiveClazz) { - if ( ! primitiveClazz.isPrimitive() ) { - throw new IllegalArgumentException( "Given class is not a primitive type : " + primitiveClazz.getName() ); - } - - if ( boolean.class == primitiveClazz ) { - return (PrimitiveWrapperDescriptor) BooleanDescriptor.INSTANCE; - } - - if ( char.class == primitiveClazz ) { - return (PrimitiveWrapperDescriptor) CharacterDescriptor.INSTANCE; - } - - if ( byte.class == primitiveClazz ) { - return (PrimitiveWrapperDescriptor) ByteDescriptor.INSTANCE; - } - - if ( short.class == primitiveClazz ) { - return (PrimitiveWrapperDescriptor) ShortDescriptor.INSTANCE; - } - - if ( int.class == primitiveClazz ) { - return (PrimitiveWrapperDescriptor) IntegerDescriptor.INSTANCE; - } - - if ( long.class == primitiveClazz ) { - return (PrimitiveWrapperDescriptor) LongDescriptor.INSTANCE; - } - - if ( float.class == primitiveClazz ) { - return (PrimitiveWrapperDescriptor) FloatDescriptor.INSTANCE; - } - - if ( double.class == primitiveClazz ) { - return (PrimitiveWrapperDescriptor) DoubleDescriptor.INSTANCE; - } - - if ( void.class == primitiveClazz ) { - throw new IllegalArgumentException( "void, as primitive type, has no wrapper equivalent" ); - } - - throw new IllegalArgumentException( "Unrecognized primitive type class : " + primitiveClazz.getName() ); - } - - @SuppressWarnings("unchecked") - public static PrimitiveWrapperDescriptor getDescriptorByWrapperType(Class wrapperClass) { - if ( wrapperClass.isPrimitive() ) { - throw new IllegalArgumentException( "Given class is a primitive type : " + wrapperClass.getName() ); - } - - if ( Boolean.class.equals( wrapperClass ) ) { - return (PrimitiveWrapperDescriptor) BooleanDescriptor.INSTANCE; - } - - if ( Character.class == wrapperClass ) { - return (PrimitiveWrapperDescriptor) CharacterDescriptor.INSTANCE; - } - - if ( Byte.class == wrapperClass ) { - return (PrimitiveWrapperDescriptor) ByteDescriptor.INSTANCE; - } - - if ( Short.class == wrapperClass ) { - return (PrimitiveWrapperDescriptor) ShortDescriptor.INSTANCE; - } - - if ( Integer.class == wrapperClass ) { - return (PrimitiveWrapperDescriptor) IntegerDescriptor.INSTANCE; - } - - if ( Long.class == wrapperClass ) { - return (PrimitiveWrapperDescriptor) LongDescriptor.INSTANCE; - } - - if ( Float.class == wrapperClass ) { - return (PrimitiveWrapperDescriptor) FloatDescriptor.INSTANCE; - } - - if ( Double.class == wrapperClass ) { - return (PrimitiveWrapperDescriptor) DoubleDescriptor.INSTANCE; - } - - // most likely Void.class, which we can't really handle here - throw new IllegalArgumentException( "Unrecognized wrapper type class : " + wrapperClass.getName() ); - } - - public static boolean isWrapper(Class clazz) { - try { - getDescriptorByWrapperType( clazz ); - return true; - } - catch (Exception e) { - return false; - } - } - - public static boolean arePrimitiveWrapperEquivalents(Class converterDefinedType, Class propertyType) { - if ( converterDefinedType.isPrimitive() ) { - return getDescriptorByPrimitiveType( converterDefinedType ).getWrapperClass().equals( propertyType ); - } - else if ( propertyType.isPrimitive() ) { - return getDescriptorByPrimitiveType( propertyType ).getWrapperClass().equals( converterDefinedType ); - } - return false; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/type/PrimitiveWrappers.java b/hibernate-core/src/main/java/org/hibernate/internal/util/type/PrimitiveWrappers.java new file mode 100644 index 000000000000..163f1c35caeb --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/type/PrimitiveWrappers.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.internal.util.type; + +/** + * Maps primitive types to their wrapper counterparts. + * + * @author Gavin King + */ +public final class PrimitiveWrappers { + + public static Class canonicalize(Class type) { + if ( type.isPrimitive() ) { + @SuppressWarnings("unchecked") + // completely safe, boolean.class is a Class + final var wrapperClass = (Class) wrapperClass( type ); + return wrapperClass; + } + else { + return type; + } + } + + private static Class wrapperClass(Class primitiveClass) { + return switch ( primitiveClass.getName() ) { + case "boolean" -> Boolean.class; + case "char" -> Character.class; + case "byte" -> Byte.class; + case "short" -> Short.class; + case "int" -> Integer.class; + case "long" -> Long.class; + case "float" -> Float.class; + case "double" -> Double.class; + default -> throw new AssertionError( "Unknown primitive type: " + primitiveClass ); + }; + } + + public static X cast(Class type, Object value) { + return canonicalize( type ).cast( value ); + } + + public static boolean isInstance(Class type, Object value) { + return canonicalize( type ).isInstance( value ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/jdbc/Expectation.java b/hibernate-core/src/main/java/org/hibernate/jdbc/Expectation.java index 941eb5e65d97..3e29db80f41e 100644 --- a/hibernate-core/src/main/java/org/hibernate/jdbc/Expectation.java +++ b/hibernate-core/src/main/java/org/hibernate/jdbc/Expectation.java @@ -179,6 +179,31 @@ protected int expectedRowCount() { } } + /** + * Row count checking. A row count is an integer value returned by + * {@link java.sql.PreparedStatement#executeUpdate()} or + * {@link java.sql.Statement#executeBatch()}. The row count is checked + * against an expected value, but is also allowed to be 0. + * For example, the expected row count for an {@code UPSERT} statement is 0 or 1. + */ + class OptionalRowCount implements Expectation { + @Override + public final void verifyOutcome(int rowCount, PreparedStatement statement, int batchPosition, String sql) { + if ( rowCount != 0 ) { + if ( batchPosition < 0 ) { + checkNonBatched( expectedRowCount(), rowCount, sql ); + } + else { + checkBatched( expectedRowCount(), rowCount, batchPosition, sql ); + } + } + } + + protected int expectedRowCount() { + return 1; + } + } + /** * Essentially identical to {@link RowCount} except that the row count * is obtained via an output parameter of a {@linkplain CallableStatement diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java index 8d82e0320a57..e51ea8284b4b 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java @@ -1304,8 +1304,7 @@ private void addMappingFiles(MetadataSources metadataSources) { } @SuppressWarnings("unchecked") // Safe, because we just checked! final var attributeConverterType = (Class>) converterClass; - converterDescriptors.add( ConverterDescriptors.of( attributeConverterType, - metamodelBuilder.getBootstrapContext().getClassmateContext() ) ); + converterDescriptors.add( ConverterDescriptors.of( attributeConverterType ) ); } else { metadataSources.addAnnotatedClass( converterClass ); @@ -1515,8 +1514,8 @@ private String exceptionHeader() { @SuppressWarnings("unchecked") private T loadSettingInstance(String settingName, Object settingValue, Class clazz) { final Class instanceClass; - if ( clazz.isAssignableFrom( settingValue.getClass() ) ) { - return (T) settingValue; + if ( clazz.isInstance( settingValue ) ) { + return clazz.cast( settingValue ); } else if ( settingValue instanceof Class ) { instanceClass = (Class) settingValue; diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/JpaCompliance.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/JpaCompliance.java index 630800df6e7b..af90dae005bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/spi/JpaCompliance.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/JpaCompliance.java @@ -123,7 +123,7 @@ public interface JpaCompliance { * {@link jakarta.persistence.EntityManager#find} should be exactly the * expected type, allowing no type coercion. *

    - * Historically, Hibernate behaved the same way. Since 6.0 however, + * Historically, Hibernate behaved the same way. Since 6.0, however, * Hibernate has the ability to coerce the passed type to the expected * type. For example, an {@link Integer} may be widened to {@link Long}. * Coercion is performed by calling diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java index 24e8f5f49fbb..c80d4d555522 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java @@ -20,6 +20,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.exec.spi.JdbcSelectExecutor; +import org.hibernate.type.descriptor.java.JavaType; import java.util.ArrayList; import java.util.List; @@ -39,11 +40,17 @@ public abstract class AbstractMultiIdEntityLoader implements MultiIdEntityLoa private final EntityMappingType entityDescriptor; private final SessionFactoryImplementor sessionFactory; protected final EntityIdentifierMapping identifierMapping; + private final boolean idCoercionEnabled; public AbstractMultiIdEntityLoader(EntityMappingType entityDescriptor, SessionFactoryImplementor sessionFactory) { this.entityDescriptor = entityDescriptor; this.sessionFactory = sessionFactory; identifierMapping = getLoadable().getIdentifierMapping(); + idCoercionEnabled = + !sessionFactory.getSessionFactoryOptions() + .getJpaCompliance().isLoadByIdComplianceEnabled() + // special handling for entity with @IdClass + && !entityDescriptor.getIdentifierMapping().isVirtual(); } protected EntityMappingType getEntityDescriptor() { @@ -111,7 +118,6 @@ private List orderedMultiLoad( Object[] ids, MultiIdLoadOptions loadOptions, SharedSessionContractImplementor session) { - final boolean idCoercionEnabled = isIdCoercionEnabled(); final var idType = getLoadable().getIdentifierMapping().getJavaType(); final int maxBatchSize = maxBatchSize( ids, loadOptions ); @@ -124,7 +130,7 @@ private List orderedMultiLoad( final var lockOptions = lockOptions( loadOptions ); for ( int i = 0; i < ids.length; i++ ) { - final Object id = idCoercionEnabled ? idType.coerce( ids[i], session ) : ids[i]; + final Object id = coerce( idType, ids[i] ); final var entityKey = new EntityKey( id, getLoadable().getEntityPersister() ); if ( !loadFromEnabledCaches( loadOptions, session, id, lockOptions, entityKey, results, i ) ) { // if we did not hit any of the continues above, @@ -154,10 +160,13 @@ private List orderedMultiLoad( return (List) results; } + private Object coerce(JavaType idType, Object id) { + return idCoercionEnabled ? idType.coerce( id ) : id; + } + private static LockOptions lockOptions(MultiIdLoadOptions loadOptions) { - return loadOptions.getLockOptions() == null - ? new LockOptions( LockMode.NONE ) - : loadOptions.getLockOptions(); + final var lockOptions = loadOptions.getLockOptions(); + return lockOptions == null ? new LockOptions( LockMode.NONE ) : lockOptions; } protected abstract int maxBatchSize(Object[] ids, MultiIdLoadOptions loadOptions); @@ -169,8 +178,8 @@ private void handleResults( List results) { final var persistenceContext = session.getPersistenceContext(); for ( Integer position : elementPositionsLoadedByBatch ) { - // the element value at this position in the results List should be - // the EntityKey for that entity - reuse it + // the element value at this position in the results List + // should be the EntityKey for that entity - reuse it final var entityKey = (EntityKey) results.get( position ); session.getPersistenceContextInternal().getBatchFetchQueue().removeBatchLoadableEntityKey( entityKey ); final Object entity = persistenceContext.getEntity( entityKey ); @@ -203,7 +212,7 @@ private boolean loadFromEnabledCaches( EntityKey entityKey, List result, int i) { - return (loadOptions.getSessionCheckMode() == SessionCheckMode.ENABLED + return ( loadOptions.getSessionCheckMode() == SessionCheckMode.ENABLED || loadOptions.isSecondLevelCacheCheckingEnabled() ) && isLoadFromCaches( loadOptions, entityKey, lockOptions, result, i, session ); } @@ -214,15 +223,19 @@ private boolean isLoadFromCaches( LockOptions lockOptions, List results, int i, SharedSessionContractImplementor session) { - if ( loadOptions.getSessionCheckMode() == SessionCheckMode.ENABLED ) { + final var removalsMode = loadOptions.getRemovalsMode(); + if ( removalsMode == RemovalsMode.EXCLUDE ) { + // note, this method is only called from orderedMultiLoad() + throw new IllegalArgumentException( "RemovalsMode.EXCLUDE is incompatible with OrderingMode.ORDERED" ); + } // look for it in the Session first final var entry = loadFromSessionCache( entityKey, lockOptions, GET, session ); final Object entity = entry.entity(); if ( entity != null ) { // put a null in the results final Object result; - if ( loadOptions.getRemovalsMode() == RemovalsMode.INCLUDE + if ( removalsMode == RemovalsMode.INCLUDE || entry.isManaged() ) { result = entity; } @@ -330,11 +343,10 @@ private List unresolvedIds( LockOptions lockOptions, SharedSessionContractImplementor session, ResolutionConsumer resolutionConsumer) { - final boolean idCoercionEnabled = isIdCoercionEnabled(); final var idType = getLoadable().getIdentifierMapping().getJavaType(); List unresolvedIds = null; for ( int i = 0; i < ids.length; i++ ) { - final Object id = idCoercionEnabled ? idType.coerce( ids[i], session ) : ids[i]; + final Object id = coerce( idType, ids[i] ); unresolvedIds = loadFromCaches( loadOptions, @@ -353,10 +365,6 @@ private List unresolvedIds( // Depending on the implementation, a specific subtype of Object[] (e.g. Integer[]) may be needed. protected abstract Object[] toIdArray(List ids); - private boolean isIdCoercionEnabled() { - return !getSessionFactory().getSessionFactoryOptions().getJpaCompliance().isLoadByIdComplianceEnabled(); - } - public interface ResolutionConsumer { void consume(int position, EntityKey entityKey, T resolvedRef); } @@ -373,13 +381,16 @@ private List loadFromCaches( // look for it in the Session first final var entry = loadFromSessionCache( entityKey, lockOptions, GET, session ); final Object sessionEntity; - if ( loadOptions.getSessionCheckMode() == SessionCheckMode.ENABLED ) { + if ( loadOptions.getSessionCheckMode() == SessionCheckMode.ENABLED ) { sessionEntity = entry.entity(); - if ( sessionEntity != null - && loadOptions.getRemovalsMode() == RemovalsMode.REPLACE - && !entry.isManaged() ) { - resolutionConsumer.consume( i, entityKey, null ); - return unresolvedIds; + if ( sessionEntity != null && !entry.isManaged() ) { + switch ( loadOptions.getRemovalsMode() ) { + case REPLACE : + resolutionConsumer.consume( i, entityKey, null ); + return unresolvedIds; + case EXCLUDE: + return unresolvedIds; + } } } else { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiNaturalIdLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiNaturalIdLoader.java index 8ac139d2d38d..f3dee6a789d0 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiNaturalIdLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiNaturalIdLoader.java @@ -129,7 +129,7 @@ private List sortResults( return results; } - private Object entityForNaturalId(PersistenceContext context, K naturalId) { + private Object entityForNaturalId(PersistenceContext context, Object naturalId) { final var descriptor = getEntityDescriptor(); final Object id = context.getNaturalIdResolutions().findCachedIdByNaturalId( naturalId, descriptor ); // id can be null if a non-existent natural id is requested, or a mutable natural id was changed and then deleted @@ -142,20 +142,26 @@ private Object[] checkPersistenceContextForCachedResults( SharedSessionContractImplementor session, LockOptions lockOptions, Consumer results ) { + final var removalsMode = loadOptions.getRemovalsMode(); + if ( removalsMode == RemovalsMode.EXCLUDE + && loadOptions.getOrderingMode() == OrderingMode.ORDERED ) { + throw new IllegalArgumentException( "RemovalsMode.EXCLUDE is incompatible with OrderingMode.ORDERED" ); + } final List unresolvedIds = arrayList( naturalIds.length ); final var context = session.getPersistenceContextInternal(); - final var naturalIdMapping = getEntityDescriptor().getNaturalIdMapping(); for ( K naturalId : naturalIds ) { - final Object entity = entityForNaturalId( context, naturalIdMapping.normalizeInput( naturalId ) ); + final Object entity = entityForNaturalId( context, naturalId ); if ( entity != null ) { // Entity is already in the persistence context final var entry = context.getEntry( entity ); - if ( loadOptions.getRemovalsMode() == RemovalsMode.INCLUDE + if ( removalsMode == RemovalsMode.INCLUDE || !entry.getStatus().isDeletedOrGone() ) { // either a managed entry, or a deleted one with returnDeleted enabled upgradeLock( entity, entry, lockOptions, session ); - final Object result = context.proxyFor( entity ); - results.accept( (E) result ); + if ( removalsMode != RemovalsMode.EXCLUDE ) { + final Object result = context.proxyFor( entity ); + results.accept( (E) result ); + } } } else { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractNaturalIdLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractNaturalIdLoader.java index 600074f7b0e3..ce7b633e34cd 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractNaturalIdLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractNaturalIdLoader.java @@ -5,7 +5,10 @@ package org.hibernate.loader.ast.internal; import org.hibernate.HibernateException; +import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.Locking; +import org.hibernate.Timeouts; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -32,7 +35,6 @@ import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.BaseExecutionContext; import org.hibernate.sql.exec.internal.CallbackImpl; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.SqlTypedMappingJdbcParameter; @@ -40,6 +42,7 @@ import org.hibernate.sql.exec.spi.JdbcParameterBinding; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.graph.internal.ImmutableFetchList; import org.hibernate.sql.results.internal.RowTransformerSingularReturnImpl; import org.hibernate.sql.results.spi.ListResultsConsumer; @@ -86,13 +89,28 @@ public EntityMappingType getLoadable() { } @Override - public T load(Object naturalIdValue, NaturalIdLoadOptions options, SharedSessionContractImplementor session) { - final var factory = session.getFactory(); + public T load(Object naturalIdToLoad, Options options, SharedSessionContractImplementor session) { + final var lockOptions = makeLockOptions( options ); + return load( naturalIdToLoad, lockOptions, session ); + } + + private LockOptions makeLockOptions(Options options) { + if ( options.getLockMode() == null || options.getLockMode() == LockMode.NONE ) { + return LockOptions.NONE; + } + if ( options.getLockMode() == LockMode.READ ) { + return LockOptions.READ; + } + + final var lockOptions = new LockOptions( options.getLockMode() ); + lockOptions.setScope( options.getLockScope() != null ? options.getLockScope() : Locking.Scope.ROOT_ONLY ); + lockOptions.setTimeout( options.getLockTimeout() != null ? options.getLockTimeout() : Timeouts.WAIT_FOREVER ); + lockOptions.setFollowOnStrategy( options.getLockFollowOn() != null ? options.getLockFollowOn() : Locking.FollowOn.ALLOW ); + return lockOptions; + } - final var lockOptions = - options.getLockOptions() == null - ? new LockOptions() - : options.getLockOptions(); + private T load(Object naturalIdValue, LockOptions lockOptions, SharedSessionContractImplementor session) { + final var factory = session.getFactory(); final var sqlSelect = LoaderSelectBuilder.createSelect( getLoadable(), @@ -128,6 +146,14 @@ public T load(Object naturalIdValue, NaturalIdLoadOptions options, SharedSession ); } + @Override + public T load(Object naturalIdValue, NaturalIdLoadOptions options, SharedSessionContractImplementor session) { + final var lockOptions = options.getLockOptions() == null + ? new LockOptions() + : options.getLockOptions(); + return load( naturalIdValue, lockOptions, session ); + } + /** * Apply restriction necessary to match the given natural-id value. * Should also apply any predicates to the predicate consumer and @@ -148,9 +174,10 @@ protected Expression resolveColumnReference( TableGroup rootTableGroup, SelectableMapping selectableMapping, SqlExpressionResolver sqlExpressionResolver) { - final var tableReference = - rootTableGroup.getTableReference( rootTableGroup.getNavigablePath(), - selectableMapping.getContainingTableExpression() ); + final var tableReference = rootTableGroup.getTableReference( + rootTableGroup.getNavigablePath(), + selectableMapping.getContainingTableExpression() + ); if ( tableReference == null ) { throw new IllegalStateException( String.format( @@ -179,17 +206,16 @@ public Object resolveNaturalIdToId(Object naturalIdValue, SharedSessionContractI final var entityPath = new NavigablePath( entityDescriptor.getRootPathName() ); final var rootQuerySpec = new QuerySpec( true ); - final var sqlAstCreationState = - new LoaderSqlAstCreationState( - rootQuerySpec, - new SqlAliasBaseManager(), - new SimpleFromClauseAccessImpl(), - LockOptions.NONE, - (fetchParent, creationState) -> ImmutableFetchList.EMPTY, - true, - new LoadQueryInfluencers( factory ), - factory.getSqlTranslationEngine() - ); + final var sqlAstCreationState = new LoaderSqlAstCreationState( + rootQuerySpec, + new SqlAliasBaseManager(), + new SimpleFromClauseAccessImpl(), + LockOptions.NONE, + (fetchParent, creationState) -> ImmutableFetchList.EMPTY, + true, + new LoadQueryInfluencers( factory ), + factory.getSqlTranslationEngine() + ); final var rootTableGroup = entityDescriptor.createRootTableGroup( true, @@ -231,12 +257,13 @@ protected R executeNaturalIdQuery( Consumer predicateConsumer, LoaderSqlAstCreationState sqlAstCreationState, SharedSessionContractImplementor session) { + assert naturalIdMapping.isNormalized( naturalIdValue ); + final var factory = session.getFactory(); - final var bindings = - new JdbcParameterBindingsImpl( naturalIdMapping.getJdbcTypeCount() ); + final var bindings = new JdbcParameterBindingsImpl( naturalIdMapping.getJdbcTypeCount() ); applyNaturalIdRestriction( - naturalIdMapping().normalizeInput( naturalIdValue ), + naturalIdValue, rootTableGroup, predicateConsumer, bindings::addBinding, @@ -282,7 +309,7 @@ protected R executeNaturalIdQuery( }; } - private static JdbcOperationQuerySelect createJdbcOperationQuerySelect( + private static JdbcSelect createJdbcOperationQuerySelect( SelectStatement sqlSelect, SessionFactoryImplementor factory, JdbcParameterBindings bindings, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionBatchLoaderArrayParam.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionBatchLoaderArrayParam.java index 3045c41a60d5..0e4ad73fb5b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionBatchLoaderArrayParam.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionBatchLoaderArrayParam.java @@ -25,9 +25,9 @@ import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.SqlTypedMappingJdbcParameter; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; import org.hibernate.sql.results.spi.ListResultsConsumer; @@ -48,7 +48,7 @@ public class CollectionBatchLoaderArrayParam private final SqlTypedMapping arraySqlTypedMapping; private final JdbcParameter jdbcParameter; private final SelectStatement sqlSelect; - private final JdbcOperationQuerySelect jdbcSelectOperation; + private final JdbcSelect jdbcSelectOperation; public CollectionBatchLoaderArrayParam( int domainBatchSize, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionBatchLoaderInPredicate.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionBatchLoaderInPredicate.java index efea2c40a173..e8177fae30a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionBatchLoaderInPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionBatchLoaderInPredicate.java @@ -14,8 +14,8 @@ import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.query.spi.QueryOptions; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; import static org.hibernate.loader.ast.internal.MultiKeyLoadHelper.countIds; import static org.hibernate.loader.ast.internal.MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER; @@ -34,7 +34,7 @@ public class CollectionBatchLoaderInPredicate private final int sqlBatchSize; private final JdbcParametersList jdbcParameters; private final SelectStatement sqlAst; - private final JdbcOperationQuerySelect jdbcSelect; + private final JdbcSelect jdbcSelect; public CollectionBatchLoaderInPredicate( int domainBatchSize, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSingleKey.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSingleKey.java index 4ff1caf44f74..38a1bf24dab0 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSingleKey.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSingleKey.java @@ -18,8 +18,8 @@ import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.BaseExecutionContext; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; import org.hibernate.sql.results.spi.ListResultsConsumer; @@ -36,7 +36,7 @@ public class CollectionLoaderSingleKey implements CollectionLoader { private final int keyJdbcCount; private final SelectStatement sqlAst; - private final JdbcOperationQuerySelect jdbcSelect; + private final JdbcSelect jdbcSelect; private final JdbcParametersList jdbcParameters; public CollectionLoaderSingleKey( diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/DatabaseSnapshotExecutor.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/DatabaseSnapshotExecutor.java index c676d11b812d..81ce23b7d327 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/DatabaseSnapshotExecutor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/DatabaseSnapshotExecutor.java @@ -4,9 +4,6 @@ */ package org.hibernate.loader.ast.internal; -import java.util.ArrayList; -import java.util.List; - import org.hibernate.LockOptions; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -25,14 +22,17 @@ import org.hibernate.sql.exec.internal.BaseExecutionContext; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.SqlTypedMappingJdbcParameter; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.internal.ImmutableFetchList; import org.hibernate.sql.results.internal.RowTransformerArrayImpl; import org.hibernate.sql.results.spi.ListResultsConsumer; import org.hibernate.type.StandardBasicTypes; +import java.util.ArrayList; +import java.util.List; + import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_OBJECT_ARRAY; import static org.hibernate.loader.LoaderLogging.LOADER_LOGGER; import static org.hibernate.pretty.MessageHelper.infoString; @@ -44,7 +44,7 @@ class DatabaseSnapshotExecutor { private final EntityMappingType entityDescriptor; - private final JdbcOperationQuerySelect jdbcSelect; + private final JdbcSelect jdbcSelect; private final JdbcParametersList jdbcParameters; DatabaseSnapshotExecutor( diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/EntityBatchLoaderArrayParam.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/EntityBatchLoaderArrayParam.java index 09322aa83d7d..bf009b055dc9 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/EntityBatchLoaderArrayParam.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/EntityBatchLoaderArrayParam.java @@ -4,8 +4,6 @@ */ package org.hibernate.loader.ast.internal; -import java.util.Locale; - import org.hibernate.LockOptions; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -21,7 +19,9 @@ import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.SqlTypedMappingJdbcParameter; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.spi.JdbcSelect; + +import java.util.Locale; import static org.hibernate.loader.ast.internal.LoaderHelper.loadByArrayParameter; import static org.hibernate.loader.ast.internal.MultiKeyLoadHelper.trimIdBatch; @@ -44,7 +44,7 @@ public class EntityBatchLoaderArrayParam private final SqlTypedMapping arraySqlTypedMapping; private final JdbcParameter jdbcParameter; private final SelectStatement sqlAst; - private final JdbcOperationQuerySelect jdbcSelectOperation; + private final JdbcSelect jdbcSelectOperation; /** diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/EntityBatchLoaderInPredicate.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/EntityBatchLoaderInPredicate.java index 78dc80db9297..c594ad2d3510 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/EntityBatchLoaderInPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/EntityBatchLoaderInPredicate.java @@ -4,8 +4,6 @@ */ package org.hibernate.loader.ast.internal; -import java.util.Locale; - import org.hibernate.LockOptions; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -16,8 +14,10 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.query.spi.QueryOptions; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; + +import java.util.Locale; import static org.hibernate.loader.ast.internal.MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER; import static org.hibernate.pretty.MessageHelper.infoString; @@ -41,7 +41,7 @@ public class EntityBatchLoaderInPredicate private final JdbcParametersList jdbcParameters; private final SelectStatement sqlAst; - private final JdbcOperationQuerySelect jdbcSelectOperation; + private final JdbcSelect jdbcSelectOperation; /** * @param domainBatchSize The maximum number of entities we will initialize for each load diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderHelper.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderHelper.java index 77fc6ac9a14a..192fd6b4766c 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderHelper.java @@ -4,8 +4,6 @@ */ package org.hibernate.loader.ast.internal; -import java.util.List; - import org.hibernate.Hibernate; import org.hibernate.LockMode; import org.hibernate.LockOptions; @@ -26,12 +24,14 @@ import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; import org.hibernate.sql.results.spi.ListResultsConsumer; import org.hibernate.type.descriptor.java.JavaType; +import java.util.List; + import static java.lang.System.arraycopy; import static java.lang.reflect.Array.newInstance; import static org.hibernate.loader.LoaderLogging.LOADER_LOGGER; @@ -188,7 +188,7 @@ public static K[] normalizeKeys( } else { for ( int i = 0; i < keys.length; i++ ) { - typedArray[i] = keyJavaType.coerce( keys[i], session ); + typedArray[i] = keyJavaType.cast( keyJavaType.coerce( keys[i] ) ); } } return typedArray; @@ -217,7 +217,7 @@ public static X[] createTypedArray(Class elementClass, @SuppressWarnings( public static List loadByArrayParameter( K[] idsToInitialize, SelectStatement sqlAst, - JdbcOperationQuerySelect jdbcOperation, + JdbcSelect jdbcOperation, JdbcParameter jdbcParameter, JdbcMapping arrayJdbcMapping, Object entityId, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiKeyLoadChunker.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiKeyLoadChunker.java index 18b8bbc55bf6..c450d5406e75 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiKeyLoadChunker.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiKeyLoadChunker.java @@ -10,9 +10,9 @@ import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; import org.hibernate.sql.results.spi.ManagedResultConsumer; @@ -52,7 +52,7 @@ interface ChunkBoundaryListener { private final JdbcParametersList jdbcParameters; private final SelectStatement sqlAst; - private final JdbcOperationQuerySelect jdbcSelect; + private final JdbcSelect jdbcSelect; public MultiKeyLoadChunker( int chunkSize, @@ -60,7 +60,7 @@ public MultiKeyLoadChunker( Bindable bindable, JdbcParametersList jdbcParameters, SelectStatement sqlAst, - JdbcOperationQuerySelect jdbcSelect) { + JdbcSelect jdbcSelect) { this.chunkSize = chunkSize; this.keyColumnCount = keyColumnCount; this.bindable = bindable; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java index d9d723a73a18..730149aee2b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java @@ -4,9 +4,6 @@ */ package org.hibernate.loader.ast.internal; -import java.util.ArrayList; -import java.util.List; - import org.hibernate.LockOptions; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -17,12 +14,15 @@ import org.hibernate.query.spi.QueryOptionsAdapter; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; import org.hibernate.sql.results.spi.ListResultsConsumer; +import java.util.ArrayList; +import java.util.List; + import static org.hibernate.internal.util.collections.CollectionHelper.arrayList; /** @@ -49,7 +49,7 @@ interface KeyValueResolver { private final KeyValueResolver keyValueResolver; - private final JdbcOperationQuerySelect jdbcSelect; + private final JdbcSelect jdbcSelect; private final LockOptions lockOptions; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java index 166709179f7e..ccd7355e7ef7 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java @@ -4,8 +4,6 @@ */ package org.hibernate.loader.ast.internal; -import java.util.List; - import org.hibernate.HibernateException; import org.hibernate.LockOptions; import org.hibernate.engine.spi.LoadQueryInfluencers; @@ -26,12 +24,14 @@ import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.Callback; import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.internal.RowTransformerSingularReturnImpl; import org.hibernate.sql.results.spi.ListResultsConsumer; +import java.util.List; + import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; @@ -43,7 +43,7 @@ public class SingleUniqueKeyEntityLoaderStandard implements SingleUniqueKeyEn private final ModelPart uniqueKeyAttribute; private final String uniqueKeyAttributePath; private final JdbcParametersList jdbcParameters; - private final JdbcOperationQuerySelect jdbcSelect; + private final JdbcSelect jdbcSelect; public SingleUniqueKeyEntityLoaderStandard( EntityMappingType entityDescriptor, @@ -147,7 +147,7 @@ private JdbcParameterBindings jdbcParameterBindings( } private static List list( - JdbcOperationQuerySelect jdbcSelect, + JdbcSelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { return executionContext.getSession().getJdbcServices().getJdbcSelectExecutor() @@ -162,7 +162,7 @@ private static List list( ); } - private static JdbcOperationQuerySelect getJdbcSelect + private static JdbcSelect getJdbcSelect (SessionFactoryImplementor factory, SelectStatement sqlAst, JdbcParameterBindings jdbcParameterBindings) { return factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory() .buildSelectTranslator( factory, sqlAst ) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiIdEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiIdEntityLoader.java index 0063236946a9..48e5199b96a9 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiIdEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiIdEntityLoader.java @@ -4,13 +4,15 @@ */ package org.hibernate.loader.ast.spi; -import java.util.List; - import org.hibernate.engine.spi.SharedSessionContractImplementor; -/** - * Loader subtype for loading multiple entities by multiple identifier values. - */ +import java.util.List; + +/// EntityMultiLoader implementation based on [identifier][org.hibernate.KeyType#IDENTIFIER]. +/// +/// @see org.hibernate.Session#findMultiple +/// +/// @author Steve Ebersole public interface MultiIdEntityLoader extends EntityMultiLoader { /** * Load multiple entities by id. The exact result depends on the passed options. diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiIdLoadOptions.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiIdLoadOptions.java index b1417d1ff3e5..eab7df01fe53 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiIdLoadOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiIdLoadOptions.java @@ -7,9 +7,14 @@ import org.hibernate.SessionCheckMode; import org.hibernate.engine.spi.SessionImplementor; -/** - * Encapsulation of the options for loading multiple entities by id - */ + +/// Encapsulation of the options for loading multiple entities (of a type) +/// by [id][org.hibernate.KeyType#IDENTIFIER]. +/// +/// @see org.hibernate.Session#findMultiple +/// @see MultiIdEntityLoader +/// +/// @author Steve Ebersole public interface MultiIdLoadOptions extends MultiLoadOptions { /** * Controls whether to check the current status of each identified entity diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiLoadOptions.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiLoadOptions.java index 2279ad8279c8..c84ad7039880 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiLoadOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiLoadOptions.java @@ -8,9 +8,13 @@ import org.hibernate.OrderingMode; import org.hibernate.RemovalsMode; -/** - * Base contract for options for multi-load operations - */ +/// Encapsulation of the options for loading multiple entities (of a type) +/// by [key][org.hibernate.KeyType]. +/// +/// @see MultiIdLoadOptions +/// @see MultiNaturalIdLoadOptions +/// +/// @author Steve Ebersole public interface MultiLoadOptions { /** * How should entities in removed status be handled. diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiNaturalIdLoadOptions.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiNaturalIdLoadOptions.java index 48ce0b5dc67b..f8d635636546 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiNaturalIdLoadOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiNaturalIdLoadOptions.java @@ -4,8 +4,11 @@ */ package org.hibernate.loader.ast.spi; -/** - * Encapsulation of the options for loading multiple entities by natural-id - */ +/// Encapsulation of the options for loading multiple entities (of a type) +/// by [natural-id][org.hibernate.KeyType#NATURAL]. +/// +/// @see MultiNaturalIdLoader +/// +/// @author Steve Ebersole public interface MultiNaturalIdLoadOptions extends MultiLoadOptions { } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiNaturalIdLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiNaturalIdLoader.java index 4aa96b303666..7f3c6bb4f8f3 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiNaturalIdLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiNaturalIdLoader.java @@ -4,25 +4,25 @@ */ package org.hibernate.loader.ast.spi; -import java.util.List; - import org.hibernate.engine.spi.SharedSessionContractImplementor; -/** - * Loader for entities by multiple natural-ids - * - * @param The entity Java type - */ +import java.util.List; + +/// EntityMultiLoader implementation based on [identifier][org.hibernate.KeyType#NATURAL]. +/// +/// @param The entity Java type +/// +/// @see org.hibernate.Session#findMultiple +/// +/// @author Steve Ebersole public interface MultiNaturalIdLoader extends EntityMultiLoader { - /** - * Load multiple entities by natural-id. The exact result depends on the passed options. - * - * @param naturalIds The natural-ids to load. The values of this array will depend on whether the - * natural-id is simple or complex. - * - * @param The basic form for a natural-id is a Map of its attribute values, or an array of the - * values positioned according to "attribute ordering". Simple natural-ids can also be expressed - * by their simple (basic/embedded) type. - */ + /// Load multiple entities by natural-id. The exact result depends on the passed options. + /// + /// @param naturalIds The natural-ids to load. The values of this array will depend on whether the + /// natural-id is simple or complex. + /// + /// @param The basic form for a natural-id is a Map of its attribute values, or an array of the + /// values positioned according to "attribute ordering". Simple natural-ids can also be expressed + /// by their simple (basic/embedded) type. List multiLoad(K[] naturalIds, MultiNaturalIdLoadOptions options, SharedSessionContractImplementor session); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/NaturalIdLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/NaturalIdLoader.java index 9333140979de..388c4e52a0d4 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/NaturalIdLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/NaturalIdLoader.java @@ -4,30 +4,46 @@ */ package org.hibernate.loader.ast.spi; +import jakarta.persistence.Timeout; +import org.hibernate.LockMode; +import org.hibernate.Locking; import org.hibernate.engine.spi.SharedSessionContractImplementor; -/** - * Loader for {@link org.hibernate.annotations.NaturalId} handling - * - * @author Steve Ebersole - */ +/// Loader for [org.hibernate.annotations.NaturalId] +/// +/// @author Steve Ebersole public interface NaturalIdLoader extends EntityLoader, MultiKeyLoader { + interface Options { + LockMode getLockMode(); + Timeout getLockTimeout(); + Locking.Scope getLockScope(); + Locking.FollowOn getLockFollowOn(); + } - /** - * Perform the load of the entity by its natural-id - * - * @param naturalIdToLoad The natural-id to load. One of 2 forms accepted: - * * Single-value - valid for entities with a simple (single-valued) - * natural-id - * * Map - valid for any natural-id load. The map is each value keyed - * by the attribute name that the value corresponds to. Even though - * this form is allowed for simple natural-ids, the single value form - * should be used as it is more efficient - * @param options The options to apply to the load operation - * @param session The session into which the entity is being loaded - */ + /// Perform the load of the entity by its natural-id + /// + /// @param naturalIdToLoad The natural-id to load. One of 2 forms accepted: + /// * Single-value - valid for entities with a simple (single-valued) + /// natural-id + /// * Map - valid for any natural-id load. The map is each value keyed + /// by the attribute name that the value corresponds to. Even though + /// this form is allowed for simple natural-ids, the single value form + /// should be used as it is more efficient + /// @param options The options to apply to the load operation + /// @param session The session into which the entity is being loaded + /// + /// @deprecated (since 7.3) : use [#load(Object, Options, SharedSessionContractImplementor)] instead. + @Deprecated T load(Object naturalIdToLoad, NaturalIdLoadOptions options, SharedSessionContractImplementor session); + /// Perform the load of the entity by its natural-id + /// + /// @param naturalIdToLoad The [normalized][org.hibernate.metamodel.mapping.NaturalIdMapping#normalizeInput] + /// form of the natural-id. + /// @param options The options to apply to the load operation + /// @param session The session into which the entity is being loaded + T load(Object naturalIdToLoad, Options options, SharedSessionContractImplementor session); + /** * Resolve the id from natural-id value */ diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/BaseNaturalIdLoadAccessImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/BaseNaturalIdLoadAccessImpl.java index 14685b1c9ba7..1de0d68f623f 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/BaseNaturalIdLoadAccessImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/BaseNaturalIdLoadAccessImpl.java @@ -15,6 +15,7 @@ import org.hibernate.IdentifierLoadAccess; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.Locking; import org.hibernate.UnknownProfileException; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; @@ -70,6 +71,14 @@ protected Object with(LockMode lockMode, PessimisticLockScope lockScope) { return this; } + protected Object with(Locking.Scope lockScope) { + if ( lockOptions == null ) { + lockOptions = new LockOptions(); + } + lockOptions.setScope( lockScope ); + return this; + } + protected Object with(Timeout timeout) { if ( lockOptions == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/IdentifierLoadAccessImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/IdentifierLoadAccessImpl.java index f8d38d956b43..6bb5a3423a2a 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/IdentifierLoadAccessImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/IdentifierLoadAccessImpl.java @@ -26,8 +26,6 @@ import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.spi.TypeConfiguration; import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; @@ -37,7 +35,7 @@ * @author Steve Ebersole */ // Hibernate Reactive extends this class: see ReactiveIdentifierLoadAccessImpl -public class IdentifierLoadAccessImpl implements IdentifierLoadAccess, JavaType.CoercionContext { +public class IdentifierLoadAccessImpl implements IdentifierLoadAccess { private final LoadAccessContext context; private final EntityPersister entityPersister; @@ -196,7 +194,10 @@ protected Object coerceId(Object id, SessionFactoryImplementor factory) { } else { try { - return entityPersister.getIdentifierMapping().getJavaType().coerce( id, this ); + final var identifierMapping = entityPersister.getIdentifierMapping(); + return identifierMapping.isVirtual() + ? id // special case for a class with an @IdClass + : identifierMapping.getJavaType().coerce( id ); } catch ( Exception e ) { throw new IllegalArgumentException( "Argument '" + id @@ -230,11 +231,6 @@ private static boolean isLoadByIdComplianceEnabled(SessionFactoryImplementor fac return factory.getSessionFactoryOptions().getJpaCompliance().isLoadByIdComplianceEnabled(); } - @Override - public TypeConfiguration getTypeConfiguration() { - return context.getSession().getSessionFactory().getTypeConfiguration(); - } - @Override public IdentifierLoadAccess enableFetchProfile(String profileName) { if ( !context.getSession().getFactory().containsFetchProfileDefinition( profileName ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/SimpleNaturalIdLoadAccessImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/SimpleNaturalIdLoadAccessImpl.java index f041771aaa06..c083911ffed7 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/SimpleNaturalIdLoadAccessImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/SimpleNaturalIdLoadAccessImpl.java @@ -16,10 +16,12 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.Locking; import org.hibernate.SimpleNaturalIdLoadAccess; import org.hibernate.graph.GraphSemantic; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.internal.SimpleNaturalIdMapping; +import org.hibernate.persister.entity.EntityPersister; /** * Implementation of {@link SimpleNaturalIdLoadAccess}. @@ -57,6 +59,11 @@ public SimpleNaturalIdLoadAccess with(LockMode lockMode, PessimisticLockScope return (SimpleNaturalIdLoadAccess) super.with( lockMode, lockScope ); } + public SimpleNaturalIdLoadAccess with(Locking.Scope lockScope) { + super.with( lockScope ); + return this; + } + @Override public SimpleNaturalIdLoadAccess with(Timeout timeout) { //noinspection unchecked @@ -99,7 +106,8 @@ private void verifySimplicity(Object naturalIdValue) { if ( !hasSimpleNaturalId && !naturalIdValue.getClass().isArray() && !(naturalIdValue instanceof List) - && !(naturalIdValue instanceof Map) ) { + && !(naturalIdValue instanceof Map) + && ! ( isNaturalIdClass( naturalIdValue ) ) ) { throw new HibernateException( String.format( Locale.ROOT, @@ -111,6 +119,11 @@ private void verifySimplicity(Object naturalIdValue) { } } + private boolean isNaturalIdClass(Object naturalIdValue) { + final EntityPersister entityPersister = entityPersister(); + return entityPersister.getNaturalIdMapping().getNaturalIdClass().isInstance( naturalIdValue ); + } + @Override public Optional loadOptional(Object naturalIdValue) { return Optional.ofNullable( load( naturalIdValue ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Any.java b/hibernate-core/src/main/java/org/hibernate/mapping/Any.java index 71ca1269e581..d9f28b6a10d5 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Any.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Any.java @@ -157,7 +157,7 @@ public void addFormula(Formula formula) { private void applySelectableLocally(Selectable selectable) { // note: adding column to meta or key mapping ultimately calls back into `#applySelectableToSuper` // to add the column to the ANY super. - if ( discriminatorDescriptor == null && getColumnSpan() == 0 ) { + if ( discriminatorDescriptor == null && !hasColumns() ) { if ( selectable instanceof Column column ) { metaMapping.addColumn( column ); } @@ -247,7 +247,7 @@ public boolean isValid(MappingContext mappingContext) throws MappingException { } private static String columnName(Column column, MetadataBuildingContext buildingContext) { - final JdbcServices jdbcServices = + final var jdbcServices = buildingContext.getBootstrapContext().getServiceRegistry() .requireService( JdbcServices.class ); return column.getQuotedName( jdbcServices.getDialect() ); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java index ff4db8c1906e..19e471b517da 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java @@ -4,15 +4,22 @@ */ package org.hibernate.mapping; +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.function.Consumer; import java.util.function.Function; +import org.hibernate.AnnotationException; +import org.hibernate.AssertionFailure; import org.hibernate.Incubating; import org.hibernate.Internal; import org.hibernate.MappingException; +import org.hibernate.models.spi.MemberDetails; +import org.hibernate.service.ServiceRegistry; import org.hibernate.type.TimeZoneStorageStrategy; import org.hibernate.annotations.SoftDelete; import org.hibernate.annotations.SoftDeleteType; @@ -63,15 +70,17 @@ import org.hibernate.type.internal.ConvertedBasicTypeImpl; import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfigurationAware; +import org.hibernate.usertype.AnnotationBasedUserType; import org.hibernate.usertype.DynamicParameterizedType; import org.hibernate.usertype.UserType; -import com.fasterxml.classmate.ResolvedType; import jakarta.persistence.AttributeConverter; import jakarta.persistence.EnumType; import jakarta.persistence.TemporalType; +import org.hibernate.usertype.UserTypeCreationContext; import static java.lang.Boolean.parseBoolean; +import static org.hibernate.internal.util.GenericAssignability.isAssignableFrom; import static org.hibernate.boot.model.convert.spi.ConverterDescriptor.TYPE_NAME_PREFIX; import static org.hibernate.internal.CoreMessageLogger.CORE_LOGGER; import static org.hibernate.internal.util.ReflectHelper.reflectedPropertyType; @@ -83,6 +92,7 @@ /** * @author Steve Ebersole + * @author Yanming Zhou */ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resolvable, JpaAttributeConverterCreationContext { @@ -145,6 +155,7 @@ public BasicValue(BasicValue original) { this.isSoftDelete = original.isSoftDelete; this.softDeleteStrategy = original.softDeleteStrategy; this.aggregateColumn = original.aggregateColumn; + this.jdbcTypeCode = original.jdbcTypeCode; } @Override @@ -215,8 +226,24 @@ public void setImplicitJavaTypeAccess(Function> getExplicitJavaTypeAccess() { + return explicitJavaTypeAccess; + } + + public Function getExplicitJdbcTypeAccess() { + return explicitJdbcTypeAccess; + } + + public Function> getExplicitMutabilityPlanAccess() { + return explicitMutabilityPlanAccess; + } + + public Function getImplicitJavaTypeAccess() { + return implicitJavaTypeAccess; + } + public Selectable getColumn() { - return getColumnSpan() == 0 ? null : getColumn( 0 ); + return hasColumns() ? getColumn( 0 ) : null; } public java.lang.reflect.Type getResolvedJavaType() { @@ -476,18 +503,17 @@ private ConverterDescriptor getSoftDeleteConverterDescriptor( SoftDelete.UnspecifiedConversion.class.equals( attributeConverterDescriptor.getAttributeConverterClass() ); if ( conversionWasUnspecified ) { final var jdbcType = BooleanJdbcType.INSTANCE.resolveIndicatedType( this, javaType ); - final var classmateContext = getBootstrapContext().getClassmateContext(); if ( jdbcType.isNumber() ) { - return ConverterDescriptors.of( NumericBooleanConverter.INSTANCE, classmateContext ); + return ConverterDescriptors.of( NumericBooleanConverter.INSTANCE ); } else if ( jdbcType.isString() ) { // here we pick 'T' / 'F' storage, though 'Y' / 'N' is equally valid - its 50/50 - return ConverterDescriptors.of( TrueFalseConverter.INSTANCE, classmateContext ); + return ConverterDescriptors.of( TrueFalseConverter.INSTANCE ); } else { // should indicate BIT or BOOLEAN == no conversion needed // - we still create the converter to properly set up JDBC type, etc - return ConverterDescriptors.of( PassThruSoftDeleteConverter.INSTANCE, classmateContext ); + return ConverterDescriptors.of( PassThruSoftDeleteConverter.INSTANCE ); } } else { @@ -509,12 +535,12 @@ public Class> getAttributeConverterClass } @Override - public ResolvedType getDomainValueResolvedType() { + public java.lang.reflect.Type getDomainValueResolvedType() { return underlyingDescriptor.getDomainValueResolvedType(); } @Override - public ResolvedType getRelationalValueResolvedType() { + public java.lang.reflect.Type getRelationalValueResolvedType() { return underlyingDescriptor.getRelationalValueResolvedType(); } @@ -612,14 +638,14 @@ public Boolean convertToEntityAttribute(Boolean relationalValue) { } } - private Resolution resolution(BasicJavaType explicitJavaType, JavaType javaType) { - final JavaType basicJavaType; + private Resolution resolution(BasicJavaType explicitJavaType, JavaType javaType) { + final JavaType basicJavaType; final JdbcType jdbcType; if ( explicitJdbcTypeAccess != null ) { final var typeConfiguration = getTypeConfiguration(); jdbcType = explicitJdbcTypeAccess.apply( typeConfiguration ); basicJavaType = javaType == null && jdbcType != null - ? jdbcType.getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration ) + ? jdbcType.getRecommendedJavaType( null, null, typeConfiguration ) : javaType; } else { @@ -630,7 +656,7 @@ private Resolution resolution(BasicJavaType explicitJavaType, JavaType throw new MappingException( "Unable to determine JavaType to use : " + this ); } - if ( basicJavaType instanceof BasicJavaType castType + if ( basicJavaType instanceof BasicJavaType castType && ( !basicJavaType.getJavaTypeClass().isEnum() || enumerationStyle == null ) ) { final var context = getBuildingContext(); final var autoAppliedTypeDef = context.getTypeDefinitionRegistry().resolveAutoApplied( castType ); @@ -673,8 +699,8 @@ private Resolution converterResolution(JavaType javaType, ConverterDescrip ); if ( javaType instanceof BasicPluralJavaType pluralJavaType - && !attributeConverterDescriptor.getDomainValueResolvedType().getErasedType() - .isAssignableFrom( javaType.getJavaTypeClass() ) ) { + && !isAssignableFrom( attributeConverterDescriptor.getDomainValueResolvedType(), + javaType.getJavaTypeClass() ) ) { // In this case, the converter applies to the element of a BasicPluralJavaType final BasicType registeredElementType = converterResolution.getLegacyResolvedBasicType(); final BasicType registeredType = registeredElementType == null ? null @@ -779,7 +805,7 @@ private JavaType specialJavaType( return xmlJavaType; } } - return javaTypeRegistry.getDescriptor( impliedJavaType ); + return javaTypeRegistry.resolveDescriptor( impliedJavaType ); } private MutabilityPlan mutabilityPlan( @@ -1030,6 +1056,10 @@ public void setExplicitTypeParams(Map explicitLocalTypeParams) { this.explicitLocalTypeParams = explicitLocalTypeParams; } + public Map getExplicitTypeParams() { + return explicitLocalTypeParams; + } + public void setExplicitTypeName(String typeName) { this.explicitTypeName = typeName; } @@ -1055,19 +1085,20 @@ public void setExplicitCustomType(Class> explicitCustomTyp throw new UnsupportedOperationException( "Unsupported attempt to set an explicit-custom-type when value is already resolved" ); } else { + final var parameters = buildCustomTypeProperties(); resolution = new UserTypeResolution<>( new CustomType<>( - getConfiguredUserTypeBean( explicitCustomType, getCustomTypeProperties() ), + getConfiguredUserTypeBean( explicitCustomType, getTypeAnnotation(), parameters ), getTypeConfiguration() ), null, - getCustomTypeProperties() + parameters ); } } } - private Properties getCustomTypeProperties() { + private Properties buildCustomTypeProperties() { final var properties = new Properties(); if ( isNotEmpty( getTypeParameters() ) ) { properties.putAll( getTypeParameters() ); @@ -1078,29 +1109,149 @@ private Properties getCustomTypeProperties() { return properties; } - private UserType getConfiguredUserTypeBean(Class> explicitCustomType, Properties properties) { + private UserType getConfiguredUserTypeBean( + Class> explicitCustomType, + Annotation typeAnnotation, Properties parameters) { final var typeInstance = - getBuildingContext().getBuildingOptions().isAllowExtensionsInCdi() - ? getUserTypeBean( explicitCustomType, properties ).getBeanInstance() - : FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( explicitCustomType ); - + createUserTypeInstance( explicitCustomType, parameters, typeAnnotation ); if ( typeInstance instanceof TypeConfigurationAware configurationAware ) { configurationAware.setTypeConfiguration( getTypeConfiguration() ); } + addParameterType( parameters, typeInstance ); + injectParameters( typeInstance, parameters ); + // envers - grr + setTypeParameters( parameters ); + return typeInstance; + } + + private UserType createUserTypeInstance( + Class> customType, + Properties parameters, + Annotation typeAnnotation) { + final var creationContext = new TypeCreationContext( parameters ); + final var typeInstance = instantiateUserType( customType, typeAnnotation, creationContext ); + if ( typeInstance instanceof AnnotationBasedUserType annotationBased ) { + initializeAnnotationBasedUserType( typeAnnotation, annotationBased, creationContext ); + } + return typeInstance; + } + + private void addParameterType(Properties properties, UserType typeInstance) { + if ( typeInstance instanceof DynamicParameterizedType + && parseBoolean( properties.getProperty( DynamicParameterizedType.IS_DYNAMIC ) ) + && properties.get( DynamicParameterizedType.PARAMETER_TYPE ) == null ) { + properties.put( DynamicParameterizedType.PARAMETER_TYPE, createParameterType() ); + } + } + + private void initializeAnnotationBasedUserType( + Annotation typeAnnotation, + AnnotationBasedUserType annotationBased, + UserTypeCreationContext creationContext) { + if ( typeAnnotation == null ) { + throw new AnnotationException( String.format( + "Custom type '%s' implements 'AnnotationBasedUserType' but no custom type annotation is present", + annotationBased.getClass().getName() ) ); + } + annotationBased.initialize( castAnnotationType( typeAnnotation, annotationBased ), creationContext ); + } + + private class TypeCreationContext implements UserTypeCreationContext { + private final Properties parameters; + + private TypeCreationContext(Properties parameters) { + this.parameters = parameters; + } - if ( typeInstance instanceof DynamicParameterizedType ) { - if ( parseBoolean( properties.getProperty( DynamicParameterizedType.IS_DYNAMIC ) ) ) { - if ( properties.get( DynamicParameterizedType.PARAMETER_TYPE ) == null ) { - properties.put( DynamicParameterizedType.PARAMETER_TYPE, createParameterType() ); + @Override + public MetadataBuildingContext getBuildingContext() { + return BasicValue.this.getBuildingContext(); + } + + @Override + public ServiceRegistry getServiceRegistry() { + return BasicValue.this.getServiceRegistry(); + } + + @Override + public MemberDetails getMemberDetails() { + return BasicValue.this.getMemberDetails(); + } + + @Override + public Properties getParameters() { + return parameters; + } + } + + private A castAnnotationType( + Annotation typeAnnotation, AnnotationBasedUserType annotationBased ) { + final var annotationType = annotationBased.getClass(); + for ( var iface: annotationType.getGenericInterfaces() ) { + if ( iface instanceof ParameterizedType parameterizedType + && parameterizedType.getRawType() == AnnotationBasedUserType.class ) { + final var typeArguments = parameterizedType.getActualTypeArguments(); + if ( typeArguments.length > 0 + && typeArguments[0] instanceof Class annotationClass ) { + if ( !annotationClass.isInstance( typeAnnotation ) ) { + throw new AnnotationException( String.format( "Annotation '%s' is not assignable to '%s'", + annotationType.getName(), iface.getTypeName() ) ); + } + @SuppressWarnings("unchecked") // safe, we just checked it + final var castAnnotation = (A) typeAnnotation; + return castAnnotation; } } } + throw new AssertionFailure( "Could not find implementing interface" ); + } - injectParameters( typeInstance, properties ); - // envers - grr - setTypeParameters( properties ); + private > T instantiateUserType( + Class customType, Annotation typeAnnotation, + UserTypeCreationContext creationContext) { + try { + if ( typeAnnotation != null ) { + // attempt to instantiate it with the annotation and context object as constructor arguments + try { + final var constructor = + customType.getDeclaredConstructor( typeAnnotation.annotationType(), + UserTypeCreationContext.class ); + constructor.setAccessible( true ); + return constructor.newInstance( typeAnnotation, creationContext ); + } + catch (NoSuchMethodException ignored) { + // attempt to instantiate it with the annotation as a constructor argument + try { + final var constructor = + customType.getDeclaredConstructor( typeAnnotation.annotationType() ); + constructor.setAccessible( true ); + return constructor.newInstance( typeAnnotation ); + } + catch (NoSuchMethodException ignored_) { + // no such constructor + } + } + } - return typeInstance; + // attempt to instantiate it with the context object as a constructor argument + try { + final var constructor = + customType.getDeclaredConstructor( UserTypeCreationContext.class ); + constructor.setAccessible( true ); + return constructor.newInstance( creationContext ); + } + catch (NoSuchMethodException ignored) { + // no such constructor, instantiate it the old way + } + + } + catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new org.hibernate.InstantiationException( "Could not instantiate custom type", customType, e ); + } + + return getBuildingContext().getBuildingOptions().isAllowExtensionsInCdi() + ? getUserTypeBean( customType, creationContext.getParameters() ).getBeanInstance() + : FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( customType ); } private ManagedBean getUserTypeBean(Class explicitCustomType, Properties properties) { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java index df98cf2be022..68f43512f92d 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java @@ -424,6 +424,11 @@ public int getColumnSpan() { return 0; } + @Override + public boolean hasColumns() { + return false; + } + @Override public Type getType() throws MappingException { return getCollectionType(); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java index f72ee0bea568..279e74fd3542 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java @@ -10,12 +10,12 @@ import java.util.Locale; import java.util.Objects; +import org.checkerframework.checker.nullness.qual.NonNull; import org.hibernate.AssertionFailure; import org.hibernate.Internal; import org.hibernate.MappingException; import org.hibernate.boot.Metadata; import org.hibernate.boot.model.naming.Identifier; -import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.dialect.Dialect; @@ -30,9 +30,6 @@ import org.hibernate.type.Type; import org.hibernate.type.descriptor.JdbcTypeNameMapper; import org.hibernate.type.descriptor.jdbc.JdbcType; -import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; -import org.hibernate.type.descriptor.sql.DdlType; -import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; import org.hibernate.type.MappingContext; import org.hibernate.type.spi.TypeConfiguration; @@ -195,35 +192,43 @@ public String getQuotedName(Dialect dialect) { @Override public String getAlias(Dialect dialect) { - final int lastLetter = lastIndexOfLetter( name ); final String suffix = AliasConstantsHelper.get( uniqueInteger ); + return qualifyAlias( dialect, suffix, aliasRoot() ); + } + private @NonNull String aliasRoot() { + final int lastLetter = lastIndexOfLetter( name ); final String alias; if ( lastLetter == -1 ) { - alias = "column"; + return "column"; } else { final String lowerCaseName = name.toLowerCase( Locale.ROOT ); - alias = lowerCaseName.length() > lastLetter + 1 + return lowerCaseName.length() > lastLetter + 1 ? lowerCaseName.substring( 0, lastLetter + 1 ) : lowerCaseName; } + } + private @NonNull String qualifyAlias(Dialect dialect, String suffix, String alias) { + final int suffixLength = suffix.length(); + final int maxAliasLength = dialect.getMaxAliasLength(); + final int freeLength = maxAliasLength - suffixLength; final boolean useRawName = - name.length() + suffix.length() <= dialect.getMaxAliasLength() + name.length() <= freeLength && !quoted && !name.equalsIgnoreCase( dialect.rowId(null) ); if ( !useRawName ) { - if ( suffix.length() >= dialect.getMaxAliasLength() ) { + if ( suffixLength >= maxAliasLength ) { throw new MappingException( String.format( "Unique suffix [%s] length must be less than maximum [%d]", - suffix, dialect.getMaxAliasLength() + suffix, maxAliasLength ) ); } - if ( alias.length() + suffix.length() > dialect.getMaxAliasLength() ) { - return alias.substring( 0, dialect.getMaxAliasLength() - suffix.length() ) + suffix; + if ( alias.length() > freeLength ) { + return alias.substring( 0, freeLength ) + suffix; } } return alias + suffix; @@ -283,50 +288,51 @@ public boolean equals(Column column) { public int getSqlTypeCode(MappingContext mapping) throws MappingException { if ( sqlTypeCode == null ) { - final Type type = getValue().getType(); - final int[] sqlTypeCodes; - try { - sqlTypeCodes = type.getSqlTypeCodes( mapping ); - } - catch ( Exception cause ) { - throw new MappingException( - String.format( - Locale.ROOT, - "Unable to resolve JDBC type code for column '%s' of table '%s'", - getName(), - getValue().getTable().getName() - ), - cause - ); - } - final int index = getTypeIndex(); - if ( index >= sqlTypeCodes.length ) { - throw new MappingException( - String.format( - Locale.ROOT, - "Unable to resolve JDBC type code for column '%s' of table '%s'", - getName(), - getValue().getTable().getName() - ) - ); - } - sqlTypeCode = sqlTypeCodes[index]; + sqlTypeCode = getSqlTypeCode( mapping, getValue().getType() ); } return sqlTypeCode; } + private int getSqlTypeCode(MappingContext mapping, Type type) { + final int[] sqlTypeCodes; + try { + sqlTypeCodes = type.getSqlTypeCodes( mapping ); + } + catch ( Exception cause ) { + throw new MappingException( + String.format( + Locale.ROOT, + "Unable to resolve JDBC type code for column '%s' of table '%s'", + getName(), + getValue().getTable().getName() + ), + cause + ); + } + final int index = getTypeIndex(); + if ( index >= sqlTypeCodes.length ) { + throw new MappingException( + String.format( + Locale.ROOT, + "Unable to resolve JDBC type code for column '%s' of table '%s'", + getName(), + getValue().getTable().getName() + ) + ); + } + return sqlTypeCodes[index]; + } + private String getSqlTypeName(TypeConfiguration typeConfiguration, Dialect dialect, MappingContext mapping) { if ( sqlTypeName == null ) { - final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); - final JdbcTypeRegistry jdbcTypeRegistry = typeConfiguration.getJdbcTypeRegistry(); + final var ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); + final var jdbcTypeRegistry = typeConfiguration.getJdbcTypeRegistry(); final int sqlTypeCode = getSqlTypeCode( mapping ); - final JdbcType jdbcType = + final var jdbcType = jdbcTypeRegistry.getConstructor( sqlTypeCode ) == null ? jdbcTypeRegistry.findDescriptor( sqlTypeCode ) : ( (BasicType) getUnderlyingType( mapping, getValue().getType(), typeIndex ) ).getJdbcType(); - final DdlType descriptor = jdbcType == null - ? null - : ddlTypeRegistry.getDescriptor( jdbcType.getDdlTypeCode() ); + final var descriptor = jdbcType == null ? null : ddlTypeRegistry.getDescriptor( jdbcType.getDdlTypeCode() ); if ( descriptor == null ) { throw new MappingException( String.format( @@ -340,7 +346,7 @@ private String getSqlTypeName(TypeConfiguration typeConfiguration, Dialect diale ); } try { - final Size size = getColumnSize( dialect, mapping ); + final var size = getColumnSize( dialect, mapping ); sqlTypeName = descriptor.getTypeName( size, getUnderlyingType( mapping, getValue().getType(), typeIndex ), @@ -367,7 +373,7 @@ private String getSqlTypeName(TypeConfiguration typeConfiguration, Dialect diale private static Type getUnderlyingType(MappingContext mappingContext, Type type, int typeIndex) { if ( type instanceof ComponentType componentType ) { int cols = 0; - for ( Type subtype : componentType.getSubtypes() ) { + for ( var subtype : componentType.getSubtypes() ) { final int columnSpan = subtype.getColumnSpan( mappingContext ); if ( cols+columnSpan > typeIndex ) { return getUnderlyingType( mappingContext, subtype, typeIndex-cols ); @@ -377,7 +383,7 @@ private static Type getUnderlyingType(MappingContext mappingContext, Type type, throw new IndexOutOfBoundsException(); } else if ( type instanceof EntityType entityType ) { - final Type idType = entityType.getIdentifierOrUniqueKeyType( mappingContext ); + final var idType = entityType.getIdentifierOrUniqueKeyType( mappingContext ); return getUnderlyingType( mappingContext, idType, typeIndex ); } else { @@ -406,7 +412,7 @@ public void setSqlTypeCode(Integer typeCode) { } public String getSqlType(Metadata mapping) { - final Database database = mapping.getDatabase(); + final var database = mapping.getDatabase(); return getSqlTypeName( database.getTypeConfiguration(), database.getDialect(), mapping ); } @@ -448,22 +454,21 @@ public Size getColumnSize(Dialect dialect, MappingContext mappingContext) { } Size calculateColumnSize(Dialect dialect, MappingContext mappingContext) { - Type type = getValue().getType(); - Long lengthToUse = getLength(); - Integer precisionToUse = getPrecision(); - Integer scaleToUse = getScale(); + var lengthToUse = getLength(); + var precisionToUse = getPrecision(); + var scaleToUse = getScale(); + var type = getValue().getType(); if ( type instanceof EntityType ) { type = getTypeForEntityValue( mappingContext, type, getTypeIndex() ); } - if ( type instanceof ComponentType ) { - type = getTypeForComponentValue( mappingContext, type, getTypeIndex() ); + if ( type instanceof ComponentType componentType ) { + type = getTypeForComponentValue( mappingContext, componentType, getTypeIndex() ); } - if ( type instanceof BasicType basicType ) { - if ( isTemporal( basicType.getExpressibleJavaType() ) ) { - precisionToUse = getTemporalPrecision(); - lengthToUse = null; - scaleToUse = null; - } + if ( type instanceof BasicType basicType + && isTemporal( basicType.getExpressibleJavaType() ) ) { + precisionToUse = getTemporalPrecision(); + lengthToUse = null; + scaleToUse = null; } if ( type == null ) { throw new AssertionFailure( "no typing information available to determine column size" ); @@ -480,23 +485,25 @@ Size calculateColumnSize(Dialect dialect, MappingContext mappingContext) { return size; } - private Type getTypeForComponentValue(MappingContext mappingContext, Type type, int typeIndex) { - final Type[] subtypes = ( (ComponentType) type ).getSubtypes(); + private Type getTypeForComponentValue(MappingContext mappingContext, ComponentType type, int typeIndex) { + final var subtypes = type.getSubtypes(); int typeStartIndex = 0; - for ( Type subtype : subtypes ) { + for ( var subtype : subtypes ) { final int columnSpan = subtype.getColumnSpan( mappingContext ); if ( typeStartIndex + columnSpan > typeIndex ) { final int subtypeIndex = typeIndex - typeStartIndex; if ( subtype instanceof EntityType ) { return getTypeForEntityValue( mappingContext, subtype, subtypeIndex ); } - if ( subtype instanceof ComponentType ) { - return getTypeForComponentValue( mappingContext, subtype, subtypeIndex ); + else if ( subtype instanceof ComponentType componentType ) { + return getTypeForComponentValue( mappingContext, componentType, subtypeIndex ); } - if ( subtypeIndex == 0 ) { + else if ( subtypeIndex == 0 ) { return subtype; } - break; + else { + break; + } } typeStartIndex += columnSpan; } @@ -514,11 +521,20 @@ private Type getTypeForComponentValue(MappingContext mappingContext, Type type, private Type getTypeForEntityValue(MappingContext mappingContext, Type type, int typeIndex) { int index = 0; if ( type instanceof EntityType entityType ) { - return getTypeForEntityValue( mappingContext, entityType.getIdentifierOrUniqueKeyType( mappingContext ), typeIndex ); + return getTypeForEntityValue( + mappingContext, + entityType.getIdentifierOrUniqueKeyType( mappingContext ), + typeIndex + ); } else if ( type instanceof ComponentType componentType ) { - for ( Type subtype : componentType.getSubtypes() ) { - final Type result = getTypeForEntityValue( mappingContext, subtype, typeIndex - index ); + for ( var subtype : componentType.getSubtypes() ) { + final var result = + getTypeForEntityValue( + mappingContext, + subtype, + typeIndex - index + ); if ( result != null ) { return result; } @@ -557,13 +573,10 @@ public boolean isSqlTypeLob(Metadata mapping) { try { final int typeCode = getSqlTypeCode( mapping ); final var ddlType = ddlTypeRegistry.getDescriptor( typeCode ); - if ( ddlType == null ) { - sqlTypeLob = JdbcType.isLob( typeCode ); - } - else { - final Size size = getColumnSize( dialect, mapping ); - sqlTypeLob = ddlType.isLob( size ); - } + sqlTypeLob = + ddlType == null + ? JdbcType.isLob( typeCode ) + : ddlType.isLob( getColumnSize( dialect, mapping ) ); } catch ( MappingException cause ) { throw cause; diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java index 1ee0aed059a5..49d352ed90d0 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java @@ -246,6 +246,17 @@ public int getColumnSpan() { return getSelectables().size(); } + @Override + public boolean hasColumns() { + for ( var property : properties ) { + if ( property.hasColumns() ) { + return true; + } + } + return discriminator != null + && discriminator.hasColumns(); + } + public List getAggregatedColumns() { final List aggregatedColumns = new ArrayList<>( getPropertySpan() ); collectAggregatedColumns( aggregatedColumns, this ); @@ -490,7 +501,7 @@ public boolean isSame(Component other) { @Override public boolean[] getColumnInsertability() { - final boolean[] result = new boolean[getColumnSpan()]; + final var result = new boolean[getColumnSpan()]; int i = 0; for ( var property : getProperties() ) { i += copyFlags( property.getValue().getColumnInsertability(), result, i, property.isInsertable() ); @@ -522,7 +533,7 @@ public boolean hasAnyInsertableColumns() { @Override public boolean[] getColumnUpdateability() { - final boolean[] result = new boolean[getColumnSpan()]; + final var result = new boolean[getColumnSpan()]; int i = 0; for ( var property : getProperties() ) { i += copyFlags( property.getValue().getColumnUpdateability(), result, i, property.isUpdatable() ); @@ -582,8 +593,8 @@ public boolean matchesAllProperties(String... propertyNames) { } public boolean hasProperty(String propertyName) { - for ( Property prop : properties ) { - if ( prop.getName().equals(propertyName) ) { + for ( var property : properties ) { + if ( property.getName().equals(propertyName) ) { return true; } } @@ -680,7 +691,7 @@ private Generator buildIdentifierGenerator(Dialect dialect, RootClass rootClass, for ( int i = 0; i < properties.size(); i++ ) { final var property = properties.get( i ); if ( property.getValue().isSimpleValue() ) { - final SimpleValue value = (SimpleValue) property.getValue(); + final var value = (SimpleValue) property.getValue(); if ( !value.getCustomIdGeneratorCreator().isAssigned() ) { // skip any 'assigned' generators, they would have been // handled by the StandardGenerationContextLocator @@ -850,9 +861,10 @@ public boolean isValid(MappingContext mappingContext) throws MappingException { } if ( assignedPropertyNames.size() != properties.size() ) { final ArrayList missingProperties = new ArrayList<>(); - for ( Property property : properties ) { - if ( !assignedPropertyNames.contains( property.getName() ) ) { - missingProperties.add( property.getName() ); + for ( var property : properties ) { + final String propertyName = property.getName(); + if ( !assignedPropertyNames.contains( propertyName ) ) { + missingProperties.add( propertyName ); } } throw new MappingException( "component type [" + componentClassName + "] has " + properties.size() + " properties but the instantiator only assigns " + assignedPropertyNames.size() + " properties. missing properties: " + missingProperties ); @@ -886,7 +898,7 @@ private int[] sortProperties(boolean forceRetainOriginalOrder) { // because XML mappings might refer to this through the defined order if ( forceRetainOriginalOrder || isAlternateUniqueKey() || isEmbedded() || getBuildingContext() instanceof MappingDocument ) { - final Property[] originalProperties = properties.toArray( new Property[0] ); + final var originalProperties = properties.toArray( new Property[0] ); properties.sort( Comparator.comparing( Property::getName ) ); originalPropertyOrder = new int[originalProperties.length]; for ( int j = 0; j < originalPropertyOrder.length; j++ ) { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/MappingHelper.java b/hibernate-core/src/main/java/org/hibernate/mapping/MappingHelper.java index bfe537b75570..15a7f9b1a5d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/MappingHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/MappingHelper.java @@ -16,6 +16,7 @@ import org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer; import org.hibernate.resource.beans.spi.ManagedBean; import org.hibernate.resource.beans.spi.ProvidedInstanceManagedBeanImpl; +import org.hibernate.usertype.AnnotationBasedUserType; import org.hibernate.usertype.ParameterizedType; import org.hibernate.usertype.UserCollectionType; @@ -75,9 +76,10 @@ public static void injectParameters(Object type, Properties parameters) { if ( type instanceof ParameterizedType parameterizedType ) { parameterizedType.setParameterValues( parameters == null ? EMPTY_PROPERTIES : parameters ); } - else if ( parameters != null && !parameters.isEmpty() ) { + else if ( parameters != null && !parameters.isEmpty() + && !( type instanceof AnnotationBasedUserType ) ) { throw new MappingException( "'UserType' implementation '" + type.getClass().getName() - + "' does not implement 'ParameterizedType' but parameters were provided" ); + + "' does not implement 'ParameterizedType' or 'AnnotationBasedUserType' but parameters were provided" ); } } @@ -106,7 +108,7 @@ public static void checkPropertyColumnDuplication( List properties, String owner) throws MappingException { for ( var property : properties ) { - if ( property.isUpdatable() || property.isInsertable() ) { + if ( ( property.isUpdatable() || property.isInsertable() ) && !property.isGenericSpecialization() ) { property.getValue().checkColumnDuplication( distinctColumns, owner ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java b/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java index 7d0f98fdd0bd..9d90770f272d 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java @@ -93,6 +93,11 @@ public int getColumnSpan() { return associatedClass.getKey().getColumnSpan(); } + @Override + public boolean hasColumns() { + return associatedClass.getKey().hasColumns(); + } + @Override public FetchMode getFetchMode() { return FetchMode.JOIN; diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java b/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java index 22b99da2bbe5..5d8c685e81e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java @@ -61,7 +61,7 @@ public String getEntityName() { } public OneToOneType getType() throws MappingException { - if ( getColumnSpan()>0 ) { + if ( hasColumns() ) { return new SpecialOneToOneType( getTypeConfiguration(), getReferencedEntityName(), @@ -93,7 +93,7 @@ public OneToOneType getType() throws MappingException { @Override public void createUniqueKey(MetadataBuildingContext context) { - if ( !hasFormula() && getColumnSpan()>0 ) { + if ( !hasFormula() && hasColumns() ) { getTable().createUniqueKey( getConstraintColumns(), context ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java index b53e780c0b5a..a21f1f734f66 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java @@ -362,6 +362,8 @@ public boolean isExplicitPolymorphism() { public abstract List getPropertyClosure(); + public abstract List getAllPropertyClosure(); + public abstract List

getTableClosure(); public abstract List getKeyClosure(); @@ -722,6 +724,23 @@ public int getJoinClosureSpan() { } public int getPropertyClosureSpan() { + int span = 0; + for ( Property property : properties ) { + if ( !property.isGeneric() ) { + span += 1; + } + } + for ( var join : joins ) { + for ( Property property : join.getProperties() ) { + if ( !property.isGeneric() ) { + span += 1; + } + } + } + return span; + } + + public int getAllPropertyClosureSpan() { int span = properties.size(); for ( var join : joins ) { span += join.getPropertySpan(); @@ -754,6 +773,23 @@ public int getJoinNumber(Property prop) { * @return A list over the "normal" properties. */ public List getProperties() { + final ArrayList list = new ArrayList<>(); + for ( Property property : properties ) { + if ( !property.isGeneric() ) { + list.add( property ); + } + } + for ( var join : joins ) { + for ( Property property : join.getProperties() ) { + if ( !property.isGeneric() ) { + list.add( property ); + } + } + } + return list; + } + + public List getAllProperties() { final ArrayList> list = new ArrayList<>(); list.add( properties ); for ( var join : joins ) { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java index c5e35b1def13..b8089fea91d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java @@ -24,6 +24,7 @@ import org.hibernate.jpa.event.spi.CallbackDefinition; import org.hibernate.metamodel.RepresentationMode; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.models.spi.MemberDetails; import org.hibernate.property.access.spi.Getter; import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.property.access.spi.PropertyAccessStrategy; @@ -71,6 +72,7 @@ public class Property implements Serializable, MetaAttributable { private PersistentClass persistentClass; private boolean naturalIdentifier; private boolean isGeneric; + private boolean isGenericSpecialization; private boolean lob; private java.util.List callbackDefinitions; private String returnedClassName; @@ -98,6 +100,10 @@ public int getColumnSpan() { return value.getColumnSpan(); } + boolean hasColumns() { + return value.hasColumns(); + } + /** * Delegates to {@link Value#getSelectables()}. */ @@ -490,6 +496,14 @@ public void setGeneric(boolean generic) { this.isGeneric = generic; } + public boolean isGenericSpecialization() { + return isGenericSpecialization; + } + + public void setGenericSpecialization(boolean genericSpecialization) { + isGenericSpecialization = genericSpecialization; + } + public boolean isLob() { return lob; } @@ -554,6 +568,7 @@ public Property copy() { property.setPersistentClass( getPersistentClass() ); property.setNaturalIdentifier( isNaturalIdentifier() ); property.setGeneric( isGeneric() ); + property.setGenericSpecialization( isGenericSpecialization() ); property.setLob( isLob() ); property.addCallbackDefinitions( getCallbackDefinitions() ); property.setReturnedClassName( getReturnedClassName() ); @@ -607,6 +622,13 @@ public Value getValue() { return value; } + @Override + public MemberDetails getMemberDetails() { + return value instanceof SimpleValue simpleValue + ? simpleValue.getMemberDetails() + : null; // TODO: Give Property a reference to the MemberDetails + } + @Override public SqlStringGenerationContext getSqlStringGenerationContext() { return context.getSqlStringGenerationContext(); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/RootClass.java b/hibernate-core/src/main/java/org/hibernate/mapping/RootClass.java index 6503fa477807..d82a0e0daa9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/RootClass.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/RootClass.java @@ -12,6 +12,7 @@ import org.hibernate.annotations.SoftDeleteType; import org.hibernate.boot.Metadata; import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.models.spi.ClassDetails; import static org.hibernate.internal.CoreMessageLogger.CORE_LOGGER; import static org.hibernate.internal.util.ReflectHelper.overridesEquals; @@ -34,6 +35,7 @@ public final class RootClass extends PersistentClass implements TableOwner, Soft private String cacheConcurrencyStrategy; private String cacheRegionName; private boolean lazyPropertiesCacheable = true; + private ClassDetails naturalIdClass; private String naturalIdCacheRegionName; private Value discriminator; @@ -134,6 +136,11 @@ public List getPropertyClosure() { return getProperties(); } + @Override + public List getAllPropertyClosure() { + return getAllProperties(); + } + @Override public List
getTableClosure() { return List.of( getTable() ); @@ -365,6 +372,14 @@ public void setLazyPropertiesCacheable(boolean lazyPropertiesCacheable) { this.lazyPropertiesCacheable = lazyPropertiesCacheable; } + public ClassDetails getNaturalIdClass() { + return naturalIdClass; + } + + public void setNaturalIdClass(ClassDetails naturalIdClass) { + this.naturalIdClass = naturalIdClass; + } + @Override public String getNaturalIdCacheRegionName() { return naturalIdCacheRegionName; diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java index 4cabe13f4194..2fb18b832e81 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -88,6 +88,8 @@ public abstract class SimpleValue implements KeyValue { private String typeName; private Properties typeParameters; + private Annotation typeAnnotation; + private MemberDetails memberDetails; private boolean isVersion; private boolean isNationalized; private boolean isLob; @@ -121,12 +123,20 @@ public SimpleValue(MetadataBuildingContext buildingContext, Table table) { protected SimpleValue(SimpleValue original) { this.buildingContext = original.buildingContext; this.metadata = original.metadata; - this.columns.addAll( original.columns ); + for ( var selectable : original.columns ) { + if ( selectable instanceof Column column ) { + final var newColumn = column.clone(); + newColumn.setValue( this ); + this.columns.add( newColumn ); + } + } this.insertability.addAll( original.insertability ); this.updatability.addAll( original.updatability ); this.partitionKey = original.partitionKey; this.typeName = original.typeName; this.typeParameters = original.typeParameters == null ? null : new Properties( original.typeParameters ); + this.typeAnnotation = original.typeAnnotation; + this.memberDetails = original.memberDetails; this.isVersion = original.isVersion; this.isNationalized = original.isNationalized; this.isLob = original.isLob; @@ -227,8 +237,8 @@ protected void justAddFormula(Formula formula) { public void sortColumns(int[] originalOrder) { if ( columns.size() > 1 ) { final var originalColumns = columns.toArray( new Selectable[0] ); - final boolean[] originalInsertability = toBooleanArray( insertability ); - final boolean[] originalUpdatability = toBooleanArray( updatability ); + final var originalInsertability = toBooleanArray( insertability ); + final var originalUpdatability = toBooleanArray( updatability ); for ( int i = 0; i < originalOrder.length; i++ ) { final int originalIndex = originalOrder[i]; final var selectable = originalColumns[i]; @@ -257,6 +267,11 @@ public int getColumnSpan() { return columns.size(); } + @Override + public boolean hasColumns() { + return !columns.isEmpty(); + } + protected Selectable getColumn(int position){ return columns.get( position ); } @@ -298,8 +313,7 @@ void setAttributeConverterDescriptor(String typeName) { (Class>) classForName( AttributeConverter.class, converterClassName, bootstrapContext ); attributeConverterDescriptor = - ConverterDescriptors.of( clazz, null, false, - bootstrapContext.getClassmateContext() ); + ConverterDescriptors.of( clazz, null, false ); } ClassLoaderService classLoaderService() { @@ -792,11 +806,27 @@ public void setTypeParameters(Map parameters) { } } + public void setTypeAnnotation(Annotation typeAnnotation) { + this.typeAnnotation = typeAnnotation; + } + + public void setMemberDetails(MemberDetails memberDetails) { + this.memberDetails = memberDetails; + } + public Properties getTypeParameters() { return typeParameters; } - public void copyTypeFrom( SimpleValue sourceValue ) { + public Annotation getTypeAnnotation() { + return typeAnnotation; + } + + public MemberDetails getMemberDetails() { + return memberDetails; + } + + public void copyTypeFrom(SimpleValue sourceValue ) { setTypeName( sourceValue.getTypeName() ); setTypeParameters( sourceValue.getTypeParameters() ); @@ -818,6 +848,8 @@ public boolean isSame(SimpleValue other) { return Objects.equals( columns, other.columns ) && Objects.equals( typeName, other.typeName ) && Objects.equals( typeParameters, other.typeParameters ) + && Objects.equals( typeAnnotation, other.typeAnnotation ) + && Objects.equals( memberDetails, other.memberDetails ) && Objects.equals( table, other.table ) && Objects.equals( foreignKeyName, other.foreignKeyName ) && Objects.equals( foreignKeyDefinition, other.foreignKeyDefinition ); @@ -883,7 +915,7 @@ public void setPartitionKey(boolean partitionColumn) { } private static boolean[] extractBooleansFromList(List list) { - final boolean[] array = new boolean[ list.size() ]; + final var array = new boolean[ list.size() ]; int i = 0; for ( Boolean value : list ) { array[ i++ ] = value; @@ -912,11 +944,11 @@ private static Annotation[] getAnnotations(MemberDetails memberDetails) { protected ParameterType createParameterType() { try { - final String[] columnNames = new String[ columns.size() ]; - final Long[] columnLengths = new Long[ columns.size() ]; - for ( int i = 0; i < columns.size(); i++ ) { - final var selectable = columns.get(i); - if ( selectable instanceof Column column ) { + final int size = columns.size(); + final var columnNames = new String[size]; + final var columnLengths = new Long[size]; + for ( int i = 0; i < size; i++ ) { + if ( columns.get(i) instanceof Column column ) { columnNames[i] = column.getName(); columnLengths[i] = column.getLength(); } @@ -1096,6 +1128,11 @@ public Type getType() { return SimpleValue.this.getType(); } + @Override + public MemberDetails getMemberDetails() { + return SimpleValue.this.getMemberDetails(); + } + // we could add this if it helps integrate old infrastructure // @Override // public Properties getParameters() { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Subclass.java b/hibernate-core/src/main/java/org/hibernate/mapping/Subclass.java index 8a420fb15bbe..d6c322557a24 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Subclass.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Subclass.java @@ -138,6 +138,11 @@ public List getPropertyClosure() { return new JoinedList<>( getSuperclass().getPropertyClosure(), getProperties() ); } + @Override + public List getAllPropertyClosure() { + return new JoinedList<>( getSuperclass().getAllPropertyClosure(), getAllProperties() ); + } + @Override public List
getTableClosure() { return new JoinedList<>( @@ -238,6 +243,11 @@ public int getPropertyClosureSpan() { return getSuperclass().getPropertyClosureSpan() + super.getPropertyClosureSpan(); } + @Override + public int getAllPropertyClosureSpan() { + return getSuperclass().getAllPropertyClosureSpan() + super.getAllPropertyClosureSpan(); + } + @Override public List getJoinClosure() { return new JoinedList<>( getSuperclass().getJoinClosure(), super.getJoinClosure() ); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java index 04d3f60d8a6b..79e2a29d16bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java @@ -30,7 +30,6 @@ import org.hibernate.dialect.Dialect; import org.hibernate.resource.transaction.spi.DdlTransactionIsolator; -import org.jboss.logging.Logger; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; @@ -46,7 +45,6 @@ * @author Gavin King */ public class Table implements Serializable, ContributableDatabaseObject { - private static final Logger LOG = Logger.getLogger( Table.class ); private static final Column[] EMPTY_COLUMN_ARRAY = new Column[0]; private final String contributor; @@ -110,8 +108,9 @@ public Table( String subselect, boolean isAbstract) { this.contributor = contributor; - this.catalog = namespace.getPhysicalName().catalog(); - this.schema = namespace.getPhysicalName().schema(); + final var physicalName = namespace.getPhysicalName(); + this.catalog = physicalName.catalog(); + this.schema = physicalName.schema(); this.name = physicalTableName; this.subselect = subselect; this.isAbstract = isAbstract; @@ -119,8 +118,9 @@ public Table( public Table(String contributor, Namespace namespace, String subselect, boolean isAbstract) { this.contributor = contributor; - this.catalog = namespace.getPhysicalName().catalog(); - this.schema = namespace.getPhysicalName().schema(); + final var physicalName = namespace.getPhysicalName(); + this.catalog = physicalName.catalog(); + this.schema = physicalName.schema(); this.subselect = subselect; this.isAbstract = isAbstract; } @@ -247,7 +247,7 @@ public Column getColumn(Column column) { return null; } else { - final Column existing = columns.get( column.getCanonicalName() ); + final var existing = columns.get( column.getCanonicalName() ); return column.equals( existing ) ? existing : null; } } @@ -276,15 +276,11 @@ public void addColumn(Column column) { if ( oldColumn == null ) { if ( primaryKey != null ) { for ( var primaryKeyColumn : primaryKey.getColumns() ) { - if ( primaryKeyColumn.getCanonicalName().equals( column.getCanonicalName() ) ) { + if ( Objects.equals( column.getCanonicalName(), + primaryKeyColumn.getCanonicalName() ) ) { + // Force the column to be non-null + // as it is part of the primary key column.setNullable( false ); - if ( LOG.isTraceEnabled() ) { - LOG.tracef( - "Forcing column [%s] to be non-null as it is part of the primary key for table [%s]", - column.getCanonicalName(), - getNameIdentifier().getCanonicalName() - ); - } } } } @@ -452,18 +448,21 @@ public void setPrimaryKey(PrimaryKey primaryKey) { } public Index getOrCreateIndex(String indexName) { - Index index = indexes.get( indexName ); - if ( index == null ) { - index = new Index(); - index.setName( indexName ); - index.setTable( this ); - indexes.put( indexName, index ); + final var index = indexes.get( indexName ); + if ( index != null ) { + return index; + } + else { + final var newIndex = new Index(); + newIndex.setName( indexName ); + newIndex.setTable( this ); + indexes.put( indexName, newIndex ); + return newIndex; } - return index; } public Index getIndex(String indexName) { - return indexes.get( indexName ); + return indexes.get( indexName ); } public Index addIndex(Index index) { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/ToOne.java b/hibernate-core/src/main/java/org/hibernate/mapping/ToOne.java index 4826d44f23cf..575aad120ebb 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/ToOne.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/ToOne.java @@ -176,7 +176,7 @@ public int[] sortProperties() { ? entityBinding.getIdentifier() : entityBinding.getRecursiveProperty( referencedPropertyName ).getValue(); if ( value instanceof Component component ) { - final int[] originalPropertyOrder = component.sortProperties(); + final var originalPropertyOrder = component.sortProperties(); if ( !sorted ) { if ( originalPropertyOrder != null ) { sortColumns( originalPropertyOrder ); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Value.java b/hibernate-core/src/main/java/org/hibernate/mapping/Value.java index a31aab07297d..4046095b8284 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Value.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Value.java @@ -51,6 +51,12 @@ public interface Value extends Serializable { */ List getColumns(); + /** + * Does the mapping involve at least one column? + * @since 7.3 + */ + boolean hasColumns(); + /** * Same as {@link #getSelectables()} except it returns the PK for the * non-owning side of a one-to-one association. diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java index 9277ea7e1098..940c0745304d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java @@ -212,7 +212,7 @@ private DomainType determineSimpleType(ValueContext typeContext) { public static DomainType determineSimpleType(ValueContext typeContext, MetadataContext context) { return switch ( typeContext.getValueClassification() ) { case BASIC -> basicDomainType( typeContext, context ); - case ENTITY -> entityDomainType (typeContext, context ); + case ENTITY -> entityDomainType( typeContext, context ); case EMBEDDABLE -> embeddableDomainType( typeContext, context ); default -> throw new AssertionFailure( "Unknown type : " + typeContext.getValueClassification() ); }; @@ -367,7 +367,7 @@ private static JavaType determineRelationalJavaType( final var descriptor = value.getJpaAttributeConverterDescriptor(); if ( descriptor != null ) { return context.getJavaTypeRegistry().resolveDescriptor( - descriptor.getRelationalValueResolvedType().getErasedType() + descriptor.getRelationalValueResolvedType() ); } } @@ -699,7 +699,7 @@ private static Member resolveVirtualIdentifierMember( Property property, EntityP private static Member resolveEntityMember(Property property, EntityPersister declaringEntity) { final String propertyName = property.getName(); return !propertyName.equals( declaringEntity.getIdentifierPropertyName() ) - && declaringEntity.findAttributeMapping( propertyName ) == null + && declaringEntity.findAttributeMapping( propertyName ) == null && !property.isGeneric() // just like in #determineIdentifierJavaMember , this *should* indicate we have an IdClass mapping ? resolveVirtualIdentifierMember( property, declaringEntity ) : getter( declaringEntity, property, propertyName, property.getType().getReturnedClass() ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableRepresentationStrategyPojo.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableRepresentationStrategyPojo.java index ff64b3d31b40..9c345ec97b82 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableRepresentationStrategyPojo.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableRepresentationStrategyPojo.java @@ -120,13 +120,13 @@ public EmbeddableRepresentationStrategyPojo( } } - private static JavaType resolveEmbeddableJavaType( + private static JavaType resolveEmbeddableJavaType( Component bootDescriptor, CompositeUserType compositeUserType, RuntimeModelCreationContext creationContext) { final var javaTypeRegistry = creationContext.getTypeConfiguration().getJavaTypeRegistry(); return compositeUserType == null - ? javaTypeRegistry.getDescriptor( bootDescriptor.getComponentClass() ) + ? javaTypeRegistry.resolveDescriptor( bootDescriptor.getComponentClass() ) : javaTypeRegistry.resolveDescriptor( compositeUserType.returnedClass(), () -> new CompositeUserTypeJavaTypeWrapper<>( compositeUserType ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EntityRepresentationStrategyPojoStandard.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EntityRepresentationStrategyPojoStandard.java index 32b3a1216e61..ccd81ab420df 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EntityRepresentationStrategyPojoStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EntityRepresentationStrategyPojoStandard.java @@ -157,7 +157,7 @@ else if ( entityPersister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoad private Map buildPropertyAccessMap(PersistentClass bootDescriptor) { final Map propertyAccessMap = new LinkedHashMap<>(); - for ( var property : bootDescriptor.getPropertyClosure() ) { + for ( var property : bootDescriptor.getAllPropertyClosure() ) { propertyAccessMap.put( property.getName(), makePropertyAccess( property ) ); } return propertyAccessMap; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java index c0c3949b7bf9..acd56efea11c 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java @@ -319,6 +319,10 @@ public void wrapUp() { // skip the version property, it was already handled previously. continue; } + if ( property.isGenericSpecialization() ) { + // Skip generic properties since they may only be declared on abstract classes + continue; + } buildAttribute( property, jpaMapping ); } @@ -655,6 +659,16 @@ private void applyGenericProperties(PersistentClass persistentClass, EntityD } mappedSuperclass = getMappedSuperclass( mappedSuperclass ); } + if ( persistentClass.isAbstract() == null || !persistentClass.isAbstract() ) { + for ( var property : persistentClass.getDeclaredProperties() ) { + if ( property.isGenericSpecialization() ) { + final var managedType = (ManagedDomainType) entityType; + final var attributeContainer = (AttributeContainer) managedType; + attributeContainer.getInFlightAccess() + .addConcreteGenericAttribute( buildAttribute( entityType, property ) ); + } + } + } } private MappedSuperclass getMappedSuperclass(PersistentClass persistentClass) { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java index 57f9b8762b89..6d39f18ec885 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java @@ -5,7 +5,10 @@ package org.hibernate.metamodel.mapping; import jakarta.persistence.Entity; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.Filter; +import org.hibernate.HibernateException; import org.hibernate.Incubating; import org.hibernate.Internal; import org.hibernate.annotations.ConcreteProxy; @@ -45,6 +48,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; +import static java.lang.String.format; import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY; /** @@ -363,7 +367,16 @@ default OptimisticLockStyle optimisticLockStyle() { /** * The mapping for the natural-id of the entity, if one is defined */ - NaturalIdMapping getNaturalIdMapping(); + @Nullable NaturalIdMapping getNaturalIdMapping(); + + @NonNull + default NaturalIdMapping requireNaturalIdMapping() { + var mapping = getNaturalIdMapping(); + if ( mapping == null ) { + throw new HibernateException( format( "Entity %s does not specify a natural id", getEntityName() ) ); + } + return mapping; + } /** * The mapping for the row-id of the entity, if one is defined. diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/NaturalIdMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/NaturalIdMapping.java index 8ecf1ef79562..e066da830037 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/NaturalIdMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/NaturalIdMapping.java @@ -6,52 +6,49 @@ import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.Incubating; import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.ast.spi.MultiNaturalIdLoader; import org.hibernate.loader.ast.spi.NaturalIdLoader; -/** - * Mapping for an entity's natural-id, if one is defined. - *

- * Natural identifiers are an alternative form of uniquely - * identifying a specific row. In this sense, they are similar - * to a primary key. In fact most natural identifiers will also - * be classified as "candidate keys" (as in a column or group of - * columns that are considered candidates for primary-key). - *

- * However, a natural id has fewer restrictions than a primary - * key. While these lessened restrictions make them inappropriate - * for use as a primary key, they are still generally usable as - * unique locators with caveats. General reasons a natural id - * might be inappropriate for use as a primary key are

    - *
  • it contains nullable values
  • - *
  • it contains modifiable values
  • - *
- *

- * See other sources for a more complete discussion of data modeling. - * - * @see org.hibernate.Session#byNaturalId - * @see org.hibernate.Session#bySimpleNaturalId - * @see org.hibernate.Session#byMultipleNaturalId - * - * @author Steve Ebersole - */ +/// Mapping for an entity's natural-id, if one is defined. +/// +/// Natural identifiers are an alternative form of uniquely +/// identifying a specific row. In this sense, they are similar +/// to a primary key. In fact most natural identifiers will also +/// be classified as "candidate keys" (as in a column or group of +/// columns that are considered candidates for primary-key). +/// +/// However, a natural id has fewer restrictions than a primary +/// key. While these lessened restrictions make them inappropriate +/// for use as a primary key, they are still generally usable as +/// unique locators with caveats. General reasons a natural id +/// might be inappropriate for use as a primary key are +/// - it contains nullable values +/// - it contains modifiable values +/// +/// @see org.hibernate.annotations.NaturalId +/// @see org.hibernate.annotations.NaturalIdCache +/// @see org.hibernate.annotations.NaturalIdClass +/// @see org.hibernate.KeyType#NATURAL +/// +/// @author Steve Ebersole @Incubating public interface NaturalIdMapping extends VirtualModelPart { String PART_NAME = "{natural-id}"; - /** - * The attribute(s) making up the natural-id. - */ + /// A [class][org.hibernate.annotations.NaturalIdClass] which used + /// as a wrapper for natural-id values. + @Nullable Class getNaturalIdClass(); + + /// The attribute(s) making up the natural-id. List getNaturalIdAttributes(); - /** - * Whether the natural-id is mutable. - * - * @apiNote For compound natural-ids, this is true if any of the attributes are mutable. - */ + /// Whether the natural-id is mutable. + /// + /// @apiNote For compound natural-ids, this is true if any of the attributes are mutable. boolean isMutable(); @Override @@ -59,67 +56,54 @@ default String getPartName() { return PART_NAME; } - /** - * Access to the natural-id's L2 cache access. Returns null if the natural-id is not - * configured for caching - */ + /// Access to the natural-id's L2 cache access. + /// Returns null if the natural-id is not configured for caching. NaturalIdDataAccess getCacheAccess(); - /** - * Verify the natural-id value(s) we are about to flush to the database - */ + /// Verify the natural-id value(s) we are about to flush to the database void verifyFlushState( Object id, Object[] currentState, Object[] loadedState, SharedSessionContractImplementor session); - /** - * Given an array of "full entity state", extract the normalized natural id representation - * - * @param state The attribute state array - * - * @return The extracted natural id values. This is a normalized - */ + /// Given an array of "full entity state", extract the normalized natural id representation. + /// + /// @param state The attribute state array + /// + /// @return The extracted natural id values. Object extractNaturalIdFromEntityState(Object[] state); - /** - * Given an entity instance, extract the normalized natural id representation - * - * @param entity The entity instance - * - * @return The extracted natural id values - */ + /// Given an entity instance, extract the normalized natural-id representation. + /// + /// @param entity The entity instance + /// + /// @return The extracted natural-id values. Object extractNaturalIdFromEntity(Object entity); - /** - * Normalize a user-provided natural-id value into the representation Hibernate uses internally - * - * @param incoming The user-supplied value - * @return The normalized, internal representation - */ + /// Normalize a user-provided natural-id value into the representation Hibernate uses internally. + /// + /// @param incoming The user-supplied value + /// @return The normalized, internal representation Object normalizeInput(Object incoming); - /** - * Validates a natural id value(s) for the described natural-id based on the expected internal representation - */ + /// Whether the incoming value is in normalized internal form. + /// + /// @see #normalizeInput + boolean isNormalized(Object incoming); + + /// Validates a natural id value(s) for the described natural-id based on the expected internal representation void validateInternalForm(Object naturalIdValue); - /** - * Calculate the hash-code of a natural-id value - * - * @param value The natural-id value - * @return The hash-code - */ + /// Calculate the hash-code of a natural-id value + /// + /// @param value The natural-id value + /// @return The hash-code int calculateHashCode(Object value); - /** - * Make a loader capable of loading a single entity by natural-id - */ + /// Make a loader capable of loading a single entity by natural-id NaturalIdLoader makeLoader(EntityMappingType entityDescriptor); - /** - * Make a loader capable of loading multiple entities by natural-id - */ + /// Make a loader capable of loading multiple entities by natural-id MultiNaturalIdLoader makeMultiLoader(EntityMappingType entityDescriptor); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ValueMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ValueMapping.java index d2069e238121..e2f730dd19f3 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ValueMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ValueMapping.java @@ -40,8 +40,7 @@ default JavaType getExpressibleJavaType() { */ default X treatAs(Class targetType) { if ( targetType.isInstance( this ) ) { - //noinspection unchecked - return (X) this; + return targetType.cast( this ); } throw new IllegalArgumentException( diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractNaturalIdMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractNaturalIdMapping.java index 52254432816e..bc048fff29c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractNaturalIdMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractNaturalIdMapping.java @@ -19,7 +19,9 @@ public abstract class AbstractNaturalIdMapping implements NaturalIdMapping { private final NavigableRole role; - public AbstractNaturalIdMapping(EntityMappingType declaringType, boolean mutable) { + public AbstractNaturalIdMapping( + EntityMappingType declaringType, + boolean mutable) { this.declaringType = declaringType; this.mutable = mutable; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java index 395328a6595e..0bcd550b280b 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java @@ -4,15 +4,12 @@ */ package org.hibernate.metamodel.mapping.internal; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; +import org.hibernate.MappingException; import org.hibernate.cache.MutableCacheKeyBuilder; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.IndexedConsumer; import org.hibernate.loader.ast.internal.CompoundNaturalIdLoader; @@ -20,6 +17,7 @@ import org.hibernate.loader.ast.spi.MultiNaturalIdLoader; import org.hibernate.loader.ast.spi.NaturalIdLoader; import org.hibernate.metamodel.UnsupportedMappingException; +import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; @@ -27,6 +25,11 @@ import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.SingularAttributeMapping; +import org.hibernate.models.spi.ClassDetails; +import org.hibernate.models.spi.ModelsContext; +import org.hibernate.property.access.spi.Getter; +import org.hibernate.property.access.spi.GetterFieldImpl; +import org.hibernate.property.access.spi.GetterMethodImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.from.TableGroup; @@ -44,14 +47,25 @@ import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.type.descriptor.java.JavaType; +import java.beans.Introspector; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.RecordComponent; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + /** * Multi-attribute NaturalIdMapping implementation */ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implements MappingType, FetchableContainer { - // todo (6.0) : create a composite MappingType for this descriptor's Object[]? - private final List attributes; + private final ValueNormalizer valueNormalizer; private List jdbcMappings; /* @@ -60,11 +74,15 @@ This value is used to determine the size of the array used to create the Immutab */ private final int maxFetchableKeyIndex; + private final SessionFactoryImplementor sessionFactory; + public CompoundNaturalIdMapping( EntityMappingType declaringType, + ClassDetails naturalIdClass, List attributes, MappingModelCreationProcess creationProcess) { super( declaringType, isMutable( attributes ) ); + this.valueNormalizer = createValueNormalizer( naturalIdClass, attributes, creationProcess ); this.attributes = attributes; int maxIndex = 0; @@ -88,6 +106,8 @@ public CompoundNaturalIdMapping( return true; } ); + + this.sessionFactory = creationProcess.getCreationContext().getSessionFactory(); } private static boolean isMutable(List attributes) { @@ -99,6 +119,12 @@ private static boolean isMutable(List attributes) { return false; } + @Override + @Nullable + public Class getNaturalIdClass() { + return valueNormalizer.getIdClassType(); + } + @Override public Object[] extractNaturalIdFromEntityState(Object[] state) { if ( state == null ) { @@ -119,7 +145,7 @@ else if ( state.length == attributes.size() ) { @Override public Object[] extractNaturalIdFromEntity(Object entity) { - final var values = new Object[ attributes.size() ]; + final var values = new Object[attributes.size()]; for ( int i = 0; i < attributes.size(); i++ ) { values[i] = attributes.get( i ).getPropertyAccess().getGetter().get( entity ); } @@ -128,22 +154,22 @@ public Object[] extractNaturalIdFromEntity(Object entity) { @Override public Object[] normalizeInput(Object incoming) { + sessionFactory.getStatistics().normalizeNaturalId( getDeclaringType().getEntityName() ); + if ( incoming instanceof Object[] array ) { + // already normalized return array; } - else if ( incoming instanceof Map valueMap ) { - final var attributes = getNaturalIdAttributes(); - final var values = new Object[ attributes.size() ]; - for ( int i = 0; i < attributes.size(); i++ ) { - values[ i ] = valueMap.get( attributes.get( i ).getAttributeName() ); - } - return values; - } else { - throw new UnsupportedMappingException( "Could not normalize compound natural id value: " + incoming ); + return valueNormalizer.normalize( incoming ); } } + @Override + public boolean isNormalized(Object incoming) { + return incoming instanceof Object[]; + } + @Override public void validateInternalForm(Object naturalIdValue) { if ( naturalIdValue != null ) { @@ -343,7 +369,7 @@ else if ( domainValue instanceof Object[] values ) { } } else { - throw new AssertionFailure("Unexpected domain value type"); + throw new AssertionFailure( "Unexpected domain value type" ); } return span; } @@ -394,7 +420,7 @@ else if ( value instanceof Object[] incoming ) { return outgoing; } else { - throw new AssertionFailure("Unexpected value"); + throw new AssertionFailure( "Unexpected value" ); } } @@ -412,7 +438,7 @@ else if ( value instanceof Object[] values ) { } } else { - throw new AssertionFailure("Unexpected value"); + throw new AssertionFailure( "Unexpected value" ); } } @@ -453,7 +479,7 @@ else if ( value instanceof Object[] incoming ) { } } else { - throw new AssertionFailure("Unexpected value"); + throw new AssertionFailure( "Unexpected value" ); } return span; } @@ -481,7 +507,7 @@ else if ( value instanceof Object[] incoming ) { } } else { - throw new AssertionFailure("Unexpected value"); + throw new AssertionFailure( "Unexpected value" ); } return span; } @@ -509,7 +535,7 @@ public ModelPart findSubPart(String name, EntityMappingType treatTargetType) { @Override public void forEachSubPart(IndexedConsumer consumer, EntityMappingType treatTarget) { for ( int i = 0; i < attributes.size(); i++ ) { - consumer.accept( i, attributes.get(i) ); + consumer.accept( i, attributes.get( i ) ); } } @@ -630,9 +656,9 @@ private AssemblerImpl(ImmutableFetchList fetches, JavaType jtd, Assemb @Override public Object[] assemble(RowProcessingState rowProcessingState) { - final var result = new Object[ subAssemblers.length ]; + final var result = new Object[subAssemblers.length]; for ( int i = 0; i < subAssemblers.length; i++ ) { - result[ i ] = subAssemblers[i].assemble( rowProcessingState ); + result[i] = subAssemblers[i].assemble( rowProcessingState ); } return result; } @@ -662,4 +688,248 @@ public JavaType getAssembledJavaType() { } } + interface ValueNormalizer { + boolean isInstance(Object value); + Object[] normalize(Object value); + Class getIdClassType(); + } + + private static ValueNormalizer createValueNormalizer( + ClassDetails naturalIdClassDetails, + List keyAttributes, + MappingModelCreationProcess creationProcess) { + if ( naturalIdClassDetails == null ) { + return new ValueNormalizerSupport( keyAttributes ); + } + + final ModelsContext modelsContext = creationProcess + .getCreationContext() + .getBootstrapContext() + .getModelsContext(); + + var naturalIdClass = naturalIdClassDetails.toJavaClass( modelsContext.getClassLoading(), modelsContext ); + var naturalIdClassComponents = extractComponents( naturalIdClass ); + var naturalIdClassGetterAccess = createNaturalIdClassGetterAccess( naturalIdClass ); + + final List> attributeMappers = new ArrayList<>(); + keyAttributes.forEach( (keyAttribute) -> { + // find the matching MemberDetails on the `naturalIdClass`... + final Getter extractor = resolveMatchingExtractor( + naturalIdClass, + keyAttribute, + naturalIdClassGetterAccess, + naturalIdClassComponents, + modelsContext + ); + // todo (natural-id-class) : atm there is functionally no difference + // between BasicAttributeMapperImpl and ToOneAttributeMapperImpl. + // ideally we'd eventually support usage of the associated key entity's + // id and then there would. see the note in ToOneAttributeMapperImpl#extractFrom + final AttributeMapper attrMapper; + if ( keyAttribute instanceof ToOneAttributeMapping ) { + attrMapper = new ToOneAttributeMapperImpl<>( keyAttribute, extractor ); + } + else { + attrMapper = new BasicAttributeMapperImpl<>( keyAttribute, extractor ); + } + attributeMappers.add( attrMapper ); + } ); + + //noinspection unchecked,rawtypes + return new KeyClassNormalizer( keyAttributes, naturalIdClass, attributeMappers ); + } + + static class ValueNormalizerSupport implements ValueNormalizer { + private final List naturalIdAttributes; + + public ValueNormalizerSupport(List naturalIdAttributes) { + this.naturalIdAttributes = naturalIdAttributes; + } + + @Override + public boolean isInstance(Object value) { + return value instanceof Map; + } + + @Override + public Object[] normalize(Object incoming) { + if ( !isInstance( incoming ) ) { + throw new UnsupportedMappingException( "Could not normalize compound natural id value: " + incoming ); + } + final var values = new Object[naturalIdAttributes.size()]; + //noinspection unchecked + final Map valuesMap = (Map) incoming; + for ( int i = 0; i < naturalIdAttributes.size(); i++ ) { + values[i] = valuesMap.get( naturalIdAttributes.get( i ).getAttributeName() ); + } + return values; + } + + @Override + public Class getIdClassType() { + return null; + } + } + + /// Responsible for decomposing a value of the NaturalIdClass into the internal array format + static class KeyClassNormalizer extends ValueNormalizerSupport { + private final Class idClassType; + private final List> idClassAttributeMappers; + + public KeyClassNormalizer( + List naturalIdAttributes, + Class idClassType, + List> idClassAttributeMappers) { + super( naturalIdAttributes ); + this.idClassType = idClassType; + this.idClassAttributeMappers = idClassAttributeMappers; + } + + @Override + public Class getIdClassType() { + return idClassType; + } + + @Override + public Object[] normalize(Object value) { + if ( idClassType.isInstance( value ) ) { + return doNormalize( idClassType.cast( value ) ); + } + + return super.normalize( value ); + } + + public Object[] doNormalize(T idClassValue) { + final Object[] result = new Object[idClassAttributeMappers.size()]; + for ( int i = 0; i < idClassAttributeMappers.size(); i++ ) { + var value = idClassAttributeMappers.get( i ).extractFrom( idClassValue ); + result[i] = value; + } + return result; + } + + public boolean isInstance(Object value) { + return idClassType.isInstance( value ) || super.isInstance( value ); + } + } + + private static Function createNaturalIdClassGetterAccess(Class naturalIdClass) { + return new Function<>() { + private Map getterMethods; + @Override + public Method apply(String name) { + if ( getterMethods == null ) { + getterMethods = extractGetterMethods( naturalIdClass ); + } + return getterMethods.get( name ); + } + }; + } + + private static Getter resolveMatchingExtractor( + Class naturalIdClass, + AttributeMapping keyAttribute, + Function getterMethodAccess, + Map naturalIdClassComponents, + ModelsContext modelsContext) { + // first, if the `naturalIdClass` is a record, look for a component + if ( naturalIdClass.isRecord() ) { + var component = naturalIdClassComponents.get( keyAttribute.getAttributeName() ); + if ( component != null ) { + return new GetterMethodImpl( + naturalIdClass, + keyAttribute.getAttributeName(), + component.getAccessor() + ); + } + } + + // next look for a getter method + var getterMethod = getterMethodAccess.apply( keyAttribute.getAttributeName() ); + if ( getterMethod != null ) { + return new GetterMethodImpl( + naturalIdClass, + keyAttribute.getAttributeName(), + getterMethod + ); + } + + // lastly, look for a field + try { + var field = naturalIdClass.getDeclaredField( keyAttribute.getAttributeName() ); + return new GetterFieldImpl( naturalIdClass, keyAttribute.getAttributeName(), field ); + } + catch (NoSuchFieldException ignore) { + } + + throw new MappingException( "Unable to find NaturalIdClass accessor for natural-id attribute: " + keyAttribute.getAttributeName() ); + } + + private static Map extractGetterMethods(Class naturalIdClass) { + final Map result = new HashMap<>(); + + for ( Method declaredMethod : naturalIdClass.getDeclaredMethods() ) { + if ( declaredMethod.getParameterCount() == 0 + && declaredMethod.getReturnType() != void.class + && !Modifier.isStatic( declaredMethod.getModifiers() ) ) { + var methodName = declaredMethod.getName(); + if ( methodName.startsWith( "is" ) ) { + result.put( + Introspector.decapitalize( methodName.substring( 2 ) ), + declaredMethod + ); + } + else if ( methodName.startsWith( "get" ) ) { + result.put( + Introspector.decapitalize( methodName.substring( 3 ) ), + declaredMethod + ); + } + } + } + + return result; + } + + private static Map extractComponents(Class naturalIdClass) { + if ( !naturalIdClass.isRecord() ) { + return Map.of(); + } + + final RecordComponent[] recordComponents = naturalIdClass.getRecordComponents(); + final Map result = new HashMap<>(); + for ( RecordComponent recordComponent : recordComponents ) { + result.put( recordComponent.getName(), recordComponent ); + } + return result; + } + + public interface AttributeMapper { + V extractFrom(T keyValue); + } + + /// AttributeMapper for both basic and embedded values + public record BasicAttributeMapperImpl(AttributeMapping entityAttribute, Getter keyClassExtractor) + implements AttributeMapper { + @Override + public Object extractFrom(T keyValue) { + return keyClassExtractor.get( keyValue ); + } + } + + /// AttributeMapper for to-one values + public record ToOneAttributeMapperImpl(AttributeMapping entityAttribute, Getter keyClassExtractor) + implements AttributeMapper { + @Override + public Object extractFrom(T keyValue) { + // todo (natural-id-class) : handle "key -> to-one" resolutions + // this requires some contract changes though to pass Session + // to be able to resolve key -> entity for the to-one. + // + + /// the other difficulty is handling "derived id" structures + // + // see `NaturalIdMapping#normalizeInput` + return keyClassExtractor.get( keyValue ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/GeneratedValuesProcessor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/GeneratedValuesProcessor.java index 9b8473a974d8..7c6b183476f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/GeneratedValuesProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/GeneratedValuesProcessor.java @@ -4,9 +4,6 @@ */ package org.hibernate.metamodel.mapping.internal; -import java.util.ArrayList; -import java.util.List; - import org.hibernate.Incubating; import org.hibernate.LockMode; import org.hibernate.LockOptions; @@ -25,11 +22,14 @@ import org.hibernate.query.spi.QueryOptions; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.internal.RowTransformerArrayImpl; +import java.util.ArrayList; +import java.util.List; + import static org.hibernate.sql.results.spi.ListResultsConsumer.UniqueSemantic.FILTER; /** @@ -51,7 +51,7 @@ @Incubating public class GeneratedValuesProcessor { private final SelectStatement selectStatement; - private final JdbcOperationQuerySelect jdbcSelect; + private final JdbcSelect jdbcSelect; private final List generatedValuesToSelect; private final JdbcParametersList jdbcParameters; @@ -217,7 +217,7 @@ public EntityMappingType getEntityDescriptor() { return entityDescriptor; } - public JdbcOperationQuerySelect getJdbcSelect() { + public JdbcSelect getJdbcSelect() { return jdbcSelect; } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java index b81f892f4ff0..7dff3a5b2219 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java @@ -65,12 +65,12 @@ public NonAggregatedIdentifierMappingImpl( super( entityPersister, rootTableName, creationProcess ); entityDescriptor = entityPersister; - if ( bootEntityDescriptor.getIdentifierMapper() == null - || bootEntityDescriptor.getIdentifierMapper() == bootEntityDescriptor.getIdentifier() ) { + final var identifierMapper = bootEntityDescriptor.getIdentifierMapper(); + final var identifier = bootEntityDescriptor.getIdentifier(); + if ( identifierMapper == null || identifierMapper == identifier ) { // cid -> getIdentifier // idClass -> null - final Component virtualIdSource = (Component) bootEntityDescriptor.getIdentifier(); - + final var virtualIdSource = (Component) identifier; virtualIdEmbeddable = new VirtualIdEmbeddable( virtualIdSource, this, @@ -85,9 +85,8 @@ public NonAggregatedIdentifierMappingImpl( else { // cid = getIdentifierMapper // idClass = getIdentifier - final var virtualIdSource = bootEntityDescriptor.getIdentifierMapper(); - final var idClassSource = (Component) bootEntityDescriptor.getIdentifier(); - + final var virtualIdSource = identifierMapper; + final var idClassSource = (Component) identifier; virtualIdEmbeddable = new VirtualIdEmbeddable( virtualIdSource, this, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java index 09bfb164a96f..b112b114e507 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java @@ -32,6 +32,7 @@ import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SoftDeleteMapping; import org.hibernate.metamodel.mapping.TableDetails; +import org.hibernate.metamodel.mapping.ValuedModelPart; import org.hibernate.metamodel.mapping.ordering.OrderByFragment; import org.hibernate.metamodel.mapping.ordering.OrderByFragmentTranslator; import org.hibernate.metamodel.mapping.ordering.TranslationContext; @@ -225,11 +226,31 @@ private static void injectAttributeMapping( @Override public boolean isBidirectionalAttributeName(NavigablePath fetchablePath, ToOneAttributeMapping modelPart) { return bidirectionalAttributeName == null - // If the FK-target of the to-one mapping is the same as the FK-target of this plural mapping, - // then we say this is bidirectional, given that this is only invoked for model parts of the - // collection elements - ? fkDescriptor.getTargetPart() == modelPart.getForeignKeyDescriptor().getTargetPart() - : fetchablePath.getLocalName().endsWith( bidirectionalAttributeName ); + // If the FK-target of the to-one mapping is the same as the FK-target of this one-to-many mapping, + // and the FK-key refer to the same column then we say this is bidirectional, + // given that this is only invoked for model parts of the collection elements + ? modelPart.getSideNature() == ForeignKeyDescriptor.Nature.KEY + && collectionDescriptor.isOneToMany() + && fkDescriptor.getTargetPart() == modelPart.getForeignKeyDescriptor().getTargetPart() + && areEqual( fkDescriptor.getKeyPart(), modelPart.getForeignKeyDescriptor().getKeyPart() ) + : fetchablePath.getLocalName().equals( bidirectionalAttributeName ); + } + + private boolean areEqual(ValuedModelPart part1, ValuedModelPart part2) { + final int typeCount = part1.getJdbcTypeCount(); + if ( part2.getJdbcTypeCount() != typeCount ) { + return false; + } + for ( int i = 0; i < typeCount; i++ ) { + final SelectableMapping selectable1 = part1.getSelectable( i ); + final SelectableMapping selectable2 = part2.getSelectable( i ); + if ( selectable1.getJdbcMapping() != selectable2.getJdbcMapping() + || !selectable1.getContainingTableExpression().equals( selectable2.getContainingTableExpression() ) + || !selectable1.getSelectionExpression().equals( selectable2.getSelectionExpression() ) ) { + return false; + } + } + return true; } public void finishInitialization( diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java index 555117bf9d97..b53d6688ea4f 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.function.BiConsumer; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.HibernateException; import org.hibernate.cache.MutableCacheKeyBuilder; import org.hibernate.dialect.Dialect; @@ -34,7 +35,6 @@ import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.spi.TypeConfiguration; import static org.hibernate.loader.ast.internal.MultiKeyLoadHelper.supportsSqlArrayType; @@ -42,10 +42,9 @@ * Single-attribute NaturalIdMapping implementation */ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping - implements JavaType.CoercionContext, BasicValuedMapping { + implements BasicValuedMapping { private final SingularAttributeMapping attribute; private final SessionFactoryImplementor sessionFactory; - private final TypeConfiguration typeConfiguration; public SimpleNaturalIdMapping( SingularAttributeMapping attribute, @@ -54,7 +53,6 @@ public SimpleNaturalIdMapping( super( declaringType, attribute.getAttributeMetadata().isUpdatable() ); this.attribute = attribute; this.sessionFactory = creationProcess.getCreationContext().getSessionFactory(); - this.typeConfiguration = creationProcess.getCreationContext().getTypeConfiguration(); } public SingularAttributeMapping getAttribute() { @@ -106,19 +104,24 @@ public Object extractNaturalIdFromEntity(Object entity) { return attribute.getPropertyAccess().getGetter().get( entity ); } + @Override + public boolean isNormalized(Object incoming) { + return incoming == null || getJavaType().getJavaTypeClass().isInstance( incoming ); + } + @Override public void validateInternalForm(Object naturalIdValue) { if ( naturalIdValue != null ) { final var naturalIdValueClass = naturalIdValue.getClass(); + // be flexible - allow a single-valued array if ( naturalIdValueClass.isArray() && !naturalIdValueClass.getComponentType().isPrimitive() ) { - // be flexible final var values = (Object[]) naturalIdValue; if ( values.length == 1 ) { naturalIdValue = values[0]; } } - if ( !getJavaType().getJavaTypeClass().isInstance( naturalIdValue ) ) { + if ( !getJavaType().isInstance( naturalIdValue ) ) { throw new IllegalArgumentException( String.format( Locale.ROOT, @@ -143,10 +146,12 @@ public Object normalizeInput(Object incoming) { final Object normalizedValue = normalizedValue( incoming ); return isLoadByIdComplianceEnabled() ? normalizedValue - : getJavaType().coerce( normalizedValue, this ); + : getJavaType().coerce( normalizedValue ); } private Object normalizedValue(Object incoming) { + sessionFactory.getStatistics().normalizeNaturalId( getDeclaringType().getEntityName() ); + if ( incoming instanceof Map valueMap ) { assert valueMap.size() == 1; assert valueMap.containsKey( getAttribute().getAttributeName() ); @@ -170,6 +175,12 @@ public List getNaturalIdAttributes() { return Collections.singletonList( attribute ); } + @Override + @Nullable + public Class getNaturalIdClass() { + return null; + } + @Override public MappingType getPartMappingType() { return attribute.getPartMappingType(); @@ -295,11 +306,6 @@ private Dialect getDialect() { return sessionFactory.getJdbcServices().getDialect(); } - @Override - public TypeConfiguration getTypeConfiguration() { - return typeConfiguration; - } - @Override public AttributeMapping asAttributeMapping() { return getAttribute(); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java index ca496516c904..2d0b9166c5f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java @@ -34,7 +34,7 @@ public interface JpaMetamodel extends Metamodel { /** * Access to a managed type through its name */ - ManagedDomainType managedType(String typeName); + ManagedDomainType managedType(String typeName); /** * Access to an entity supporting Hibernate's entity-name feature @@ -50,13 +50,13 @@ public interface JpaMetamodel extends Metamodel { * Specialized handling for resolving entity-name references in * an HQL query */ - EntityDomainType getHqlEntityReference(String entityName); + EntityDomainType getHqlEntityReference(String entityName); /** * Specialized handling for resolving entity-name references in * an HQL query */ - EntityDomainType resolveHqlEntityReference(String entityName); + EntityDomainType resolveHqlEntityReference(String entityName); /** * Same as {@link #managedType(Class)} except {@code null} is returned rather @@ -80,7 +80,7 @@ public interface JpaMetamodel extends Metamodel { * Same as {@link #managedType(String)} except {@code null} is returned rather * than throwing an exception */ - @Nullable ManagedDomainType findManagedType(@Nullable String typeName); + @Nullable ManagedDomainType findManagedType(@Nullable String typeName); /** * Same as {@link #entity(String)} except {@code null} is returned rather diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractAttribute.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractAttribute.java index 22b999de5b4c..55c45407641a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractAttribute.java @@ -63,8 +63,11 @@ public String getName() { @Override public Class getJavaType() { - return valueType instanceof BasicTypeImpl basicType - ? basicType.getJavaType() + // TODO: create a new method to abstract this logic + return valueType instanceof BasicTypeImpl basicType + // handles primitives in basic types + ? (Class) basicType.getJavaType() + // good for everything else : attributeJtd.getJavaTypeClass(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractIdentifiableType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractIdentifiableType.java index e3edd41e03c2..6b423d5c2d04 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractIdentifiableType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractIdentifiableType.java @@ -114,14 +114,15 @@ public boolean hasSingleIdAttribute() { } @Override - @SuppressWarnings("unchecked") public SqmSingularPersistentAttribute getId(Class javaType) { ensureNoIdClass(); final var id = findIdAttribute(); if ( id != null ) { checkType( id, javaType ); } - return (SqmSingularPersistentAttribute) id; + @SuppressWarnings("unchecked") // safe, we just checked + final var castId = (SqmSingularPersistentAttribute) id; + return castId; } private void ensureNoIdClass() { @@ -148,8 +149,7 @@ else if ( getSuperType() != null ) { private void checkType(SingularPersistentAttribute attribute, Class javaType) { if ( !javaType.isAssignableFrom( attribute.getType().getJavaType() ) ) { - final JavaType attributeJavaType = attribute.getAttributeJavaType(); - if ( !( attributeJavaType instanceof PrimitiveJavaType primitiveJavaType ) + if ( !( attribute.getAttributeJavaType() instanceof PrimitiveJavaType primitiveJavaType ) || primitiveJavaType.getPrimitiveClass() != javaType ) { throw new IllegalArgumentException( String.format( @@ -165,14 +165,15 @@ private void checkType(SingularPersistentAttribute attribute, Class jav } @Override - @SuppressWarnings("unchecked") public SqmSingularPersistentAttribute getDeclaredId(Class javaType) { ensureNoIdClass(); if ( id == null ) { throw new IllegalArgumentException( "The id attribute is not declared on this type [" + getTypeName() + "]" ); } checkType( id, javaType ); - return (SqmSingularPersistentAttribute) id; + @SuppressWarnings("unchecked") // safe, we just checked + final var castId = (SqmSingularPersistentAttribute) id; + return castId; } @Override @@ -226,14 +227,16 @@ else if ( idClassType instanceof SimpleDomainType simpleDomainType ) { } @Override - @SuppressWarnings("unchecked") public void visitIdClassAttributes(Consumer> attributeConsumer) { if ( nonAggregatedIdAttributes != null ) { nonAggregatedIdAttributes.forEach( attributeConsumer ); } - else if ( getSuperType() != null ) { - //noinspection rawtypes - getSuperType().visitIdClassAttributes( (Consumer) attributeConsumer ); + else { + final var superType = getSuperType(); + if ( superType != null ) { + //noinspection rawtypes, unchecked + superType.visitIdClassAttributes( (Consumer) attributeConsumer ); + } } } @@ -247,14 +250,15 @@ public boolean hasDeclaredVersionAttribute() { } @Override - @SuppressWarnings("unchecked") public SingularPersistentAttribute getVersion(Class javaType) { if ( hasVersionAttribute() ) { final var version = findVersionAttribute(); if ( version != null ) { checkType( version, javaType ); } - return (SingularPersistentAttribute) version; + @SuppressWarnings("unchecked") // safe, we just checked + final var castVersion = (SingularPersistentAttribute) version; + return castVersion; } else { return null; @@ -288,11 +292,12 @@ else if ( getSuperType() != null ) { } @Override - @SuppressWarnings("unchecked") public SingularPersistentAttribute getDeclaredVersion(Class javaType) { checkDeclaredVersion(); checkType( versionAttribute, javaType ); - return (SingularPersistentAttribute) versionAttribute; + @SuppressWarnings("unchecked") // safe, we just checked + final var castVersion = (SingularPersistentAttribute) versionAttribute; + return castVersion; } private void checkDeclaredVersion() { @@ -360,9 +365,8 @@ public void applyNonAggregatedIdAttributes( nonAggregatedIdAttributes.add( (SqmSingularPersistentAttribute) idAttribute ); if ( AbstractIdentifiableType.this == idAttribute.getDeclaringType() ) { @SuppressWarnings("unchecked") - // Safe, because we know it's declared by this type - final PersistentAttribute declaredAttribute = - (PersistentAttribute) idAttribute; + // Safe, because we know it's declared by this type + final var declaredAttribute = (PersistentAttribute) idAttribute; addAttribute( declaredAttribute ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractManagedType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractManagedType.java index f9c319934dd6..25554e1a5d4b 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractManagedType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractManagedType.java @@ -126,8 +126,9 @@ public RepresentationMode getRepresentationMode() { @Override public void visitAttributes(Consumer> action) { visitDeclaredAttributes( action ); - if ( getSuperType() != null ) { - getSuperType().visitAttributes( action ); + final var superType = getSuperType(); + if ( superType != null ) { + superType.visitAttributes( action ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractPluralAttribute.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractPluralAttribute.java index 8fcf2003a79a..4c085a9b4546 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractPluralAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractPluralAttribute.java @@ -125,8 +125,9 @@ public SimpleDomainType getKeyGraphType() { @Override public boolean isAssociation() { - return getPersistentAttributeType() == PersistentAttributeType.ONE_TO_MANY - || getPersistentAttributeType() == PersistentAttributeType.MANY_TO_MANY; + final var persistentAttributeType = getPersistentAttributeType(); + return persistentAttributeType == PersistentAttributeType.ONE_TO_MANY + || persistentAttributeType == PersistentAttributeType.MANY_TO_MANY; } @Override @@ -147,8 +148,8 @@ public Class getBindableJavaType() { @SuppressWarnings("unchecked") @Override public SqmPath createSqmPath(SqmPath lhs, @Nullable SqmPathSource intermediatePathSource) { - // We need an unchecked cast here : PluralPersistentAttribute implements path source with its element type - // but resolving paths from it must produce collection-typed expressions. + // We need an unchecked cast here: PluralPersistentAttribute implements PathSource with + // its element type, but resolving paths from it must produce collection-typed expressions. return (SqmPath) new SqmPluralValuedSimplePath<>( PathHelper.append( lhs, this, intermediatePathSource ), this, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPath.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPath.java index 3fbe0ec4040e..03b74253ba57 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPath.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPath.java @@ -29,7 +29,7 @@ protected AnyDiscriminatorSqmPath( @Override public AnyDiscriminatorSqmPath copy(SqmCopyContext context) { - final AnyDiscriminatorSqmPath existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPathSource.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPathSource.java index a49b01080e81..64ea52e4f7d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPathSource.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyDiscriminatorSqmPathSource.java @@ -12,7 +12,6 @@ import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.tree.domain.SqmDomainType; import org.hibernate.query.sqm.tree.domain.SqmPath; -import org.hibernate.spi.NavigablePath; import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.java.JavaType; @@ -38,10 +37,11 @@ public AnyDiscriminatorSqmPathSource( @Override public SqmPath createSqmPath(SqmPath lhs, @Nullable SqmPathSource intermediatePathSource) { - final NavigablePath navigablePath = + final var path = lhs.getNavigablePath(); + final var navigablePath = intermediatePathSource == null - ? lhs.getNavigablePath() - : lhs.getNavigablePath().append( intermediatePathSource.getPathName() ); + ? path + : path.append( intermediatePathSource.getPathName() ); return new AnyDiscriminatorSqmPath<>( navigablePath, pathModel, lhs, lhs.nodeBuilder() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingDomainTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingDomainTypeImpl.java index 81e7b9977877..1145ed937e8a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingDomainTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingDomainTypeImpl.java @@ -6,7 +6,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.mapping.Any; -import org.hibernate.mapping.Column; import org.hibernate.metamodel.model.domain.AnyMappingDomainType; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.model.domain.SimpleDomainType; @@ -18,8 +17,6 @@ import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.internal.ConvertedBasicTypeImpl; -import java.util.List; - import static jakarta.persistence.metamodel.Type.PersistenceType.ENTITY; import static org.hibernate.metamodel.mapping.internal.AnyDiscriminatorPart.determineDiscriminatorConverter; @@ -40,10 +37,9 @@ public AnyMappingDomainTypeImpl( this.anyType = anyType; this.baseJtd = baseJtd; - final MetaType discriminatorType = (MetaType) anyType.getDiscriminatorType(); - final BasicType discriminatorBaseType = (BasicType) discriminatorType.getBaseType(); - final NavigableRole navigableRole = resolveNavigableRole( bootAnyMapping ); - + final var discriminatorType = (MetaType) anyType.getDiscriminatorType(); + final var discriminatorBaseType = (BasicType) discriminatorType.getBaseType(); + final var navigableRole = resolveNavigableRole( bootAnyMapping ); anyDiscriminatorType = new ConvertedBasicTypeImpl( navigableRole.getFullPath(), discriminatorBaseType.getJdbcType(), @@ -73,13 +69,14 @@ public String getTypeName() { } private NavigableRole resolveNavigableRole(Any bootAnyMapping) { - final StringBuilder buffer = new StringBuilder(); - if ( bootAnyMapping.getTable() != null ) { - buffer.append( bootAnyMapping.getTable().getName() ); + final var buffer = new StringBuilder(); + final var table = bootAnyMapping.getTable(); + if ( table != null ) { + buffer.append( table.getName() ); } buffer.append( "(" ); - final List columns = bootAnyMapping.getColumns(); + final var columns = bootAnyMapping.getColumns(); for ( int i = 0; i < columns.size(); i++ ) { buffer.append( columns.get( i ).getName() ); if ( i+1 < columns.size() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/ArrayTupleType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/ArrayTupleType.java index f25f1eb7e394..c06524882631 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/ArrayTupleType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/ArrayTupleType.java @@ -53,7 +53,7 @@ public String getTypeName() { } private static JavaType[] getTypeDescriptors(SqmExpressible[] components) { - final JavaType[] typeDescriptors = new JavaType[components.length]; + final var typeDescriptors = new JavaType[components.length]; for ( int i = 0; i < components.length; i++ ) { typeDescriptors[i] = components[i].getExpressibleJavaType(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/BasicSqmPathSource.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/BasicSqmPathSource.java index cd699971ba16..6b909090f748 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/BasicSqmPathSource.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/BasicSqmPathSource.java @@ -48,8 +48,8 @@ public String getTypeName() { @Override public @Nullable SqmPathSource findSubPathSource(String name) { - String path = pathModel.getPathName(); - String pathDesc = path == null || path.startsWith( "{" ) ? " " : " '" + pathModel.getPathName() + "' "; + final String path = pathModel.getPathName(); + final String pathDesc = path == null || path.startsWith( "{" ) ? " " : " '" + pathModel.getPathName() + "' "; throw new TerminalPathException( "Terminal path" + pathDesc + "has no attribute '" + name + "'" ); } @@ -85,8 +85,8 @@ public boolean isGeneric() { @Override public String toString() { - return "BasicSqmPathSource(" + - getPathName() + " : " + getJavaType().getSimpleName() + - ")"; + return "BasicSqmPathSource(" + + getPathName() + " : " + getJavaType().getSimpleName() + + ")"; } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/DomainModelHelper.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/DomainModelHelper.java index d01d2ef09ac3..f4cfb2433533 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/DomainModelHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/DomainModelHelper.java @@ -5,7 +5,6 @@ package org.hibernate.metamodel.model.domain.internal; import org.hibernate.metamodel.MappingMetamodel; -import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.ManagedDomainType; @@ -28,9 +27,9 @@ static boolean isCompatible( return true; } else { - final ModelPart modelPart1 = + final var modelPart1 = getEntityAttributeModelPart( attribute1, attribute1.getDeclaringType(), mappingMetamodel ); - final ModelPart modelPart2 = + final var modelPart2 = getEntityAttributeModelPart( attribute2, attribute2.getDeclaringType(), mappingMetamodel ); return modelPart1 != null && modelPart2 != null @@ -43,13 +42,13 @@ static ModelPart getEntityAttributeModelPart( ManagedDomainType domainType, MappingMetamodel mappingMetamodel) { if ( domainType instanceof EntityDomainType ) { - final EntityMappingType entity = mappingMetamodel.getEntityDescriptor( domainType.getTypeName() ); - return entity.findSubPart( attribute.getName() ); + return mappingMetamodel.getEntityDescriptor( domainType.getTypeName() ) + .findSubPart( attribute.getName() ); } else { ModelPart candidate = null; - for ( ManagedDomainType subType : domainType.getSubTypes() ) { - final ModelPart modelPart = getEntityAttributeModelPart( attribute, subType, mappingMetamodel ); + for ( var subType : domainType.getSubTypes() ) { + final var modelPart = getEntityAttributeModelPart( attribute, subType, mappingMetamodel ); if ( modelPart != null ) { if ( candidate != null && !isCompatibleModelPart( candidate, modelPart ) ) { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddableTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddableTypeImpl.java index 267c7cba8051..d308d62873c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddableTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddableTypeImpl.java @@ -5,9 +5,12 @@ package org.hibernate.metamodel.model.domain.internal; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.AssertionFailure; import org.hibernate.metamodel.UnsupportedMappingException; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.model.domain.DomainType; @@ -31,8 +34,10 @@ public class EmbeddableTypeImpl extends AbstractManagedType implements SqmEmbeddableDomainType, Serializable { + @SuppressWarnings("FieldCanBeLocal") private final boolean isDynamic; private final EmbeddedDiscriminatorSqmPathSource discriminatorPathSource; + private final List> subtypes = new ArrayList<>(); public EmbeddableTypeImpl( JavaType javaType, @@ -61,15 +66,27 @@ public PersistenceType getPersistenceType() { public int getTupleLength() { int count = 0; for ( var attribute : getSingularAttributes() ) { - count += ( (SqmDomainType) attribute.getType() ).getTupleLength(); + if ( attribute.getType() instanceof SqmDomainType domainType ) { + count += domainType.getTupleLength(); + } + else { + throw new AssertionFailure( "Should have been a domain type" ); + } } return count; } @Override public Collection> getSubTypes() { - //noinspection unchecked - return (Collection>) super.getSubTypes(); + return subtypes; + } + + @Override + public void addSubType(ManagedDomainType subType) { + super.addSubType( subType ); + if ( subType instanceof SqmEmbeddableDomainType entityDomainType ) { + subtypes.add( entityDomainType ); + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddedDiscriminatorSqmPath.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddedDiscriminatorSqmPath.java index 65a3b05667d5..650f0c406415 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddedDiscriminatorSqmPath.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddedDiscriminatorSqmPath.java @@ -52,7 +52,7 @@ public EmbeddableDomainType getEmbeddableDomainType() { @Override public EmbeddedDiscriminatorSqmPath copy(SqmCopyContext context) { - final EmbeddedDiscriminatorSqmPath existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityDiscriminatorSqmPath.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityDiscriminatorSqmPath.java index 10fe6fa1b404..093b7d59238e 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityDiscriminatorSqmPath.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityDiscriminatorSqmPath.java @@ -64,7 +64,7 @@ public EntityMappingType getEntityDescriptor() { @Override public EntityDiscriminatorSqmPath copy(SqmCopyContext context) { - final EntityDiscriminatorSqmPath existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } @@ -80,6 +80,7 @@ public EntityDiscriminatorSqmPath copy(SqmCopyContext context) { public X accept(SemanticQueryWalker walker) { return entityDescriptor.hasSubclasses() ? walker.visitDiscriminatorPath( this ) - : walker.visitEntityTypeLiteralExpression( new SqmLiteralEntityType( entityDomainType, nodeBuilder() ) ); + : walker.visitEntityTypeLiteralExpression( + new SqmLiteralEntityType( entityDomainType, nodeBuilder() ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityPersisterConcurrentMap.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityPersisterConcurrentMap.java index db13d7b2e468..85526604475c 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityPersisterConcurrentMap.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityPersisterConcurrentMap.java @@ -7,10 +7,11 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; import org.hibernate.persister.entity.EntityPersister; +import static java.util.stream.Collectors.toUnmodifiableMap; + /** * Concurrent Map implementation of mappings entity name -> EntityPersister. * Concurrency is optimised for read operations; write operations will @@ -26,10 +27,7 @@ public final class EntityPersisterConcurrentMap { public EntityPersister get(final String name) { final var entityPersisterHolder = map.get( name ); - if ( entityPersisterHolder != null ) { - return entityPersisterHolder.entityPersister; - } - return null; + return entityPersisterHolder == null ? null : entityPersisterHolder.entityPersister; } public EntityPersister[] values() { @@ -55,10 +53,10 @@ public String[] keys() { } private void recomputeValues() { - //Assumption: the write lock is being held (synchronize on this) + //Assumption: the write lock is held (synchronize on this) final int size = map.size(); - final EntityPersister[] newValues = new EntityPersister[size]; - final String[] newKeys = new String[size]; + final var newValues = new EntityPersister[size]; + final var newKeys = new String[size]; int i = 0; for ( var entry : map.entrySet() ) { newValues[i] = entry.getValue().entityPersister; @@ -75,7 +73,7 @@ private void recomputeValues() { */ @Deprecated(forRemoval = true) public Map convertToMap() { - return map.entrySet().stream().collect( Collectors.toUnmodifiableMap( + return map.entrySet().stream().collect( toUnmodifiableMap( Map.Entry::getKey, e -> e.getValue().entityPersister ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityTypeImpl.java index b9f2da1a067a..6a7a91821778 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityTypeImpl.java @@ -7,7 +7,9 @@ import java.io.ObjectStreamException; import java.io.Serial; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Locale; import jakarta.persistence.metamodel.EntityType; @@ -15,19 +17,16 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.mapping.PersistentClass; import org.hibernate.metamodel.UnsupportedMappingException; -import org.hibernate.metamodel.mapping.DiscriminatorType; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityVersionMapping; import org.hibernate.metamodel.model.domain.IdentifiableDomainType; import org.hibernate.metamodel.model.domain.JpaMetamodel; -import org.hibernate.metamodel.model.domain.PersistentAttribute; +import org.hibernate.metamodel.model.domain.ManagedDomainType; import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor; -import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.PathException; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.tree.domain.SqmDomainType; -import org.hibernate.query.sqm.tree.domain.SqmManagedDomainType; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmPersistentAttribute; import org.hibernate.query.sqm.tree.domain.SqmEntityDomainType; @@ -35,6 +34,7 @@ import static jakarta.persistence.metamodel.Bindable.BindableType.ENTITY_TYPE; import static jakarta.persistence.metamodel.Type.PersistenceType.ENTITY; +import static jakarta.persistence.metamodel.Type.PersistenceType.MAPPED_SUPERCLASS; import static org.hibernate.metamodel.model.domain.internal.DomainModelHelper.isCompatible; /** @@ -50,6 +50,7 @@ public class EntityTypeImpl private final String jpaEntityName; private final JpaMetamodelImplementor metamodel; private final SqmPathSource discriminatorPathSource; + private final List> subtypes = new ArrayList<>(); public EntityTypeImpl( String entityName, @@ -77,10 +78,10 @@ public EntityTypeImpl( } private EntityDiscriminatorSqmPathSource entityDiscriminatorPathSource(JpaMetamodelImplementor metamodel) { - final EntityPersister entityDescriptor = + final var entityDescriptor = metamodel.getMappingMetamodel() .getEntityDescriptor( getHibernateEntityName() ); - final DiscriminatorType discriminatorType = entityDescriptor.getDiscriminatorDomainType(); + final var discriminatorType = entityDescriptor.getDiscriminatorDomainType(); return discriminatorType == null ? null : new EntityDiscriminatorSqmPathSource<>( discriminatorType, this, entityDescriptor ); } @@ -151,7 +152,7 @@ public SqmEntityDomainType getPathType() { @Override public @Nullable SqmPathSource findSubPathSource(String name) { - final PersistentAttribute attribute = super.findAttribute( name ); + final var attribute = super.findAttribute( name ); if ( attribute != null ) { return (SqmPathSource) attribute; } @@ -176,13 +177,19 @@ else if ( EntityDiscriminatorMapping.matchesRoleName( name ) ) { @Override public @Nullable SqmPathSource findSubPathSource(String name, boolean includeSubtypes) { - final PersistentAttribute attribute = super.findAttribute( name ); + final var attribute = super.findAttribute( name ); if ( attribute != null ) { + if ( attribute.getDeclaringType().getPersistenceType() == MAPPED_SUPERCLASS ) { + final var concreteGeneric = findConcreteGenericAttribute( name ); + if ( concreteGeneric != null ) { + return (SqmPathSource) concreteGeneric; + } + } return (SqmPathSource) attribute; } else { if ( includeSubtypes ) { - final PersistentAttribute subtypeAttribute = findSubtypeAttribute( name ); + final var subtypeAttribute = findSubtypeAttribute( name ); if ( subtypeAttribute != null ) { return (SqmPathSource) subtypeAttribute; } @@ -201,8 +208,8 @@ else if ( EntityDiscriminatorMapping.matchesRoleName( name ) ) { private SqmPersistentAttribute findSubtypeAttribute(String name) { SqmPersistentAttribute subtypeAttribute = null; - for ( SqmManagedDomainType subtype : getSubTypes() ) { - final SqmPersistentAttribute candidate = subtype.findSubTypesAttribute( name ); + for ( var subtype : super.getSubTypes() ) { + final var candidate = subtype.findSubTypesAttribute( name ); if ( candidate != null ) { if ( subtypeAttribute != null && !isCompatible( subtypeAttribute, candidate, metamodel.getMappingMetamodel() ) ) { @@ -249,8 +256,15 @@ public PersistenceType getPersistenceType() { @Override public Collection> getSubTypes() { - //noinspection unchecked - return (Collection>) super.getSubTypes(); + return subtypes; + } + + @Override + public void addSubType(ManagedDomainType subType) { + super.addSubType( subType ); + if ( subType instanceof SqmEntityDomainType entityDomainType ) { + subtypes.add( entityDomainType ); + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java index e412aa39bb02..36a76ab9f4cc 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java @@ -14,6 +14,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; @@ -66,11 +67,11 @@ */ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable { - private static class ImportInfo { + private static class ImportInfo { private final String importedName; - private Class loadedClass; // could be null for boot metamodel import; not final to allow for populating later + private Class loadedClass; // could be null for boot metamodel import; not final to allow for populating later - private ImportInfo(String importedName, Class loadedClass) { + private ImportInfo(String importedName, Class loadedClass) { this.importedName = importedName; this.loadedClass = loadedClass; } @@ -93,7 +94,7 @@ private ImportInfo(String importedName, Class loadedClass) { private final Map, String> entityProxyInterfaceMap = new HashMap<>(); - private final Map> nameToImportMap = new ConcurrentHashMap<>(); + private final Map nameToImportMap = new ConcurrentHashMap<>(); private final Map knownInvalidnameToImportMap = new ConcurrentHashMap<>(); @@ -116,14 +117,13 @@ public ServiceRegistry getServiceRegistry() { } @Override - public @Nullable ManagedDomainType findManagedType(@Nullable String typeName) { - //noinspection unchecked - return typeName == null ? null : (ManagedDomainType) managedTypeByName.get( typeName ); + public @Nullable ManagedDomainType findManagedType(@Nullable String typeName) { + return typeName == null ? null : managedTypeByName.get( typeName ); } @Override - public ManagedDomainType managedType(String typeName) { - final ManagedDomainType managedType = findManagedType( typeName ); + public ManagedDomainType managedType(String typeName) { + final var managedType = findManagedType( typeName ); if ( managedType == null ) { throw new IllegalArgumentException( "Not a managed type: " + typeName ); } @@ -137,8 +137,8 @@ public EntityDomainType findEntityType(@Nullable String entityName) { return null; } - final ManagedDomainType managedType = managedTypeByName.get( entityName ); - if ( managedType instanceof EntityDomainType entityDomainType ){ + if ( managedTypeByName.get( entityName ) + instanceof EntityDomainType entityDomainType ){ return entityDomainType; } @@ -148,7 +148,7 @@ public EntityDomainType findEntityType(@Nullable String entityName) { // was no direct match, we need to iterate over all of the and look based on // JPA entity-name. - for ( Map.Entry> entry : managedTypeByName.entrySet() ) { + for ( var entry : managedTypeByName.entrySet() ) { if ( entry.getValue() instanceof EntityDomainType possibility ) { if ( entityName.equals( possibility.getName() ) ) { return possibility; @@ -176,7 +176,7 @@ public EmbeddableDomainType findEmbeddableType(@Nullable String embeddableNam if ( embeddableName == null ) { return null; } - final ManagedDomainType managedType = managedTypeByName.get( embeddableName ); + final var managedType = managedTypeByName.get( embeddableName ); if ( !( managedType instanceof EmbeddableDomainType embeddableDomainType ) ) { return null; } @@ -185,7 +185,7 @@ public EmbeddableDomainType findEmbeddableType(@Nullable String embeddableNam @Override public EmbeddableDomainType embeddable(String embeddableName) { - final EmbeddableDomainType embeddableType = findEmbeddableType( embeddableName ); + final var embeddableType = findEmbeddableType( embeddableName ); if ( embeddableType == null ) { throw new IllegalArgumentException( "Not an embeddable: " + embeddableName ); } @@ -193,23 +193,22 @@ public EmbeddableDomainType embeddable(String embeddableName) { } @Override - public EntityDomainType getHqlEntityReference(String entityName) { - Class loadedClass = null; - final ImportInfo importInfo = resolveImport( entityName ); + public EntityDomainType getHqlEntityReference(String entityName) { + Class loadedClass = null; + final var importInfo = resolveImport( entityName ); if ( importInfo != null ) { loadedClass = importInfo.loadedClass; entityName = importInfo.importedName; } - final EntityDomainType entityDescriptor = findEntityType( entityName ); + final var entityDescriptor = findEntityType( entityName ); if ( entityDescriptor != null ) { - //noinspection unchecked - return (EntityDomainType) entityDescriptor; + return entityDescriptor; } if ( loadedClass == null ) { loadedClass = resolveRequestedClass( entityName ); - // populate class cache for boot metamodel imports + // populate the class cache for boot metamodel imports if ( importInfo != null && loadedClass != null ) { importInfo.loadedClass = loadedClass; } @@ -221,24 +220,35 @@ public EntityDomainType getHqlEntityReference(String entityName) { } @Override - public EntityDomainType resolveHqlEntityReference(String entityName) { - final EntityDomainType hqlEntityReference = getHqlEntityReference( entityName ); + public EntityDomainType resolveHqlEntityReference(String entityName) { + final var hqlEntityReference = getHqlEntityReference( entityName ); if ( hqlEntityReference == null ) { throw new EntityTypeException( "Could not resolve entity name '" + entityName + "'", entityName ); } return hqlEntityReference; } + private static ManagedDomainType checkDomainType(Class cls, ManagedDomainType domainType) { + if ( domainType != null && !Objects.equals( domainType.getJavaType(), cls ) ) { + throw new IllegalStateException( "Managed type " + domainType + + " has a different Java type than requested" ); + } + else { + @SuppressWarnings("unchecked") // Safe, we checked it + final var type = (ManagedDomainType) domainType; + return type; + } + } + @Override @Nullable public ManagedDomainType findManagedType(Class cls) { - //noinspection unchecked - return (ManagedDomainType) managedTypeByClass.get( cls ); + return checkDomainType( cls, managedTypeByClass.get( cls ) ); } @Override public ManagedDomainType managedType(Class cls) { - final ManagedDomainType type = findManagedType( cls ); + final var type = findManagedType( cls ); if ( type == null ) { // per JPA throw new IllegalArgumentException( "Not a managed type: " + cls ); @@ -249,17 +259,15 @@ public ManagedDomainType managedType(Class cls) { @Override @Nullable public EntityDomainType findEntityType(Class cls) { - final ManagedType type = managedTypeByClass.get( cls ); - if ( !( type instanceof EntityDomainType ) ) { - return null; - } - //noinspection unchecked - return (EntityDomainType) type; + return checkDomainType( cls, managedTypeByClass.get( cls ) ) + instanceof EntityDomainType entityDomainType + ? entityDomainType + : null; } @Override public EntityDomainType entity(Class cls) { - final EntityDomainType entityType = findEntityType( cls ); + final var entityType = findEntityType( cls ); if ( entityType == null ) { throw new IllegalArgumentException( "Not an entity: " + cls.getName() ); } @@ -268,17 +276,15 @@ public EntityDomainType entity(Class cls) { @Override public @Nullable EmbeddableDomainType findEmbeddableType(Class cls) { - final ManagedType type = managedTypeByClass.get( cls ); - if ( !( type instanceof EmbeddableDomainType ) ) { - return null; - } - //noinspection unchecked - return (EmbeddableDomainType) type; + return checkDomainType( cls, managedTypeByClass.get( cls ) ) + instanceof EmbeddableDomainType embeddableDomainType + ? embeddableDomainType + : null; } @Override public EmbeddableDomainType embeddable(Class cls) { - final EmbeddableDomainType embeddableType = findEmbeddableType( cls ); + final var embeddableType = findEmbeddableType( cls ); if ( embeddableType == null ) { throw new IllegalArgumentException( "Not an embeddable: " + cls.getName() ); } @@ -322,7 +328,7 @@ public Set> getEmbeddables() { @Override public EnumJavaType getEnumType(String className) { - final EnumJavaType enumJavaType = enumJavaTypes.get( className ); + final var enumJavaType = enumJavaTypes.get( className ); if ( enumJavaType != null ) { return enumJavaType; } @@ -349,7 +355,7 @@ public > E enumValue(EnumJavaType enumType, String enumValu @Override public JavaType getJavaConstantType(String className, String fieldName) { try { - final Field referencedField = getJavaField( className, fieldName ); + final var referencedField = getJavaField( className, fieldName ); if ( referencedField != null ) { return getTypeConfiguration().getJavaTypeRegistry() .resolveDescriptor( referencedField.getType() ); @@ -364,7 +370,7 @@ public JavaType getJavaConstantType(String className, String fieldName) { @Override public T getJavaConstant(String className, String fieldName) { try { - final Field referencedField = getJavaField( className, fieldName ); + final var referencedField = getJavaField( className, fieldName ); //noinspection unchecked return (T) referencedField.get( null ); } @@ -383,7 +389,7 @@ private Field getJavaField(String className, String fieldName) throws NoSuchFiel @Override public void addNamedEntityGraph(String graphName, RootGraphImplementor rootGraph) { - final EntityGraph old = entityGraphMap.put( graphName, rootGraph.makeImmutableCopy( graphName ) ); + final var old = entityGraphMap.put( graphName, rootGraph.makeImmutableCopy( graphName ) ); if ( old != null ) { CORE_LOGGER.tracef( "EntityGraph named '%s' was replaced", graphName ); } @@ -396,7 +402,7 @@ public RootGraphImplementor findEntityGraphByName(String name) { @Override public List> findEntityGraphsByJavaType(Class entityClass) { - final EntityDomainType entityType = entity( entityClass ); + final var entityType = entity( entityClass ); if ( entityType == null ) { throw new IllegalArgumentException( "Given class is not an entity: " + entityClass.getName() ); } @@ -415,7 +421,7 @@ public List> findEntityGraphsByJavaType(Class enti @Override public Map> getNamedEntityGraphs(Class entityClass) { - final EntityDomainType entityType = entity( entityClass ); + final var entityType = entity( entityClass ); if ( entityType == null ) { throw new IllegalArgumentException( "Given class is not an entity: " + entityClass.getName() ); } @@ -434,27 +440,26 @@ public Map> getNamedEntityGraphs(Class e @Override public String qualifyImportableName(String queryName) { - final ImportInfo importInfo = resolveImport( queryName ); + final var importInfo = resolveImport( queryName ); return importInfo == null ? null : importInfo.importedName; } - private ImportInfo resolveImport(final String name) { - final ImportInfo importInfo = nameToImportMap.get( name ); + private ImportInfo resolveImport(final String name) { + final var importInfo = nameToImportMap.get( name ); //optimal path first if ( importInfo != null ) { - //noinspection unchecked - return (ImportInfo) importInfo; + return importInfo; } else { - //then check the negative cache, to avoid bothering the classloader unnecessarily + //then check the negative cache to avoid bothering the classloader unnecessarily if ( knownInvalidnameToImportMap.containsKey( name ) ) { return null; } else { - // see if the name is a fully-qualified class name - final Class loadedClass = resolveRequestedClass( name ); + // see if the name is a fully qualified class name + final var loadedClass = resolveRequestedClass( name ); if ( loadedClass == null ) { - // it is NOT a fully-qualified class name - add a marker entry so we do not keep trying later + // it is NOT a fully qualified class name - add a marker entry, so we do not keep trying later // note that ConcurrentHashMap does not support null value so a marker entry is needed // [HHH-14948] But only add it if the cache size isn't getting too large, as in some use cases // the queries are dynamically generated and this cache could lead to memory leaks when left unbounded. @@ -470,9 +475,9 @@ private ImportInfo resolveImport(final String name) { return null; } else { - // it is a fully-qualified class name - add it to the cache + // it is a fully qualified class name - add it to the cache // so to not needing to load from the classloader again - final ImportInfo info = new ImportInfo<>( name, loadedClass ); + final var info = new ImportInfo( name, loadedClass ); nameToImportMap.put( name, info ); return info; } @@ -481,24 +486,22 @@ private ImportInfo resolveImport(final String name) { } private void applyNamedEntityGraphs(Collection namedEntityGraphs) { - for ( NamedEntityGraphDefinition definition : namedEntityGraphs ) { + for ( var definition : namedEntityGraphs ) { CORE_LOGGER.tracef( "Applying named entity graph [name=%s, source=%s]", definition.name(), definition.source() ); - final RootGraphImplementor graph = definition.graphCreator().createEntityGraph( - (entityClass) -> { - final ManagedDomainType managedDomainType = managedTypeByClass.get( entityClass ); - if ( managedDomainType instanceof EntityDomainType match ) { + final var graph = definition.graphCreator().createEntityGraph( + entityClass -> { + if ( managedTypeByClass.get( entityClass ) instanceof EntityDomainType match ) { return match; } throw new IllegalArgumentException( "Cannot resolve entity class : " + entityClass.getName() ); }, - (jpaEntityName) -> { - for ( Map.Entry> entry : managedTypeByName.entrySet() ) { - if ( entry.getValue() instanceof EntityDomainType possibility ) { - if ( jpaEntityName.equals( possibility.getName() ) ) { - return possibility; - } + jpaEntityName -> { + for ( var entry : managedTypeByName.entrySet() ) { + if ( entry.getValue() instanceof EntityDomainType possibility + && jpaEntityName.equals( possibility.getName() ) ) { + return possibility; } } throw new IllegalArgumentException( "Cannot resolve entity name : " + jpaEntityName ); @@ -509,7 +512,7 @@ private void applyNamedEntityGraphs(Collection named } - private Class resolveRequestedClass(String entityName) { + private Class resolveRequestedClass(String entityName) { try { return classLoaderService.classForName( entityName ); } @@ -522,7 +525,7 @@ private Class resolveRequestedClass(String entityName) { public EntityDomainType resolveEntityReference(Class javaType) { // try the incoming Java type as a "strict" entity reference { - final ManagedDomainType managedType = managedTypeByClass.get( javaType ); + final var managedType = managedTypeByClass.get( javaType ); if ( managedType instanceof EntityDomainType ) { return (EntityDomainType) managedType; } @@ -538,37 +541,34 @@ public EntityDomainType resolveEntityReference(Class javaType) { // otherwise, try to handle it as a polymorphic reference { - final EntityDomainType polymorphicDomainType = - (EntityDomainType) polymorphicEntityReferenceMap.get( javaType ); + final var polymorphicDomainType = + (EntityDomainType) + polymorphicEntityReferenceMap.get( javaType ); if ( polymorphicDomainType != null ) { return polymorphicDomainType; } // create a set of descriptors that should be used to build the polymorphic EntityDomainType final Set> matchingDescriptors = new HashSet<>(); - for ( ManagedDomainType managedType : managedTypeByName.values() ) { - if ( managedType.getPersistenceType() != Type.PersistenceType.ENTITY ) { - continue; - } - // see if we should add `entityDomainType` as one of the matching-descriptors. - if ( javaType.isAssignableFrom( managedType.getJavaType() ) ) { - // the queried type is assignable from the type of the current entity-type - // we should add it to the collecting set of matching descriptors. it should + for ( var managedType : managedTypeByName.values() ) { + if ( managedType.getPersistenceType() == Type.PersistenceType.ENTITY + // see if we should add EntityDomainType as one of the matching descriptors. + && javaType.isAssignableFrom( managedType.getJavaType() ) ) { + // The queried type is assignable from the type of the current entity type. + // We should add it to the collecting set of matching descriptors. It should // be added aside from a few cases... - // if the managed-type has a super type and the java type is assignable from the super type, - // do not add the managed-type as the super itself will get added and the initializers for - // entity mappings already handle loading subtypes - adding it would be redundant and lead to - // incorrect results - final ManagedDomainType superType = managedType.getSuperType(); - if ( superType != null - && superType.getPersistenceType() == Type.PersistenceType.ENTITY - && javaType.isAssignableFrom( superType.getJavaType() ) ) { - continue; + // If the managed type has a supertype and the java type is assignable from the super type, + // do not add the managed type as the supertype itself will get added and the initializers + // for entity mappings already handle loading subtypes - adding it would be redundant and + // lead to incorrect results + final var superType = managedType.getSuperType(); + if ( superType == null + || superType.getPersistenceType() != Type.PersistenceType.ENTITY + || !javaType.isAssignableFrom( superType.getJavaType() ) ) { + matchingDescriptors.add( (EntityDomainType) managedType ); } - // otherwise, add it - matchingDescriptors.add( (EntityDomainType) managedType ); } } @@ -604,10 +604,11 @@ public void processJpa( Collection namedEntityGraphDefinitions, RuntimeModelCreationContext runtimeModelCreationContext) { bootMetamodel.getImports() - .forEach( (key, value) -> this.nameToImportMap.put( key, new ImportInfo<>( value, null ) ) ); + .forEach( (key, value) -> nameToImportMap.put( key, + new ImportInfo( value, null ) ) ); this.entityProxyInterfaceMap.putAll( entityProxyInterfaceMap ); - final MetadataContext context = new MetadataContext( + final var context = new MetadataContext( this, mappingMetamodel, bootMetamodel, @@ -628,31 +629,31 @@ public void processJpa( this.jpaMetaModelPopulationSetting = jpaMetaModelPopulationSetting; // Identifiable types (Entities and MappedSuperclasses) - this.managedTypeByName.putAll( context.getIdentifiableTypesByName() ); - this.managedTypeByClass.putAll( context.getEntityTypeMap() ); - this.managedTypeByClass.putAll( context.getMappedSuperclassTypeMap() ); + managedTypeByName.putAll( context.getIdentifiableTypesByName() ); + managedTypeByClass.putAll( context.getEntityTypeMap() ); + managedTypeByClass.putAll( context.getMappedSuperclassTypeMap() ); // Embeddable types int mapEmbeddables = 0; - for ( EmbeddableDomainType embeddable : context.getEmbeddableTypeSet() ) { + for ( var embeddable : context.getEmbeddableTypeSet() ) { // Do not register the embeddable types for id classes if ( embeddable.getExpressibleJavaType() instanceof EntityJavaType ) { continue; } - final Class embeddableClass = embeddable.getJavaType(); + final var embeddableClass = embeddable.getJavaType(); if ( embeddableClass != Map.class ) { - this.managedTypeByClass.put( embeddable.getJavaType(), embeddable ); - this.managedTypeByName.put( embeddable.getTypeName(), embeddable ); + managedTypeByClass.put( embeddable.getJavaType(), embeddable ); + managedTypeByName.put( embeddable.getTypeName(), embeddable ); } else { - this.managedTypeByName.put( "dynamic-embeddable-" + mapEmbeddables++, embeddable ); + managedTypeByName.put( "dynamic-embeddable-" + mapEmbeddables++, embeddable ); } } typeConfiguration.getJavaTypeRegistry().forEachDescriptor( descriptor -> { if ( descriptor instanceof EnumJavaType> enumJavaType ) { - final Class> enumJavaClass = enumJavaType.getJavaTypeClass(); - for ( Enum enumConstant : enumJavaClass.getEnumConstants() ) { + final var enumJavaClass = enumJavaType.getJavaTypeClass(); + for ( var enumConstant : enumJavaClass.getEnumConstants() ) { addAllowedEnumLiteralsToEnumTypesMap( allowedEnumLiteralsToEnumTypeNames, enumConstant.name(), @@ -731,7 +732,8 @@ private EntityTypeImpl buildEntityType( MetadataContext context, TypeConfiguration typeConfiguration) { context.pushEntityWorkedOn( persistentClass ); - final var entityType = entityType( persistentClass, persistentClass.getMappedClass(), context, typeConfiguration ); + final var entityType = + entityType( persistentClass, persistentClass.getMappedClass(), context, typeConfiguration ); context.registerEntityType( persistentClass, entityType ); context.popEntityWorkedOn( persistentClass ); return entityType; @@ -746,23 +748,26 @@ private EntityTypeImpl entityType( final var supertype = (IdentifiableDomainType) supertypeForPersistentClass( persistentClass, context, typeConfiguration ); - final JavaType javaType; + return new EntityTypeImpl<>( entityJavaType( mappedClass, context ), + supertype, persistentClass, this ); + } + + private static JavaType entityJavaType(Class mappedClass, MetadataContext context) { if ( mappedClass == null || Map.class.isAssignableFrom( mappedClass ) ) { // dynamic map //noinspection unchecked - javaType = (JavaType) new DynamicModelJavaType(); + return (JavaType) new DynamicModelJavaType(); } else { - javaType = context.getTypeConfiguration().getJavaTypeRegistry() + return context.getTypeConfiguration().getJavaTypeRegistry() .resolveEntityTypeDescriptor( mappedClass ); } - return new EntityTypeImpl<>( javaType, supertype, persistentClass, this ); } private void handleUnusedMappedSuperclasses(MetadataContext context, TypeConfiguration typeConfiguration) { - final Set unusedMappedSuperclasses = context.getUnusedMappedSuperclasses(); + final var unusedMappedSuperclasses = context.getUnusedMappedSuperclasses(); if ( !unusedMappedSuperclasses.isEmpty() ) { - for ( MappedSuperclass mappedSuperclass : unusedMappedSuperclasses ) { + for ( var mappedSuperclass : unusedMappedSuperclasses ) { CORE_LOGGER.unusedMappedSuperclass( mappedSuperclass.getMappedClass().getName() ); locateOrBuildMappedSuperclassType( mappedSuperclass, context, typeConfiguration ); } @@ -773,7 +778,7 @@ private MappedSuperclassDomainType locateOrBuildMappedSuperclassType( MappedSuperclass mappedSuperclass, MetadataContext context, TypeConfiguration typeConfiguration) { - final MappedSuperclassDomainType mappedSuperclassType = + final var mappedSuperclassType = context.locateMappedSuperclassType( mappedSuperclass ); return mappedSuperclassType == null ? buildMappedSuperclassType( mappedSuperclass, mappedSuperclass.getMappedClass(), context, typeConfiguration ) @@ -786,13 +791,13 @@ private MappedSuperclassTypeImpl buildMappedSuperclassType( MetadataContext context, TypeConfiguration typeConfiguration) { @SuppressWarnings("unchecked") - final IdentifiableDomainType superType = + final var superType = (IdentifiableDomainType) supertypeForMappedSuperclass( mappedSuperclass, context, typeConfiguration ); - final JavaType javaType = + final var javaType = context.getTypeConfiguration().getJavaTypeRegistry() .resolveManagedTypeDescriptor( mappedClass ); - final MappedSuperclassTypeImpl mappedSuperclassType = + final var mappedSuperclassType = new MappedSuperclassTypeImpl<>( javaType, mappedSuperclass, superType, this ); context.registerMappedSuperclassType( mappedSuperclass, mappedSuperclassType ); return mappedSuperclassType; @@ -802,15 +807,14 @@ private IdentifiableDomainType supertypeForPersistentClass( PersistentClass persistentClass, MetadataContext context, TypeConfiguration typeConfiguration) { - final MappedSuperclass superMappedSuperclass = persistentClass.getSuperMappedSuperclass(); - final IdentifiableDomainType supertype = + final var superMappedSuperclass = persistentClass.getSuperMappedSuperclass(); + final var supertype = superMappedSuperclass == null ? null : locateOrBuildMappedSuperclassType( superMappedSuperclass, context, typeConfiguration ); - - //no mappedSuperclass, check for a super entity if ( supertype == null ) { - final PersistentClass superPersistentClass = persistentClass.getSuperclass(); + // no mapped superclass, check for a super entity + final var superPersistentClass = persistentClass.getSuperclass(); return superPersistentClass == null ? null : locateOrBuildEntityType( superPersistentClass, context, typeConfiguration ); @@ -824,14 +828,14 @@ private IdentifiableDomainType supertypeForMappedSuperclass( MappedSuperclass mappedSuperclass, MetadataContext context, TypeConfiguration typeConfiguration) { - final MappedSuperclass superMappedSuperclass = mappedSuperclass.getSuperMappedSuperclass(); - final IdentifiableDomainType superType = + final var superMappedSuperclass = mappedSuperclass.getSuperMappedSuperclass(); + final var superType = superMappedSuperclass == null ? null : locateOrBuildMappedSuperclassType( superMappedSuperclass, context, typeConfiguration ); - //no mappedSuperclass, check for a super entity if ( superType == null ) { - final PersistentClass superPersistentClass = mappedSuperclass.getSuperPersistentClass(); + //no mapped superclass, check for a super entity + final var superPersistentClass = mappedSuperclass.getSuperPersistentClass(); return superPersistentClass == null ? null : locateOrBuildEntityType( superPersistentClass, context, typeConfiguration ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappedSuperclassTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappedSuperclassTypeImpl.java index eb6030e662f8..4f5cb67470a9 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappedSuperclassTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappedSuperclassTypeImpl.java @@ -9,7 +9,6 @@ import org.hibernate.metamodel.UnsupportedMappingException; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.model.domain.IdentifiableDomainType; -import org.hibernate.metamodel.model.domain.PersistentAttribute; import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.tree.domain.SqmDomainType; @@ -89,7 +88,7 @@ public SqmMappedSuperclassDomainType getPathType() { @Override public @Nullable SqmPathSource findSubPathSource(String name) { - final PersistentAttribute attribute = findAttribute( name ); + final var attribute = findAttribute( name ); if ( attribute != null ) { return (SqmPathSource) attribute; } @@ -103,7 +102,7 @@ else if ( "id".equalsIgnoreCase( name ) ) { @Override public @Nullable SqmPathSource getIdentifierDescriptor() { - return (SqmPathSource) super.getIdentifierDescriptor(); + return super.getIdentifierDescriptor(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java index 33209e400641..e6853604cd6c 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java @@ -35,6 +35,7 @@ import org.hibernate.metamodel.model.domain.JpaMetamodel; import org.hibernate.metamodel.model.domain.ManagedDomainType; import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.query.sqm.tree.domain.SqmManagedDomainType; import org.hibernate.type.BindingContext; import org.hibernate.query.sqm.tuple.TupleType; import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor; @@ -113,12 +114,12 @@ public class MappingMetamodelImpl // - ignoring Hibernate's representation mode (entity mode), the Class // object for an entity (or mapped superclass) always refers to the same // JPA EntityType and Hibernate EntityPersister. The problem arises with - // embeddables. For an embeddable, as with the rest of its metamodel, + // embeddables. For an embeddable type, as with the rest of its metamodel, // Hibernate combines the embeddable's relational/mapping while JPA does // not. This is perfectly consistent with each paradigm. But it results // in a mismatch since JPA expects a single "type descriptor" for a - // given embeddable class while Hibernate incorporates the - // relational/mapping info so we have a "type descriptor" for each usage + // given embeddable class, while Hibernate incorporates the + // relational/mapping info, so we have a "type descriptor" for each usage // of that embeddable type. (Think embeddable versus embedded.) // // To account for this, we track both paradigms here. @@ -258,11 +259,9 @@ private void processBootCollections( for ( final var model : collectionBindings ) { final String role = model.getRole(); final var persister = - persisterFactory.createCollectionPersister( - model, + persisterFactory.createCollectionPersister( model, cacheImplementor.getCollectionRegionAccess( new NavigableRole( role ) ), - modelCreationContext - ); + modelCreationContext ); collectionPersisterMap.put( role, persister ); if ( persister.getIndexType() instanceof org.hibernate.type.EntityType entityType ) { registerEntityParticipant( entityType, persister ); @@ -360,32 +359,29 @@ public boolean isEntityClass(Class entityJavaType) { @Override public EntityPersister getEntityDescriptor(Class entityJavaType) { - var entityPersister = entityPersisterMap.get( entityJavaType.getName() ); - if ( entityPersister == null ) { - final String mappedEntityName = entityProxyInterfaceMap.get( entityJavaType ); - if ( mappedEntityName != null ) { - entityPersister = entityPersisterMap.get( mappedEntityName ); - } - } - if ( entityPersister == null ) { - throw new UnknownEntityTypeException( entityJavaType ); - } - return entityPersister; + return getEntityPersister( entityJavaType ); } @Override @Deprecated(forRemoval = true) @SuppressWarnings( "removal" ) public EntityPersister locateEntityDescriptor(Class byClass) { - var entityPersister = entityPersisterMap.get( byClass.getName() ); - if ( entityPersister == null ) { + return getEntityPersister( byClass ); + } + + private EntityPersister getEntityPersister(Class byClass) { + final var entityPersister = entityPersisterMap.get( byClass.getName() ); + if ( entityPersister != null ) { + return entityPersister; + } + else { final String mappedEntityName = entityProxyInterfaceMap.get( byClass ); if ( mappedEntityName != null ) { - entityPersister = entityPersisterMap.get( mappedEntityName ); + final var persister = entityPersisterMap.get( mappedEntityName ); + if ( persister != null ) { + return persister; + } } - } - if ( entityPersister == null ) { throw new UnknownEntityTypeException( byClass ); } - return entityPersister; } @Override @@ -419,12 +415,12 @@ public Set> getEmbeddables() { } @Override - public @Nullable ManagedDomainType findManagedType(@Nullable String typeName) { + public @Nullable ManagedDomainType findManagedType(@Nullable String typeName) { return jpaMetamodel.findManagedType( typeName ); } @Override - public ManagedDomainType managedType(String typeName) { + public ManagedDomainType managedType(String typeName) { return jpaMetamodel.managedType( typeName ); } @@ -449,12 +445,12 @@ public EmbeddableDomainType embeddable(String embeddableName) { } @Override - public EntityDomainType getHqlEntityReference(String entityName) { + public EntityDomainType getHqlEntityReference(String entityName) { return jpaMetamodel.getHqlEntityReference( entityName ); } @Override - public EntityDomainType resolveHqlEntityReference(String entityName) { + public EntityDomainType resolveHqlEntityReference(String entityName) { return jpaMetamodel.resolveHqlEntityReference( entityName ); } @@ -644,19 +640,19 @@ public String[] getAllCollectionRoles() { public @Nullable BindableType resolveParameterBindType(Class javaType) { final var typeConfiguration = getTypeConfiguration(); - final BasicType basicType = typeConfiguration.getBasicTypeForJavaType( javaType ); + final var basicType = typeConfiguration.getBasicTypeForJavaType( javaType ); // For enums, we simply don't know the exact mapping if there is no basic type registered if ( basicType != null || javaType.isEnum() ) { return basicType; } - final ManagedDomainType managedType = jpaMetamodel.findManagedType( javaType ); + final var managedType = jpaMetamodel.findManagedType( javaType ); if ( managedType != null ) { - return (BindableType) managedType; + return (SqmManagedDomainType) managedType; } final var javaTypeRegistry = typeConfiguration.getJavaTypeRegistry(); - final JavaType javaTypeDescriptor = javaTypeRegistry.findDescriptor( javaType ); + final var javaTypeDescriptor = javaTypeRegistry.findDescriptor( javaType ); if ( javaTypeDescriptor != null ) { final JdbcType recommendedJdbcType = javaTypeDescriptor.getRecommendedJdbcType( typeConfiguration.getCurrentBaseSqlTypeIndicators() ); @@ -685,12 +681,12 @@ public String[] getAllCollectionRoles() { return null; } - final Class clazz = unproxiedClass( bindValue ); - - // Resolve superclass bindable type if necessary, as we don't register types for e.g. Inet4Address + final var clazz = unproxiedClass( bindValue ); + // Resolve the superclass bindable type if necessary, + // as we don't register types for e.g. Inet4Address Class c = clazz; do { - final BindableType type = resolveParameterBindType( c ); + final var type = resolveParameterBindType( c ); if ( type != null ) { return type; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/PathHelper.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/PathHelper.java index b9889b827173..3a30d128b970 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/PathHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/PathHelper.java @@ -11,8 +11,9 @@ public class PathHelper { public static NavigablePath append(SqmPath lhs, SqmPathSource rhs, @Nullable SqmPathSource intermediatePathSource) { + final var navigablePath = lhs.getNavigablePath(); return intermediatePathSource == null - ? lhs.getNavigablePath().append( rhs.getPathName() ) - : lhs.getNavigablePath().append( intermediatePathSource.getPathName() ).append( rhs.getPathName() ); + ? navigablePath.append( rhs.getPathName() ) + : navigablePath.append( intermediatePathSource.getPathName() ).append( rhs.getPathName() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/PluralAttributeBuilder.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/PluralAttributeBuilder.java index a47add5cdde1..028144d45019 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/PluralAttributeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/PluralAttributeBuilder.java @@ -86,7 +86,7 @@ public static PersistentAttribute build( attributeMetadata.getMember() ); - final Class javaClass = attributeJtd.getJavaTypeClass(); + final var javaClass = attributeJtd.getJavaTypeClass(); if ( Map.class.equals( javaClass ) ) { return new MapAttributeImpl( builder ); } @@ -124,7 +124,7 @@ else if ( Collection.class.isAssignableFrom( javaClass ) ) { private static SimpleDomainType determineListIndexOrMapKeyType( PluralAttributeMetadata attributeMetadata, MetadataContext metadataContext) { - final Class javaType = attributeMetadata.getJavaType(); + final var javaType = attributeMetadata.getJavaType(); if ( Map.class.isAssignableFrom( javaType ) ) { return (SimpleDomainType) determineSimpleType( attributeMetadata.getMapKeyValueContext(), metadataContext ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/SingularAttributeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/SingularAttributeImpl.java index 28f1f9c3565d..de34e2d38051 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/SingularAttributeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/SingularAttributeImpl.java @@ -17,11 +17,9 @@ import org.hibernate.metamodel.model.domain.PluralPersistentAttribute; import org.hibernate.metamodel.model.domain.SimpleDomainType; import org.hibernate.query.SemanticException; -import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmBindableType; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.hql.spi.SqmCreationState; -import org.hibernate.query.sqm.internal.SqmMappingModelHelper; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmSingularJoin; @@ -38,6 +36,7 @@ import org.hibernate.type.descriptor.java.JavaType; import static jakarta.persistence.metamodel.Bindable.BindableType.SINGULAR_ATTRIBUTE; +import static org.hibernate.query.sqm.internal.SqmMappingModelHelper.resolveSqmPathSource; import static org.hibernate.query.sqm.spi.SqmCreationHelper.buildSubNavigablePath; import static org.hibernate.query.sqm.spi.SqmCreationHelper.determineAlias; @@ -77,7 +76,7 @@ public SingularAttributeImpl( this.isVersion = isVersion; this.isOptional = isOptional; - this.sqmPathSource = SqmMappingModelHelper.resolveSqmPathSource( + this.sqmPathSource = resolveSqmPathSource( name, this, attributeType, @@ -157,15 +156,14 @@ public SqmJoin createSqmJoin( @Nullable String alias, boolean fetched, SqmCreationState creationState) { - final NodeBuilder nodeBuilder = creationState.getCreationContext().getNodeBuilder(); + final var nodeBuilder = creationState.getCreationContext().getNodeBuilder(); if ( getType() instanceof AnyMappingDomainType ) { throw new SemanticException( "An @Any attribute cannot be join fetched" ); } else if ( sqmPathSource.getPathType() instanceof BasicPluralType ) { final SqmSetReturningFunction setReturningFunction = nodeBuilder.unnestArray( lhs.get( getName() ) ); - //noinspection unchecked - final SqmFunctionJoin join = new SqmFunctionJoin<>( + final var join = new SqmFunctionJoin<>( createNavigablePath( lhs, alias ), setReturningFunction, true, @@ -232,11 +230,11 @@ public NavigablePath createNavigablePath(SqmPath parent, @Nullable String ali "LHS cannot be null for a sub-navigable reference - " + getName() ); } - final SqmPathSource parentPathSource = parent.getResolvedModel(); - final NavigablePath parentNavigablePath = - parentPathSource instanceof PluralPersistentAttribute - ? parent.getNavigablePath().append( CollectionPart.Nature.ELEMENT.getName() ) - : parent.getNavigablePath(); + final var navigablePath = parent.getNavigablePath(); + final var parentNavigablePath = + parent.getResolvedModel() instanceof PluralPersistentAttribute + ? navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ) + : navigablePath; if ( getDeclaringType() instanceof IdentifiableDomainType declaringType && !declaringType.hasSingleIdAttribute() ) { return new EntityIdentifierNavigablePath( parentNavigablePath, null ) @@ -291,7 +289,7 @@ public boolean isOptional() { @Override public boolean isAssociation() { - final PersistentAttributeType persistentAttributeType = getPersistentAttributeType(); + final var persistentAttributeType = getPersistentAttributeType(); return persistentAttributeType == PersistentAttributeType.MANY_TO_ONE || persistentAttributeType == PersistentAttributeType.ONE_TO_ONE; } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 76d9652c8e94..ba839acb8145 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -427,6 +427,7 @@ public abstract class AbstractEntityPersister private AttributeMappingsList attributeMappings; protected AttributeMappingsMap declaredAttributeMappings = AttributeMappingsMap.builder().build(); + protected AttributeMappingsMap declaredGenericAttributeMappings = AttributeMappingsMap.builder().build(); protected AttributeMappingsList staticFetchableList; // We build a cache for getters and setters to avoid megamorphic calls private Getter[] getterCache; @@ -3461,14 +3462,7 @@ protected UpdateCoordinator buildUpdateCoordinator() { } protected UpdateCoordinator buildMergeCoordinator() { - // we only have updates to issue for entities with one or more singular attributes - for ( int i = 0; i < attributeMappings.size(); i++ ) { - if ( attributeMappings.get( i ) instanceof SingularAttributeMapping ) { - return new MergeCoordinator( this, factory ); - } - } - // otherwise, nothing to update - return new UpdateCoordinatorNoOp( this ); + return new MergeCoordinator( this, factory ); } protected DeleteCoordinator buildDeleteCoordinator() { @@ -4701,28 +4695,68 @@ private void inheritSupertypeSpecialAttributeMappings() { private void buildDeclaredAttributeMappings (MappingModelCreationProcess creationProcess, PersistentClass bootEntityDescriptor) { + final var allPropertyClosure = bootEntityDescriptor.getAllPropertyClosure(); final var properties = getProperties(); final var mappingsBuilder = AttributeMappingsMap.builder(); + final var genericMappingsBuilder = AttributeMappingsMap.builder(); int stateArrayPosition = getStateArrayInitialPosition( creationProcess ); int fetchableIndex = getFetchableIndexOffset(); - for ( int i = 0; i < getPropertySpan(); i++ ) { - final var runtimeAttributeDefinition = properties[i]; - final String attributeName = runtimeAttributeDefinition.getName(); - final var bootProperty = bootEntityDescriptor.getProperty( attributeName ); - if ( superMappingType == null + int i = 0; + for ( var property : allPropertyClosure ) { + if ( !property.isGeneric() ) { + final var runtimeAttributeDefinition = properties[i]; + final String attributeName = runtimeAttributeDefinition.getName(); + final var bootProperty = bootEntityDescriptor.getProperty( attributeName ); + if ( superMappingType == null || superMappingType.findAttributeMapping( bootProperty.getName() ) == null ) { - mappingsBuilder.put( - attributeName, + mappingsBuilder.put( + attributeName, + generateNonIdAttributeMapping( + runtimeAttributeDefinition, + bootProperty, + stateArrayPosition++, + fetchableIndex++, + creationProcess + ) + ); + } + declaredAttributeMappings = mappingsBuilder.build(); + i++; + } + else { + final int span = property.getColumnSpan(); + final String[] colNames = new String[span]; + final var selectables = property.getSelectables(); + final Dialect dialect = getDialect(); + final TypeConfiguration typeConfiguration = creationProcess.getCreationContext().getTypeConfiguration(); + for ( int k = 0; k < selectables.size(); k++ ) { + final var selectable = selectables.get(k); + if ( selectable instanceof Formula formula ) { + formula.setFormula( substituteBrackets( formula.getFormula() ) ); + colNames[k] = selectable.getTemplate( dialect, typeConfiguration ); + } + else if ( selectable instanceof Column column ) { + colNames[k] = column.getQuotedName( dialect ); + } + } + final String tableName = determineTableName( property.getValue().getTable() ); + genericMappingsBuilder.put( + property.getName(), generateNonIdAttributeMapping( - runtimeAttributeDefinition, - bootProperty, - stateArrayPosition++, - fetchableIndex++, + property.getName(), + property.getType(), + property.getCascadeStyle(), + -1, + tableName, + colNames, + property, + -1, + -1, creationProcess ) ); + declaredGenericAttributeMappings = genericMappingsBuilder.build(); } - declaredAttributeMappings = mappingsBuilder.build(); // otherwise, it's defined on the supertype, skip it here } } @@ -4851,8 +4885,9 @@ else if ( bootEntityDescriptor.hasNaturalId() ) { } } - protected NaturalIdMapping generateNaturalIdMapping - (MappingModelCreationProcess creationProcess, PersistentClass bootEntityDescriptor) { + protected NaturalIdMapping generateNaturalIdMapping( + MappingModelCreationProcess creationProcess, + PersistentClass bootEntityDescriptor) { //noinspection AssertWithSideEffects assert bootEntityDescriptor.hasNaturalId(); @@ -4860,9 +4895,16 @@ else if ( bootEntityDescriptor.hasNaturalId() ) { assert naturalIdAttributeIndexes.length > 0; if ( naturalIdAttributeIndexes.length == 1 ) { + if ( bootEntityDescriptor.getRootClass().getNaturalIdClass() != null ) { + throw new UnsupportedMappingException( "NaturalIdClass not supported for simple naturaal-id mappings" ); + } final String propertyName = getPropertyNames()[ naturalIdAttributeIndexes[ 0 ] ]; final var attributeMapping = (SingularAttributeMapping) findAttributeMapping( propertyName ); - return new SimpleNaturalIdMapping( attributeMapping, this, creationProcess ); + return new SimpleNaturalIdMapping( + attributeMapping, + this, + creationProcess + ); } // collect the names of the attributes making up the natural-id. @@ -4886,7 +4928,12 @@ else if ( bootEntityDescriptor.hasNaturalId() ) { throw new MappingException( "Expected multiple natural-id attributes, but found only one: " + getEntityName() ); } - return new CompoundNaturalIdMapping(this, collectedAttrMappings, creationProcess ); + return new CompoundNaturalIdMapping( + this, + bootEntityDescriptor.getRootClass().getNaturalIdClass(), + collectedAttrMappings, + creationProcess + ); } protected static SqmMultiTableMutationStrategy interpretSqmMultiTableStrategy( @@ -5234,15 +5281,37 @@ protected AttributeMapping generateNonIdAttributeMapping( int stateArrayPosition, int fetchableIndex, MappingModelCreationProcess creationProcess) { - final var creationContext = creationProcess.getCreationContext(); - - final String attrName = tupleAttrDefinition.getName(); - final Type attrType = tupleAttrDefinition.getType(); - + final Type type = tupleAttrDefinition.getType(); final int propertyIndex = getPropertyIndex( bootProperty.getName() ); + final String[] attrColumnExpression = type instanceof BasicType && bootProperty.getSelectables().get( 0 ).isFormula() + ? propertyColumnFormulaTemplates[ propertyIndex ] + : getPropertyColumnNames( propertyIndex ) ; + return generateNonIdAttributeMapping( + tupleAttrDefinition.getName(), + type, + tupleAttrDefinition.getCascadeStyle(), + propertyIndex, + getTableName( getPropertyTableNumbers()[propertyIndex] ), + attrColumnExpression, + bootProperty, + stateArrayPosition, + fetchableIndex, + creationProcess + ); + } - final String tableExpression = getTableName( getPropertyTableNumbers()[propertyIndex] ); - final String[] attrColumnNames = getPropertyColumnNames( propertyIndex ); + protected AttributeMapping generateNonIdAttributeMapping( + String attrName, + Type attrType, + CascadeStyle cascadeStyle, + int propertyIndex, + String tableExpression, + String[] attrColumnNames, + Property bootProperty, + int stateArrayPosition, + int fetchableIndex, + MappingModelCreationProcess creationProcess) { + final var creationContext = creationProcess.getCreationContext(); final var propertyAccess = getRepresentationStrategy().resolvePropertyAccess( bootProperty ); @@ -5274,7 +5343,7 @@ protected AttributeMapping generateNonIdAttributeMapping( value.isColumnInsertable( 0 ), value.isColumnUpdateable( 0 ), propertyAccess, - tupleAttrDefinition.getCascadeStyle(), + cascadeStyle, creationProcess ); } @@ -5312,7 +5381,7 @@ protected AttributeMapping generateNonIdAttributeMapping( else { final var basicBootValue = (BasicValue) value; - if ( attrColumnNames[ 0 ] != null ) { + if ( !value.getSelectables().get( 0 ).isFormula() ) { attrColumnExpression = attrColumnNames[ 0 ]; isAttrColumnExpressionFormula = false; @@ -5345,8 +5414,7 @@ protected AttributeMapping generateNonIdAttributeMapping( resolveAggregateColumnBasicType( creationProcess, role, column ); } else { - final String[] attrColumnFormulaTemplate = propertyColumnFormulaTemplates[ propertyIndex ]; - attrColumnExpression = attrColumnFormulaTemplate[ 0 ]; + attrColumnExpression = attrColumnNames[ 0 ]; isAttrColumnExpressionFormula = true; customReadExpr = null; customWriteExpr = null; @@ -5386,7 +5454,7 @@ protected AttributeMapping generateNonIdAttributeMapping( value.isColumnInsertable( 0 ), value.isColumnUpdateable( 0 ), propertyAccess, - tupleAttrDefinition.getCascadeStyle(), + cascadeStyle, creationProcess ); } @@ -5431,7 +5499,7 @@ else if ( attrType instanceof CompositeType ) { tableExpression, null, propertyAccess, - tupleAttrDefinition.getCascadeStyle(), + cascadeStyle, creationProcess ); } @@ -5443,7 +5511,7 @@ else if ( attrType instanceof CollectionType ) { bootProperty, this, propertyAccess, - tupleAttrDefinition.getCascadeStyle(), + cascadeStyle, getFetchMode( stateArrayPosition ), creationProcess ); @@ -5459,7 +5527,7 @@ else if ( attrType instanceof EntityType entityType ) { this, entityType, propertyAccess, - tupleAttrDefinition.getCascadeStyle(), + cascadeStyle, creationProcess ); } @@ -5722,6 +5790,11 @@ else if ( treatTargetType != this ) { } private ModelPart findSubPartInSubclassMappings(String name) { + final var declaredGenericAttribute = declaredGenericAttributeMappings.get( name ); + if ( declaredGenericAttribute != null ) { + return declaredGenericAttribute; + } + ModelPart attribute = null; if ( isNotEmpty( subclassMappingTypes ) ) { for ( var subMappingType : subclassMappingTypes.values() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/DirtyHelper.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/DirtyHelper.java index d8eb504ad3f6..9819b2cdb036 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/DirtyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/DirtyHelper.java @@ -96,9 +96,7 @@ public static int[] findDirty( int[] results = null; int count = 0; int span = propertyTypes.length; - for ( int i = 0; i < span; i++ ) { - if ( isDirty( propertyTypes, currentState, previousState, includeColumns, session, i ) ) { if ( results == null ) { results = new int[span]; @@ -106,7 +104,6 @@ public static int[] findDirty( results[count++] = i; } } - return count == 0 ? null : ArrayHelper.trim( results, count ); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/MergeCoordinator.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/MergeCoordinator.java index 1b6f78497dc9..b8733df5a32d 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/MergeCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/MergeCoordinator.java @@ -5,6 +5,7 @@ package org.hibernate.persister.entity.mutation; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.sql.model.MutationOperation; import org.hibernate.sql.model.ast.builder.AbstractTableUpdateBuilder; @@ -26,4 +27,49 @@ protected AbstractTableUpdateBuilder newTableUp return new TableMergeBuilder<>( entityPersister(), tableMapping, factory() ); } + @Override + protected UpdateValuesAnalysisImpl analyzeUpdateValues( + Object entity, + Object[] values, + Object oldVersion, + Object[] oldValues, + int[] dirtyAttributeIndexes, + InclusionChecker inclusionChecker, + InclusionChecker lockingChecker, + InclusionChecker dirtinessChecker, + Object rowId, + boolean forceDynamicUpdate, + SharedSessionContractImplementor session) { + final UpdateValuesAnalysisImpl updateValuesAnalysis = super.analyzeUpdateValues( + entity, + values, + oldVersion, + oldValues, + dirtyAttributeIndexes, + inclusionChecker, + lockingChecker, + dirtinessChecker, + rowId, + forceDynamicUpdate, + session + ); + if ( oldValues == null ) { + final TableSet tablesNeedingUpdate = updateValuesAnalysis.getTablesNeedingUpdate(); + final TableSet tablesWithNonNullValues = updateValuesAnalysis.getTablesWithNonNullValues(); + final TableSet tablesWithPreviousNonNullValues = updateValuesAnalysis.getTablesWithPreviousNonNullValues(); + for ( EntityTableMapping tableMapping : entityPersister().getTableMappings() ) { + // Need to upsert into all non-optional table mappings + if ( !tableMapping.isOptional() ) { + // If the table was previously not needing an update, remove it from tablesWithPreviousNonNullValues + // to avoid triggering a delete-statement for this operation + if ( !tablesNeedingUpdate.contains( tableMapping ) ) { + tablesWithPreviousNonNullValues.remove( tableMapping ); + } + tablesNeedingUpdate.add( tableMapping ); + tablesWithNonNullValues.add( tableMapping ); + } + } + } + return updateValuesAnalysis; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/TableSet.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/TableSet.java index 4d2de0f42b09..e8fb90cf818c 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/TableSet.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/TableSet.java @@ -34,6 +34,13 @@ public void add(final TableMapping tableMapping) { bits.set( tableMapping.getRelativePosition() ); } + public void remove(final TableMapping tableMapping) { + if ( bits != null ) { + assert addForChecks( tableMapping ); + bits.set( tableMapping.getRelativePosition(), false ); + } + } + public boolean isEmpty() { return bits == null; } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java index f2c9fc3f2117..c6e15517c607 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java @@ -600,7 +600,7 @@ protected boolean[] getPropertiesToUpdate(final int[] dirtyProperties, final boo } } - private UpdateValuesAnalysisImpl analyzeUpdateValues( + protected UpdateValuesAnalysisImpl analyzeUpdateValues( Object entity, Object[] values, Object oldVersion, @@ -1018,7 +1018,10 @@ protected BatchKey getVersionUpdateBatchkey(){ } private boolean resultCheck( - Object id, PreparedStatementDetails statementDetails, int affectedRowCount, int batchPosition) { + Object id, + PreparedStatementDetails statementDetails, + int affectedRowCount, + int batchPosition) { return identifiedResultsCheck( statementDetails, affectedRowCount, diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/EntityDomainResultBuilder.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/EntityDomainResultBuilder.java index 11a3f45b0955..3474537561be 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/EntityDomainResultBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/EntityDomainResultBuilder.java @@ -19,13 +19,13 @@ /** * @author Christian Beikov */ -public class EntityDomainResultBuilder implements ResultBuilder { +class EntityDomainResultBuilder implements ResultBuilder { private final NavigablePath navigablePath; private final EntityMappingType entityDescriptor; private final FetchBuilderBasicValued discriminatorFetchBuilder; - public EntityDomainResultBuilder(EntityMappingType entityDescriptor) { + EntityDomainResultBuilder(EntityMappingType entityDescriptor) { this.entityDescriptor = entityDescriptor; this.navigablePath = new NavigablePath( entityDescriptor.getEntityName() ); final EntityDiscriminatorMapping discriminatorMapping = entityDescriptor.getDiscriminatorMapping(); diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java index fce45e336280..2de0be02215b 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java @@ -17,23 +17,20 @@ import org.hibernate.sql.exec.internal.JdbcCallParameterExtractorImpl; import org.hibernate.sql.exec.internal.JdbcCallRefCursorExtractorImpl; import org.hibernate.sql.exec.spi.JdbcCallFunctionReturn; -import org.hibernate.type.BasicType; -import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.spi.TypeConfiguration; import jakarta.persistence.ParameterMode; /** * @author Steve Ebersole */ -public class FunctionReturnImpl implements FunctionReturnImplementor { +class FunctionReturnImpl implements FunctionReturnImplementor { private final ProcedureCallImplementor procedureCall; private final int sqlTypeCode; private OutputableType ormType; - public FunctionReturnImpl(ProcedureCallImplementor procedureCall, int sqlTypeCode) { + FunctionReturnImpl(ProcedureCallImplementor procedureCall, int sqlTypeCode) { this.procedureCall = procedureCall; this.sqlTypeCode = sqlTypeCode; } @@ -67,11 +64,11 @@ private OutputableType getOrmType(SharedSessionContractImplementor persistenc return ormType; } else { - final TypeConfiguration typeConfiguration = persistenceContext.getFactory().getTypeConfiguration(); - final JavaType javaType = + final var typeConfiguration = persistenceContext.getFactory().getTypeConfiguration(); + final var javaType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( getJdbcTypeCode() ) - .getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration ); - final BasicType basicType = + .getRecommendedJavaType( null, null, typeConfiguration ); + final var basicType = typeConfiguration.standardBasicTypeForJavaType( javaType.getJavaTypeClass() ); //noinspection unchecked return (OutputableType) basicType; diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java index 798d3f729b57..3a7d21178c7d 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java @@ -31,11 +31,13 @@ import org.hibernate.procedure.ProcedureOutputs; import org.hibernate.procedure.spi.FunctionReturnImplementor; import org.hibernate.procedure.spi.NamedCallableQueryMemento; +import org.hibernate.procedure.spi.NamedCallableQueryMemento.ParameterMemento; import org.hibernate.procedure.spi.ParameterStrategy; import org.hibernate.procedure.spi.ProcedureCallImplementor; import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.query.KeyedPage; import org.hibernate.query.KeyedResultList; +import org.hibernate.query.spi.ProcedureParameterMetadataImplementor; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmBindableType; import org.hibernate.sql.exec.spi.JdbcOperationQueryCall; @@ -50,8 +52,6 @@ import org.hibernate.query.spi.QueryImplementor; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.ScrollableResultsImplementor; -import org.hibernate.query.sqm.SqmExpressible; -import org.hibernate.result.Output; import org.hibernate.result.ResultSetOutput; import org.hibernate.result.UpdateCountOutput; import org.hibernate.result.internal.OutputsExecutionContext; @@ -78,6 +78,7 @@ import jakarta.persistence.metamodel.Type; import static java.lang.Boolean.parseBoolean; +import static java.lang.Integer.parseInt; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.emptySet; @@ -89,6 +90,7 @@ import static org.hibernate.jpa.HibernateHints.HINT_CALLABLE_FUNCTION_RETURN_TYPE; import static org.hibernate.procedure.internal.NamedCallableQueryMementoImpl.ParameterMementoImpl.fromRegistration; import static org.hibernate.procedure.internal.Util.resolveResultSetMappingClasses; +import static org.hibernate.procedure.internal.Util.resolveResultSetMappingNames; import static org.hibernate.procedure.internal.Util.resolveResultSetMappings; import static org.hibernate.query.results.ResultSetMapping.resolveResultSetMapping; @@ -105,7 +107,7 @@ public class ProcedureCallImpl private FunctionReturnImpl functionReturn; - private final ProcedureParameterMetadataImpl parameterMetadata; + private final ProcedureParameterMetadataImplementor parameterMetadata; private final ProcedureParamBindings parameterBindings; private final ResultSetMapping resultSetMapping; @@ -117,29 +119,46 @@ public class ProcedureCallImpl private ProcedureOutputsImpl outputs; private static String mappingId(String procedureName, Class[] resultClasses) { + assert resultClasses != null && resultClasses.length > 0; return procedureName + ":" + join( ",", resultClasses ); } private static String mappingId(String procedureName, String[] resultSetMappingNames) { + assert resultSetMappingNames != null && resultSetMappingNames.length > 0; return procedureName + ":" + join( ",", resultSetMappingNames ); } + private void registerParameters(SharedSessionContractImplementor session, NamedCallableQueryMemento memento) { + for ( var parameterMemento : memento.getParameterMementos() ) { + registerParameter( parameterMemento.resolve( session ) ); + } + } + + private ProcedureCallImpl( + SharedSessionContractImplementor session, + String procedureName, + String resultSetMappingName, + boolean resultSetMappingDynamic, + Set synchronizedQuerySpaces) { + super( session ); + this.procedureName = procedureName; + this.synchronizedQuerySpaces = synchronizedQuerySpaces; + final var factory = session.getSessionFactory(); + resultSetMapping = resolveResultSetMapping( resultSetMappingName, resultSetMappingDynamic, factory ); + parameterMetadata = new ProcedureParameterMetadataImpl(); + parameterBindings = new ProcedureParamBindings( parameterMetadata, factory ); + } + /** * The no-returns form. * * @param session The session * @param procedureName The name of the procedure to call */ - public ProcedureCallImpl(SharedSessionContractImplementor session, String procedureName) { - super( session ); - this.procedureName = procedureName; - - parameterMetadata = new ProcedureParameterMetadataImpl(); - parameterBindings = new ProcedureParamBindings( parameterMetadata, getSessionFactory() ); - - resultSetMapping = resolveResultSetMapping( procedureName, true, session.getSessionFactory() ); - - synchronizedQuerySpaces = null; + public ProcedureCallImpl( + SharedSessionContractImplementor session, + String procedureName) { + this( session, procedureName, procedureName, true, null ); } /** @@ -149,22 +168,14 @@ public ProcedureCallImpl(SharedSessionContractImplementor session, String proced * @param procedureName The name of the procedure to call * @param resultClasses The classes making up the result */ - public ProcedureCallImpl(SharedSessionContractImplementor session, String procedureName, Class... resultClasses) { - super( session ); - - assert resultClasses != null && resultClasses.length > 0; - - this.procedureName = procedureName; - - final var factory = session.getSessionFactory(); - - parameterMetadata = new ProcedureParameterMetadataImpl(); - parameterBindings = new ProcedureParamBindings( parameterMetadata, factory ); - - synchronizedQuerySpaces = new HashSet<>(); - - resultSetMapping = resolveResultSetMapping( mappingId( procedureName, resultClasses ), factory ); - + public ProcedureCallImpl( + SharedSessionContractImplementor session, + String procedureName, + Class... resultClasses) { + this( session, procedureName, + mappingId( procedureName, resultClasses ), + false, + new HashSet<>() ); resolveResultSetMappingClasses( resultClasses, resultSetMapping, @@ -184,22 +195,11 @@ public ProcedureCallImpl( final SharedSessionContractImplementor session, String procedureName, String... resultSetMappingNames) { - super( session ); - - assert resultSetMappingNames != null && resultSetMappingNames.length > 0; - - this.procedureName = procedureName; - - final var factory = session.getSessionFactory(); - - parameterMetadata = new ProcedureParameterMetadataImpl(); - parameterBindings = new ProcedureParamBindings( parameterMetadata, factory ); - - synchronizedQuerySpaces = new HashSet<>(); - - resultSetMapping = resolveResultSetMapping( mappingId( procedureName, resultSetMappingNames ), factory ); - - Util.resolveResultSetMappingNames( + this( session, procedureName, + mappingId( procedureName, resultSetMappingNames ), + false, + new HashSet<>() ); + resolveResultSetMappingNames( resultSetMappingNames, resultSetMapping, synchronizedQuerySpaces::add, @@ -213,20 +213,13 @@ public ProcedureCallImpl( * @param session The session * @param memento The named/stored memento */ - ProcedureCallImpl(SharedSessionContractImplementor session, NamedCallableQueryMemento memento) { - super( session ); - - procedureName = memento.getCallableName(); - - final var factory = session.getSessionFactory(); - - parameterMetadata = new ProcedureParameterMetadataImpl( memento, session ); - parameterBindings = new ProcedureParamBindings( parameterMetadata, factory ); - - synchronizedQuerySpaces = makeCopy( memento.getQuerySpaces() ); - - resultSetMapping = resolveResultSetMapping( memento.getRegistrationName(), factory ); - + ProcedureCallImpl( + SharedSessionContractImplementor session, + NamedCallableQueryMemento memento) { + this( session, memento.getCallableName(), + memento.getRegistrationName(), + false, + makeCopy( memento.getQuerySpaces() ) ); resolveResultSetMappings( memento.getResultSetMappingNames(), memento.getResultSetMappingClasses(), @@ -234,7 +227,7 @@ public ProcedureCallImpl( synchronizedQuerySpaces::add, this::getSessionFactory ); - + registerParameters( session, memento ); applyOptions( memento ); } @@ -248,19 +241,10 @@ public ProcedureCallImpl( SharedSessionContractImplementor session, NamedCallableQueryMemento memento, Class... resultTypes) { - super( session ); - - procedureName = memento.getCallableName(); - - final var factory = session.getSessionFactory(); - - parameterMetadata = new ProcedureParameterMetadataImpl( memento, session ); - parameterBindings = new ProcedureParamBindings( parameterMetadata, factory ); - - synchronizedQuerySpaces = makeCopy( memento.getQuerySpaces() ); - - resultSetMapping = resolveResultSetMapping( mappingId( procedureName, resultTypes ), factory ); - + this( session, memento.getCallableName(), + mappingId( memento.getCallableName(), resultTypes ), + false, + makeCopy( memento.getQuerySpaces() ) ); resolveResultSetMappings( null, resultTypes, @@ -268,7 +252,7 @@ public ProcedureCallImpl( synchronizedQuerySpaces::add, this::getSessionFactory ); - + registerParameters( session, memento ); applyOptions( memento ); } @@ -276,19 +260,10 @@ public ProcedureCallImpl( SharedSessionContractImplementor session, NamedCallableQueryMementoImpl memento, String... resultSetMappingNames) { - super( session ); - - procedureName = memento.getCallableName(); - - final var factory = session.getSessionFactory(); - - parameterMetadata = new ProcedureParameterMetadataImpl( memento, session ); - parameterBindings = new ProcedureParamBindings( parameterMetadata, factory ); - - synchronizedQuerySpaces = makeCopy( memento.getQuerySpaces() ); - - resultSetMapping = resolveResultSetMapping( mappingId( procedureName, resultSetMappingNames ), factory ); - + this( session, memento.getCallableName(), + mappingId( memento.getCallableName(), resultSetMappingNames ), + false, + makeCopy( memento.getQuerySpaces() ) ); resolveResultSetMappings( resultSetMappingNames, null, @@ -296,7 +271,7 @@ public ProcedureCallImpl( synchronizedQuerySpaces::add, this::getSessionFactory ); - + registerParameters( session, memento ); applyOptions( memento ); } @@ -332,7 +307,7 @@ public MutableQueryOptions getQueryOptions() { } @Override - public ProcedureParameterMetadataImpl getParameterMetadata() { + public ProcedureParameterMetadataImplementor getParameterMetadata() { return parameterMetadata; } @@ -369,7 +344,7 @@ private void markAsFunctionCallRefRefCursor() { public ProcedureCallImpl markAsFunctionCall(Class resultType) { final var basicType = getTypeConfiguration().getBasicTypeForJavaType( resultType ); if ( basicType == null ) { - throw new IllegalArgumentException( "Could not resolve a BasicType for the java type: " + resultType.getName() ); + throw new IllegalArgumentException( "Could not resolve a BasicType for the Java type: " + resultType.getName() ); } markAsFunctionCall( basicType ); return this; @@ -381,7 +356,7 @@ public ProcedureCall markAsFunctionCall(Type typeReference) { throw new IllegalArgumentException( "Given type is not an OutputableType: " + typeReference ); } if ( resultSetMapping.getNumberOfResultBuilders() == 0 ) { - final SqmExpressible expressible = resolveExpressible( typeReference ); + final var expressible = resolveExpressible( typeReference ); // Function returns might not be represented as callable parameters, // but we still want to convert the result to the requested java type if possible resultSetMapping.addResultBuilder( new ScalarDomainResultBuilder<>( expressible.getExpressibleJavaType() ) ); @@ -510,12 +485,12 @@ private SqmBindableType resolveExpressible(Type typeReference) { } private void registerParameter(ProcedureParameterImplementor parameter) { - getParameterMetadata().registerParameter( parameter ); + parameterMetadata.registerParameter( parameter ); } @Override public ProcedureParameterImplementor getParameterRegistration(int position) { - return getParameterMetadata().getQueryParameter( position ); + return parameterMetadata.getQueryParameter( position ); } @Override @@ -556,12 +531,12 @@ public ProcedureParameterImplementor registerParameter( @Override public ProcedureParameterImplementor getParameterRegistration(String name) { - return getParameterMetadata().getQueryParameter( name ); + return parameterMetadata.getQueryParameter( name ); } @Override public List> getRegisteredParameters() { - return unmodifiableList( getParameterMetadata().getRegistrationsAsList() ); + return unmodifiableList( parameterMetadata.getRegistrationsAsList() ); } @Override @@ -651,8 +626,9 @@ private ProcedureOutputsImpl buildOutputs() { private Map, JdbcCallParameterRegistration> collectParameterRegistrations(JdbcOperationQueryCall call) { final Map, JdbcCallParameterRegistration> parameterRegistrations = new IdentityHashMap<>(); - if ( call.getFunctionReturn() != null ) { - parameterRegistrations.put( functionReturn, call.getFunctionReturn() ); + final var funReturn = call.getFunctionReturn(); + if ( funReturn != null ) { + parameterRegistrations.put( functionReturn, funReturn ); } final var registrations = getParameterMetadata().getRegistrationsAsList(); final var jdbcParameters = call.getParameterRegistrations(); @@ -665,8 +641,9 @@ private Map, JdbcCallParameterRegistration> collectParamet private List collectRefCursorExtractors(JdbcOperationQueryCall call) { final List refCursorExtractors = new ArrayList<>(); - if ( call.getFunctionReturn() != null ) { - final var refCursorExtractor = call.getFunctionReturn().getRefCursorExtractor(); + final var funReturn = call.getFunctionReturn(); + if ( funReturn != null ) { + final var refCursorExtractor = funReturn.getRefCursorExtractor(); if ( refCursorExtractor != null ) { refCursorExtractors.add( refCursorExtractor ); } @@ -674,8 +651,7 @@ private List collectRefCursorExtractors(JdbcOperatio final var registrations = getParameterMetadata().getRegistrationsAsList(); final var jdbcParameters = call.getParameterRegistrations(); for ( int i = 0; i < registrations.size(); i++ ) { - final var jdbcCallParameterRegistration = jdbcParameters.get( i ); - final var refCursorExtractor = jdbcCallParameterRegistration.getRefCursorExtractor(); + final var refCursorExtractor = jdbcParameters.get( i ).getRefCursorExtractor(); if ( refCursorExtractor != null ) { refCursorExtractors.add( refCursorExtractor ); } @@ -685,7 +661,7 @@ private List collectRefCursorExtractors(JdbcOperatio private JdbcParameterBindings parameterBindings( Map, JdbcCallParameterRegistration> parameterRegistrations) { - final JdbcParameterBindings jdbcParameterBindings = + final var jdbcParameterBindings = new JdbcParameterBindingsImpl( parameterRegistrations.size() ); for ( var entry : parameterRegistrations.entrySet() ) { final var registration = entry.getValue(); @@ -694,20 +670,18 @@ private JdbcParameterBindings parameterBindings( final var parameter = entry.getKey(); final var binding = getParameterBindings().getBinding( parameter ); if ( !binding.isBound() ) { - if ( parameter.getPosition() == null ) { - throw new IllegalArgumentException( "The parameter named [" + parameter + "] was not set! You need to call the setParameter method." ); + if ( parameter.isNamed() ) { + throw new IllegalArgumentException( "The parameter named '" + parameter + "' was not set" ); } else { - throw new IllegalArgumentException( "The parameter at position [" + parameter + "] was not set! You need to call the setParameter method." ); + throw new IllegalArgumentException( "The parameter at position " + parameter + " was not set" ); } } final var parameterType = (JdbcMapping) registration.getParameterType(); jdbcParameterBindings.addBinding( (JdbcParameter) parameterBinder, - new JdbcParameterBindingImpl( - parameterType, - parameterType.convertToRelationalValue( binding.getBindValue() ) - ) + new JdbcParameterBindingImpl( parameterType, + parameterType.convertToRelationalValue( binding.getBindValue() ) ) ); } } @@ -785,14 +759,13 @@ public NamedCallableQueryMemento toMemento(String name) { ); } - private static List toParameterMementos( - ProcedureParameterMetadataImpl parameterMetadata) { + private static List toParameterMementos( + ProcedureParameterMetadataImplementor parameterMetadata) { if ( parameterMetadata.getParameterStrategy() == ParameterStrategy.UNKNOWN ) { - // none... return emptyList(); } else { - final List mementos = new ArrayList<>(); + final List mementos = new ArrayList<>(); parameterMetadata.visitRegistrations( queryParameter -> mementos.add( fromRegistration( (ProcedureParameterImplementor) queryParameter ) ) @@ -815,12 +788,10 @@ public Query applyGraph(@SuppressWarnings("rawtypes") RootGraph graph, GraphS // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // outputs - private ProcedureOutputs procedureResult; - @Override public boolean execute() { try { - return outputs().getCurrent() instanceof ResultSetOutput; + return getOutputs().getCurrent() instanceof ResultSetOutput; } catch (NoMoreOutputsException e) { return false; @@ -834,30 +805,23 @@ public boolean execute() { } } - protected ProcedureOutputs outputs() { - if ( procedureResult == null ) { - procedureResult = getOutputs(); - } - return procedureResult; - } - @Override protected int doExecuteUpdate() { - // the expectation is that there is just one Output, of type UpdateCountOutput + // The expectation is that there is just one Output, of type UpdateCountOutput try { execute(); return getUpdateCount(); } finally { - outputs().release(); + getOutputs().release(); } } @Override public Object getOutputParameterValue(int position) { - // NOTE : according to spec (specifically), an exception thrown from this method should not mark for rollback. + // According to spec, an exception thrown from this method should not mark for rollback. try { - return outputs().getOutputParameterValue( position ); + return getOutputs().getOutputParameterValue( position ); } catch (ParameterStrategyException e) { throw new IllegalArgumentException( "Invalid mix of named and positional parameters", e ); @@ -869,9 +833,9 @@ public Object getOutputParameterValue(int position) { @Override public Object getOutputParameterValue(String parameterName) { - // NOTE : according to spec (specifically), an exception thrown from this method should not mark for rollback. + // According to spec, an exception thrown from this method should not mark for rollback. try { - return outputs().getOutputParameterValue( parameterName ); + return getOutputs().getOutputParameterValue( parameterName ); } catch (ParameterStrategyException e) { throw new IllegalArgumentException( "Invalid mix of named and positional parameters", e ); @@ -883,17 +847,19 @@ public Object getOutputParameterValue(String parameterName) { @Override public boolean hasMoreResults() { - return outputs().goToNext() && outputs().getCurrent() instanceof ResultSetOutput; + final var outputs = getOutputs(); + return outputs.goToNext() + && outputs.getCurrent() instanceof ResultSetOutput; } @Override public int getUpdateCount() { try { - final Output rtn = outputs().getCurrent(); - if ( rtn == null ) { + final var output = getOutputs().getCurrent(); + if ( output == null ) { return -1; } - else if ( rtn instanceof UpdateCountOutput updateCount ) { + else if ( output instanceof UpdateCountOutput updateCount ) { return updateCount.getUpdateCount(); } else { @@ -919,7 +885,7 @@ protected List doList() { } else { try { - if ( outputs().getCurrent() instanceof ResultSetOutput resultSetOutput ) { + if ( getOutputs().getCurrent() instanceof ResultSetOutput resultSetOutput ) { return ((ResultSetOutput) resultSetOutput).getResultList(); } else { @@ -928,9 +894,9 @@ protected List doList() { } } catch (NoMoreOutputsException e) { - // todo : the spec is completely silent on these type of edge-case scenarios. - // Essentially here we'd have a case where there are no more results (ResultSets nor updateCount) but - // getResultList was called. + // TODO: the spec is completely silent on these type of edge-case scenarios. + // Essentially here we'd have a case where there are no more results + // (ResultSets nor updateCount) but getResultList() was called. return null; } catch (HibernateException he) { @@ -972,22 +938,13 @@ public List getResultList() { public R getSingleResult() { final var resultList = getResultList(); if ( resultList == null || resultList.isEmpty() ) { - throw new NoResultException( - String.format( - "Call to stored procedure [%s] returned no results", - getProcedureName() - ) - ); + throw new NoResultException( "Call to stored procedure '" + getProcedureName() + + "' returned no results" ); } else if ( resultList.size() > 1 ) { - throw new NonUniqueResultException( - String.format( - "Call to stored procedure [%s] returned multiple results", - getProcedureName() - ) - ); + throw new NonUniqueResultException( "Call to stored procedure '" + getProcedureName() + + "' returned multiple results" ); } - return resultList.get( 0 ); } @@ -998,14 +955,9 @@ public R getSingleResultOrNull() { return null; } else if ( resultList.size() > 1 ) { - throw new NonUniqueResultException( - String.format( - "Call to stored procedure [%s] returned multiple results", - getProcedureName() - ) - ); + throw new NonUniqueResultException( "Call to stored procedure '" + getProcedureName() + + "' returned multiple results" ); } - return resultList.get( 0 ); } @@ -1046,7 +998,7 @@ public T unwrap(Class type) { public ProcedureCallImplementor setLockMode(LockModeType lockMode) { // the JPA spec requires IllegalStateException here, even // though it's logically an UnsupportedOperationException - throw new IllegalStateException( "Illegal attempt to set lock mode for a procedure calls" ); + throw new IllegalStateException( "Illegal attempt to set lock mode for a procedure call" ); } @Override @@ -1062,7 +1014,7 @@ public ProcedureCallImplementor setTimeout(Integer timeout) { public LockModeType getLockMode() { // the JPA spec requires IllegalStateException here, even // though it's logically an UnsupportedOperationException - throw new IllegalStateException( "Illegal attempt to get lock mode on a native-query" ); + throw new IllegalStateException( "Illegal attempt to get lock mode for a procedure call" ); } @Override @Deprecated @@ -1070,39 +1022,33 @@ public QueryImplementor setLockOptions(LockOptions lockOptions) { throw new UnsupportedOperationException( "setLockOptions does not apply to procedure calls" ); } - @Override + @Override @SuppressWarnings("resource") public ProcedureCallImplementor setHint(String hintName, Object value) { switch ( hintName ) { case HINT_CALLABLE_FUNCTION: - if ( value != null ) { - if ( value instanceof Boolean bool ) { - if ( bool ) { - applyCallableFunctionHint(); - } - } - else if ( parseBoolean( value.toString() ) ) { - applyCallableFunctionHint(); - } + if ( value instanceof Boolean bool && bool + || value instanceof String string && parseBoolean( string ) ) { + applyCallableFunctionHint(); + } + else { + throw new IllegalArgumentException( "Illegal value for hint '" + hintName + "'" ); } break; case HINT_CALLABLE_FUNCTION_RETURN_TYPE: - if ( value != null ) { - if ( value instanceof Integer code ) { - //noinspection resource - markAsFunctionCall( code ); - } - else if ( value instanceof Type type ) { - //noinspection resource - markAsFunctionCall( type ); - } - else if ( value instanceof Class type ) { - //noinspection resource - markAsFunctionCall( type ); - } - else { - //noinspection resource - markAsFunctionCall( Integer.parseInt( value.toString() ) ); - } + if ( value instanceof Integer code ) { + markAsFunctionCall( code ); + } + else if ( value instanceof Type type ) { + markAsFunctionCall( type ); + } + else if ( value instanceof Class type ) { + markAsFunctionCall( type ); + } + else if ( value instanceof String string ) { + markAsFunctionCall( parseInt( string ) ); + } + else { + throw new IllegalArgumentException( "Illegal value for hint '" + hintName + "'" ); } break; default: diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java index d4686e03774f..5e7942736e49 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java @@ -5,7 +5,6 @@ package org.hibernate.procedure.internal; import java.sql.CallableStatement; -import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -26,7 +25,7 @@ * * @author Steve Ebersole */ -public class ProcedureOutputsImpl extends OutputsImpl implements ProcedureOutputs { +class ProcedureOutputsImpl extends OutputsImpl implements ProcedureOutputs { private final ProcedureCallImpl procedureCall; private final CallableStatement callableStatement; @@ -53,7 +52,7 @@ public T getOutputParameterValue(ProcedureParameter parameter) { if ( parameter.getMode() == ParameterMode.IN ) { throw new ParameterMisuseException( "IN parameter not valid for output extraction" ); } - final JdbcCallParameterRegistration registration = parameterRegistrations.get( parameter ); + final var registration = parameterRegistrations.get( parameter ); if ( registration == null ) { throw new IllegalArgumentException( "Parameter [" + parameter + "] is not registered with this procedure call" ); } @@ -108,7 +107,7 @@ private ProcedureCurrentReturnState(boolean isResultSet, int updateCount, int re @Override public boolean indicatesMoreOutputs() { return super.indicatesMoreOutputs() - || ProcedureOutputsImpl.this.refCursorParamIndex < refCursorParameters.length; + || ProcedureOutputsImpl.this.refCursorParamIndex < refCursorParameters.length; } @Override @@ -118,8 +117,8 @@ protected boolean hasExtendedReturns() { @Override protected Output buildExtendedReturn() { - final JdbcCallRefCursorExtractor refCursorParam = refCursorParameters[ProcedureOutputsImpl.this.refCursorParamIndex++]; - final ResultSet resultSet = refCursorParam.extractResultSet( + final var refCursorParam = refCursorParameters[ProcedureOutputsImpl.this.refCursorParamIndex++]; + final var resultSet = refCursorParam.extractResultSet( callableStatement, procedureCall.getSession() ); @@ -133,13 +132,14 @@ protected boolean hasFunctionReturns() { @Override protected Output buildFunctionReturn() { - final Object result = parameterRegistrations.get( procedureCall.getFunctionReturn() ) - .getParameterExtractor() - .extractValue( - callableStatement, - false, - procedureCall.getSession() - ); + final Object result = + parameterRegistrations.get( procedureCall.getFunctionReturn() ) + .getParameterExtractor() + .extractValue( + callableStatement, + false, + procedureCall.getSession() + ); final List results = new ArrayList<>( 1 ); results.add( result ); return buildResultSetOutput( () -> results ); diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParamBindings.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParamBindings.java index f53fb220006a..6c2deaf960c6 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParamBindings.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParamBindings.java @@ -14,6 +14,7 @@ import org.hibernate.procedure.spi.ProcedureParameterBinding; import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.query.QueryParameter; +import org.hibernate.query.spi.ProcedureParameterMetadataImplementor; import org.hibernate.query.spi.QueryParameterBinding; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.QueryParameterImplementor; @@ -29,87 +30,87 @@ public class ProcedureParamBindings implements QueryParameterBindings { private static final Logger LOG = Logger.getLogger( QueryParameterBindings.class ); - private final ProcedureParameterMetadataImpl parameterMetadata; + private final ProcedureParameterMetadataImplementor parameterMetadata; private final SessionFactoryImplementor sessionFactory; private final Map, ProcedureParameterBinding> bindingMap = new HashMap<>(); public ProcedureParamBindings( - ProcedureParameterMetadataImpl parameterMetadata, + ProcedureParameterMetadataImplementor parameterMetadata, SessionFactoryImplementor sessionFactory) { this.parameterMetadata = parameterMetadata; this.sessionFactory = sessionFactory; } - public ProcedureParameterMetadataImpl getParameterMetadata() { + public ProcedureParameterMetadataImplementor getParameterMetadata() { return parameterMetadata; } @Override public boolean isBound(QueryParameterImplementor parameter) { - //noinspection SuspiciousMethodCalls - return bindingMap.containsKey( parameter ); + return parameter instanceof ProcedureParameterImplementor + && bindingMap.containsKey( parameter ); } @Override public

ProcedureParameterBinding

getBinding(QueryParameterImplementor

parameter) { - return getQueryParamerBinding( (ProcedureParameterImplementor

) parameter ); + return getQueryParameterBinding( (ProcedureParameterImplementor

) parameter ); } - public

ProcedureParameterBinding

getQueryParamerBinding(ProcedureParameterImplementor

parameter) { + public

ProcedureParameterBinding

getQueryParameterBinding(ProcedureParameterImplementor

parameter) { final var procParam = parameterMetadata.resolve( parameter ); - var binding = bindingMap.get( procParam ); + final var binding = bindingMap.get( procParam ); if ( binding == null ) { if ( !parameterMetadata.containsReference( parameter ) ) { throw new IllegalArgumentException( "Passed parameter is not registered with this query" ); } - binding = new ProcedureParameterBindingImpl<>( procParam, sessionFactory ); - bindingMap.put( procParam, binding ); + final var parameterBinding = new ProcedureParameterBindingImpl<>( procParam, sessionFactory ); + bindingMap.put( procParam, parameterBinding ); + return parameterBinding; + } + else { + //noinspection unchecked + return (ProcedureParameterBinding

) binding; } - //noinspection unchecked - return (ProcedureParameterBinding

) binding; } @Override - public

ProcedureParameterBinding

getBinding(String name) { - //noinspection unchecked - final var parameter = - (ProcedureParameterImplementor

) - parameterMetadata.getQueryParameter( name ); + public ProcedureParameterBinding getBinding(String name) { + final var parameter = parameterMetadata.getQueryParameter( name ); if ( parameter == null ) { - throw new IllegalArgumentException( "Parameter does not exist: " + name ); + throw new IllegalArgumentException( "Parameter with name '" + name + "' does not exist" ); } - return getQueryParamerBinding( parameter ); + return getQueryParameterBinding( (ProcedureParameterImplementor) parameter ); } @Override - public

ProcedureParameterBinding

getBinding(int position) { - //noinspection unchecked - final var parameter = - (ProcedureParameterImplementor

) - parameterMetadata.getQueryParameter( position ); + public ProcedureParameterBinding getBinding(int position) { + final var parameter = parameterMetadata.getQueryParameter( position ); if ( parameter == null ) { throw new IllegalArgumentException( "Parameter at position " + position + "does not exist" ); } - return getQueryParamerBinding( parameter ); + return getQueryParameterBinding( (ProcedureParameterImplementor) parameter ); } @Override public void validate() { - parameterMetadata.visitRegistrations( parameter -> validate( (ProcedureParameterImplementor) parameter ) ); + if ( LOG.isDebugEnabled() ) { + parameterMetadata.visitRegistrations( + parameter -> validate( (ProcedureParameterImplementor) parameter ) ); + } } private void validate(ProcedureParameterImplementor procParam) { - final ParameterMode mode = procParam.getMode(); + final var mode = procParam.getMode(); if ( mode == ParameterMode.IN || mode == ParameterMode.INOUT ) { if ( !getBinding( procParam ).isBound() ) { // depending on "pass nulls" this might be OK - for now, just log a warning - if ( procParam.getPosition() != null ) { + if ( procParam.isOrdinal() ) { LOG.debugf( "Procedure parameter at position %s is not bound", procParam.getPosition() ); } else { - LOG.debugf( "Procedure parameter %s is not bound", procParam.getName() ); + LOG.debugf( "Procedure parameter '%s' is not bound", procParam.getName() ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterBindingImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterBindingImpl.java index 8b1cf5a39536..cf80e1e8d8ee 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterBindingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterBindingImpl.java @@ -14,10 +14,10 @@ * * @author Steve Ebersole */ -public class ProcedureParameterBindingImpl +class ProcedureParameterBindingImpl extends QueryParameterBindingImpl implements ProcedureParameterBinding { - public ProcedureParameterBindingImpl( + ProcedureParameterBindingImpl( ProcedureParameterImplementor queryParameter, SessionFactoryImplementor sessionFactory) { super( queryParameter, sessionFactory ); diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java index 4ba366ceef69..c8244612676a 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java @@ -8,7 +8,6 @@ import java.util.Objects; import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; -import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.procedure.ParameterTypeException; import org.hibernate.procedure.spi.NamedCallableQueryMemento; import org.hibernate.procedure.spi.ParameterStrategy; @@ -16,7 +15,6 @@ import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.type.BindableType; import org.hibernate.type.OutputableType; -import org.hibernate.type.internal.BindingTypeHelper; import org.hibernate.query.spi.AbstractQueryParameter; import org.hibernate.query.spi.QueryParameterBinding; import org.hibernate.sql.exec.internal.JdbcCallParameterExtractorImpl; @@ -30,10 +28,12 @@ import jakarta.persistence.ParameterMode; +import static org.hibernate.type.internal.BindingTypeHelper.resolveTemporalPrecision; + /** * @author Steve Ebersole */ -public class ProcedureParameterImpl extends AbstractQueryParameter implements ProcedureParameterImplementor { +class ProcedureParameterImpl extends AbstractQueryParameter implements ProcedureParameterImplementor { private final String name; private final Integer position; @@ -43,7 +43,7 @@ public class ProcedureParameterImpl extends AbstractQueryParameter impleme /** * Used for named Query parameters */ - public ProcedureParameterImpl( + ProcedureParameterImpl( String name, ParameterMode mode, Class javaType, @@ -101,12 +101,16 @@ public NamedCallableQueryMemento.ParameterMemento toMemento() { public JdbcCallParameterRegistration toJdbcParameterRegistration( int startIndex, ProcedureCallImplementor procedureCall) { - final QueryParameterBinding binding = procedureCall.getParameterBindings().getBinding( this ); - final boolean isNamed = procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && this.name != null; - final SharedSessionContractImplementor session = procedureCall.getSession(); + final QueryParameterBinding binding = + procedureCall.getParameterBindings() + .getBinding( this ); + final boolean isNamed = + procedureCall.getParameterStrategy() == ParameterStrategy.NAMED + && name != null; + final var session = procedureCall.getSession(); - final OutputableType typeToUse = (OutputableType) - BindingTypeHelper.resolveTemporalPrecision( + final var typeToUse = (OutputableType) + resolveTemporalPrecision( binding == null ? null : binding.getExplicitTemporalPrecision(), getBindableType( binding ), session.getFactory().getQueryEngine().getCriteriaBuilder() @@ -116,14 +120,20 @@ public JdbcCallParameterRegistration toJdbcParameterRegistration( final JdbcParameterBinder parameterBinder; final JdbcCallRefCursorExtractorImpl refCursorExtractor; final JdbcCallParameterExtractorImpl parameterExtractor; - final ExtractedDatabaseMetaData databaseMetaData = + final var databaseMetaData = session.getFactory().getJdbcServices().getJdbcEnvironment() .getExtractedDatabaseMetaData(); final boolean passProcedureParameterNames = - session.getFactory().getSessionFactoryOptions().isPassProcedureParameterNames(); + session.getFactory().getSessionFactoryOptions() + .isPassProcedureParameterNames(); switch ( mode ) { case REF_CURSOR: - jdbcParamName = this.name != null && databaseMetaData.supportsNamedParameters() && passProcedureParameterNames ? this.name : null; + jdbcParamName = + name != null + && databaseMetaData.supportsNamedParameters() + && passProcedureParameterNames + ? name + : null; refCursorExtractor = new JdbcCallRefCursorExtractorImpl( startIndex ); parameterBinder = null; parameterExtractor = null; @@ -151,16 +161,24 @@ public JdbcCallParameterRegistration toJdbcParameterRegistration( break; } - return new JdbcCallParameterRegistrationImpl( jdbcParamName, startIndex, mode, typeToUse, parameterBinder, parameterExtractor, refCursorExtractor ); + return new JdbcCallParameterRegistrationImpl( + jdbcParamName, + startIndex, + mode, + typeToUse, + parameterBinder, + parameterExtractor, + refCursorExtractor + ); } private BindableType getBindableType(QueryParameterBinding binding) { - if ( getHibernateType() != null ) { - return getHibernateType(); + final var type = getHibernateType(); + if ( type != null ) { + return type; } else if ( binding != null ) { - //noinspection unchecked - return (BindableType) binding.getBindType(); + return binding.getBindType(); } else { return null; @@ -175,7 +193,7 @@ private String getJdbcParamName( ExtractedDatabaseMetaData databaseMetaData) { return isNamed && passProcedureParameterNames && canDoNameParameterBinding( typeToUse, procedureCall, databaseMetaData ) - ? this.name + ? name : null; } @@ -203,8 +221,8 @@ private JdbcParameterBinder getParameterBinder(BindableType typeToUse, String ) ); } - else if ( typeToUse instanceof BasicType ) { - return new JdbcParameterImpl( (BasicType) typeToUse ); + else if ( typeToUse instanceof BasicType basicType ) { + return new JdbcParameterImpl( basicType ); } else { throw new UnsupportedOperationException(); @@ -217,8 +235,8 @@ private boolean canDoNameParameterBinding( ExtractedDatabaseMetaData databaseMetaData) { return procedureCall.getFunctionReturn() == null && databaseMetaData.supportsNamedParameters() - && hibernateType instanceof ProcedureParameterNamedBinder - && ( (ProcedureParameterNamedBinder) hibernateType ).canDoSetting(); + && hibernateType instanceof ProcedureParameterNamedBinder binder + && binder.canDoSetting(); } @Override @@ -227,19 +245,21 @@ public int hashCode() { } @Override - public boolean equals(Object o) { - if ( this == o ) { + public boolean equals(Object object) { + if ( this == object ) { return true; } - if ( o == null ) { + else if ( object == null ) { return false; } - if ( !(o instanceof ProcedureParameterImpl that) ) { + else if ( !(object instanceof ProcedureParameterImpl that) ) { return false; } - return Objects.equals( name, that.name ) - && Objects.equals( position, that.position ) - && mode == that.mode; + else { + return Objects.equals( name, that.name ) + && Objects.equals( position, that.position ) + && mode == that.mode; + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterMetadataImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterMetadataImpl.java index ac0d02d98462..cd77b4e06cba 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterMetadataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterMetadataImpl.java @@ -14,13 +14,10 @@ import jakarta.persistence.Parameter; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.procedure.spi.NamedCallableQueryMemento; import org.hibernate.procedure.spi.ParameterStrategy; import org.hibernate.type.BindableType; import org.hibernate.query.QueryParameter; import org.hibernate.query.internal.QueryParameterBindingsImpl; -import org.hibernate.procedure.ProcedureParameter; import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.query.spi.ProcedureParameterMetadataImplementor; import org.hibernate.query.spi.QueryParameterBindings; @@ -36,18 +33,11 @@ * * @author Steve Ebersole */ -public class ProcedureParameterMetadataImpl implements ProcedureParameterMetadataImplementor { +class ProcedureParameterMetadataImpl implements ProcedureParameterMetadataImplementor { private ParameterStrategy parameterStrategy = ParameterStrategy.UNKNOWN; private List> parameters; - public ProcedureParameterMetadataImpl() { - } - - public ProcedureParameterMetadataImpl(NamedCallableQueryMemento memento, SharedSessionContractImplementor session) { - memento.getParameterMementos() - .forEach( parameterMemento -> registerParameter( parameterMemento.resolve( session ) ) ); - } - + @Override public void registerParameter(ProcedureParameterImplementor parameter) { if ( parameter.isNamed() ) { if ( parameterStrategy == ParameterStrategy.POSITIONAL ) { @@ -99,17 +89,18 @@ public boolean hasPositionalParameters() { @Override public Set getNamedParameterNames() { - if ( !hasNamedParameters() ) { - return emptySet(); - } - - final Set rtn = new HashSet<>(); - for ( ProcedureParameter parameter : parameters ) { - if ( parameter.getName() != null ) { - rtn.add( parameter.getName() ); + if ( hasNamedParameters() && parameters != null ) { + final Set names = new HashSet<>(); + for ( var parameter : parameters ) { + if ( parameter.getName() != null ) { + names.add( parameter.getName() ); + } } + return names; + } + else { + return emptySet(); } - return rtn; } @Override @@ -123,13 +114,14 @@ public boolean containsReference(QueryParameter parameter) { && parameters.contains( (ProcedureParameterImplementor) parameter ); } + @Override public ParameterStrategy getParameterStrategy() { return parameterStrategy; } @Override public boolean hasAnyMatching(Predicate> filter) { - if ( parameters.isEmpty() ) { + if ( parameters == null || parameters.isEmpty() ) { return false; } else { @@ -144,9 +136,11 @@ public boolean hasAnyMatching(Predicate> filter) { @Override public ProcedureParameterImplementor findQueryParameter(String name) { - for ( var parameter : parameters ) { - if ( name.equals( parameter.getName() ) ) { - return parameter; + if ( parameters != null ) { + for ( var parameter : parameters ) { + if ( name.equals( parameter.getName() ) ) { + return parameter; + } } } return null; @@ -163,9 +157,12 @@ public ProcedureParameterImplementor getQueryParameter(String name) { @Override public ProcedureParameterImplementor findQueryParameter(int positionLabel) { - for ( var parameter : parameters ) { - if ( parameter.getName() == null && positionLabel == parameter.getPosition() ) { - return parameter; + if ( parameters != null ) { + for ( var parameter : parameters ) { + if ( parameter.getName() == null + && positionLabel == parameter.getPosition() ) { + return parameter; + } } } return null; @@ -174,18 +171,20 @@ public ProcedureParameterImplementor findQueryParameter(int positionLabel) { @Override public ProcedureParameterImplementor getQueryParameter(int positionLabel) { final var queryParameter = findQueryParameter( positionLabel ); - if ( queryParameter != null ) { - return queryParameter; + if ( queryParameter == null ) { + throw new IllegalArgumentException( + "Positional parameter " + positionLabel + " is not registered with this procedure call" ); } - throw new IllegalArgumentException( "Positional parameter [" + positionLabel + "] is not registered with this procedure call" ); + return queryParameter; } @Override public

ProcedureParameterImplementor

resolve(Parameter

parameter) { - if ( parameter instanceof ProcedureParameterImplementor

parameterImplementor ) { + if ( parameters != null + && parameter instanceof ProcedureParameterImplementor

procedureParam ) { for ( var registered : parameters ) { if ( registered == parameter ) { - return parameterImplementor; + return procedureParam; } } } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ScalarDomainResultBuilder.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ScalarDomainResultBuilder.java index 240436770c6c..3c21c2a2987f 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ScalarDomainResultBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ScalarDomainResultBuilder.java @@ -20,10 +20,10 @@ /** * @author Steve Ebersole */ -public class ScalarDomainResultBuilder implements ResultBuilder { +class ScalarDomainResultBuilder implements ResultBuilder { private final JavaType typeDescriptor; - public ScalarDomainResultBuilder(JavaType typeDescriptor) { + ScalarDomainResultBuilder(JavaType typeDescriptor) { this.typeDescriptor = typeDescriptor; } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/Util.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/Util.java index 9145aaf77bca..319ff33ac5ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/Util.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/Util.java @@ -6,15 +6,11 @@ import java.util.function.Consumer; -import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.metamodel.MappingMetamodel; -import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.UnknownSqlResultSetMappingException; import org.hibernate.query.internal.ResultSetMappingResolutionContext; -import org.hibernate.query.named.NamedObjectRepository; -import org.hibernate.query.named.NamedResultSetMappingMemento; import org.hibernate.query.results.ResultSetMapping; -import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; + +import static org.hibernate.internal.util.collections.ArrayHelper.isEmpty; /** * Utilities used to implement procedure call support. @@ -28,18 +24,18 @@ private Util() { public static void resolveResultSetMappings( String[] resultSetMappingNames, - Class[] resultSetMappingClasses, + Class[] resultSetMappingClasses, ResultSetMapping resultSetMapping, Consumer querySpaceConsumer, ResultSetMappingResolutionContext context) { - if ( ! ArrayHelper.isEmpty( resultSetMappingNames ) ) { + if ( !isEmpty( resultSetMappingNames ) ) { // cannot specify both - if ( ! ArrayHelper.isEmpty( resultSetMappingClasses ) ) { + if ( !isEmpty( resultSetMappingClasses ) ) { throw new IllegalArgumentException( "Cannot specify both result-set mapping names and classes" ); } resolveResultSetMappingNames( resultSetMappingNames, resultSetMapping, querySpaceConsumer, context ); } - else if ( ! ArrayHelper.isEmpty( resultSetMappingClasses ) ) { + else if ( !isEmpty( resultSetMappingClasses ) ) { resolveResultSetMappingClasses( resultSetMappingClasses, resultSetMapping, querySpaceConsumer, context ); } @@ -51,31 +47,26 @@ public static void resolveResultSetMappingNames( ResultSetMapping resultSetMapping, Consumer querySpaceConsumer, ResultSetMappingResolutionContext context) { - final NamedObjectRepository namedObjectRepository = context.getNamedObjectRepository(); + final var namedObjectRepository = context.getNamedObjectRepository(); for ( String resultSetMappingName : resultSetMappingNames ) { - final NamedResultSetMappingMemento memento = + final var memento = namedObjectRepository.getResultSetMappingMemento( resultSetMappingName ); if ( memento == null ) { throw new UnknownSqlResultSetMappingException( "Unknown SqlResultSetMapping [" + resultSetMappingName + "]" ); } - memento.resolve( - resultSetMapping, - querySpaceConsumer, - context - ); + memento.resolve( resultSetMapping, querySpaceConsumer, context ); } } public static void resolveResultSetMappingClasses( - Class[] resultSetMappingClasses, + Class[] resultSetMappingClasses, ResultSetMapping resultSetMapping, Consumer querySpaceConsumer, ResultSetMappingResolutionContext context) { - final MappingMetamodel mappingMetamodel = context.getMappingMetamodel(); - final JavaTypeRegistry javaTypeRegistry = mappingMetamodel.getTypeConfiguration().getJavaTypeRegistry(); - - for ( Class resultSetMappingClass : resultSetMappingClasses ) { - final EntityPersister entityDescriptor = mappingMetamodel.findEntityDescriptor( resultSetMappingClass ); + final var mappingMetamodel = context.getMappingMetamodel(); + final var javaTypeRegistry = mappingMetamodel.getTypeConfiguration().getJavaTypeRegistry(); + for ( var resultSetMappingClass : resultSetMappingClasses ) { + final var entityDescriptor = mappingMetamodel.findEntityDescriptor( resultSetMappingClass ); if ( entityDescriptor != null ) { resultSetMapping.addResultBuilder( new EntityDomainResultBuilder( entityDescriptor ) ); for ( String querySpace : entityDescriptor.getSynchronizedQuerySpaces() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/QueryArgumentException.java b/hibernate-core/src/main/java/org/hibernate/query/QueryArgumentException.java index 828f3ac61e17..1b6705d39364 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/QueryArgumentException.java +++ b/hibernate-core/src/main/java/org/hibernate/query/QueryArgumentException.java @@ -15,11 +15,20 @@ */ public class QueryArgumentException extends IllegalArgumentException { private final Class parameterType; + private final Class argumentType; private final Object argument; public QueryArgumentException(String message, Class parameterType, Object argument) { - super(message); + super( message + " (argument [" + argument + "] is not assignable to " + parameterType.getName() + ")" ); this.parameterType = parameterType; + this.argumentType = argument == null ? null : argument.getClass(); + this.argument = argument; + } + + public QueryArgumentException(String message, Class parameterType, Class argumentType, Object argument) { + super( message + " (" + argumentType.getName() + " is not assignable to " + parameterType.getName() + ")" ); + this.parameterType = parameterType; + this.argumentType = argumentType; this.argument = argument; } @@ -27,6 +36,10 @@ public Class getParameterType() { return parameterType; } + public Class getArgumentType() { + return argumentType; + } + public Object getArgument() { return argument; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java index d21452932703..d76383e2f79f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java @@ -90,6 +90,7 @@ * @since 6.0 * * @author Steve Ebersole + * @author Yoobin Yoon */ @Incubating public interface HibernateCriteriaBuilder extends CriteriaBuilder { @@ -2744,6 +2745,54 @@ JpaExpression arrayAgg( @Incubating JpaExpression arrayTrim(Expression arrayExpression, Integer elementCount); + /** + * Reverses the order of elements in an array. + * + * @since 7.2 + */ + @Incubating + JpaExpression arrayReverse(Expression arrayExpression); + + /** + * Sorts the elements of an array in ascending order. + * + * @since 7.2 + */ + @Incubating + JpaExpression arraySort(Expression arrayExpression); + + /** + * Sorts the elements of an array in the specified order. + * + * @since 7.2 + */ + @Incubating + JpaExpression arraySort(Expression arrayExpression, boolean descending); + + /** + * Sorts the elements of an array in the specified order. + * + * @since 7.2 + */ + @Incubating + JpaExpression arraySort(Expression arrayExpression, Expression descendingExpression); + + /** + * Create an expression that sorts the given array with explicit null ordering. + * + * @since 7.2 + */ + @Incubating + JpaExpression arraySort(Expression arrayExpression, boolean descending, boolean nullsFirst); + + /** + * Create an expression that sorts the given array with explicit null ordering. + * + * @since 7.2 + */ + @Incubating + JpaExpression arraySort(Expression arrayExpression, Expression descendingExpression, Expression nullsFirstExpression); + /** * Creates array with the same element N times, as specified by the arguments. * @@ -3403,6 +3452,62 @@ default JpaPredicate arrayOverlapsNullable(T[] array1, Expression array @Incubating > JpaExpression collectionTrim(Expression arrayExpression, Integer elementCount); + /** + * Create an expression that reverses the order of the elements of a collection. + * + * @since 7.2 + */ + @Incubating + > JpaExpression collectionReverse(Expression collectionExpression); + + /** + * Create an expression that sorts the elements of a collection. + * + * @since 7.2 + */ + @Incubating + > JpaExpression collectionSort(Expression collectionExpression); + + /** + * Create an expression that sorts the given collection in specified order. + * + * @since 7.2 + */ + @Incubating + > JpaExpression collectionSort(Expression collectionExpression, boolean descending); + + /** + * Create an expression that sorts the given collection in specified order. + * + * @since 7.2 + */ + @Incubating + > JpaExpression collectionSort( + Expression collectionExpression, + Expression descendingExpression); + + /** + * Create an expression that sorts the given collection with explicit null ordering. + * + * @since 7.2 + */ + @Incubating + > JpaExpression collectionSort( + Expression collectionExpression, + boolean descending, + boolean nullsFirst); + + /** + * Create an expression that sorts the given collection with explicit null ordering. + * + * @since 7.2 + */ + @Incubating + > JpaExpression collectionSort( + Expression collectionExpression, + Expression descendingExpression, + Expression nullsFirstExpression); + /** * Creates basic collection with the same element N times, as specified by the arguments. * diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java index 2a67e5ab3625..71e4cd403596 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java @@ -2557,6 +2557,45 @@ public JpaExpression arrayTrim(Expression arrayExpression, Integer return criteriaBuilder.arrayTrim( arrayExpression, elementCount ); } + @Override + @Incubating + public JpaExpression arrayReverse(Expression arrayExpression) { + return criteriaBuilder.arrayReverse( arrayExpression ); + } + + @Override + @Incubating + public JpaExpression arraySort(Expression arrayExpression) { + return criteriaBuilder.arraySort( arrayExpression ); + } + + @Override + @Incubating + public JpaExpression arraySort(Expression arrayExpression, boolean descending) { + return criteriaBuilder.arraySort( arrayExpression, descending ); + } + + @Override + @Incubating + public JpaExpression arraySort(Expression arrayExpression, Expression descendingExpression) { + return criteriaBuilder.arraySort( arrayExpression, descendingExpression ); + } + + @Override + @Incubating + public JpaExpression arraySort(Expression arrayExpression, boolean descending, boolean nullsFirst) { + return criteriaBuilder.arraySort( arrayExpression, descending, nullsFirst ); + } + + @Override + @Incubating + public JpaExpression arraySort( + Expression arrayExpression, + Expression descendingExpression, + Expression nullsFirstExpression) { + return criteriaBuilder.arraySort( arrayExpression, descendingExpression, nullsFirstExpression ); + } + @Override @Incubating public JpaExpression arrayFill( @@ -3103,6 +3142,52 @@ public > JpaExpression collectionTrim( return criteriaBuilder.collectionTrim( arrayExpression, elementCount ); } + @Override + @Incubating + public > JpaExpression collectionReverse(Expression collectionExpression) { + return criteriaBuilder.collectionReverse( collectionExpression ); + } + + @Override + @Incubating + public > JpaExpression collectionSort(Expression collectionExpression) { + return criteriaBuilder.collectionSort( collectionExpression ); + } + + @Override + @Incubating + public > JpaExpression collectionSort( + Expression collectionExpression, + boolean descending) { + return criteriaBuilder.collectionSort( collectionExpression, descending ); + } + + @Override + @Incubating + public > JpaExpression collectionSort( + Expression collectionExpression, + Expression descendingExpression) { + return criteriaBuilder.collectionSort( collectionExpression, descendingExpression ); + } + + @Override + @Incubating + public > JpaExpression collectionSort( + Expression collectionExpression, + boolean descending, + boolean nullsFirst) { + return criteriaBuilder.collectionSort( collectionExpression, descending, nullsFirst ); + } + + @Override + @Incubating + public > JpaExpression collectionSort( + Expression collectionExpression, + Expression descendingExpression, + Expression nullsFirstExpression) { + return criteriaBuilder.collectionSort( collectionExpression, descendingExpression, nullsFirstExpression ); + } + @Override @Incubating public JpaExpression> collectionFill( diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java index 1cd1d5d0e2ac..e120e438de3a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java @@ -5,7 +5,6 @@ package org.hibernate.query.hql.internal; import org.hibernate.metamodel.model.domain.JpaMetamodel; -import org.hibernate.metamodel.model.domain.ManagedDomainType; import org.hibernate.query.SemanticException; import org.hibernate.query.hql.spi.DotIdentifierConsumer; import org.hibernate.query.hql.spi.SemanticPathPart; @@ -16,6 +15,7 @@ import org.hibernate.query.sqm.function.SqmFunctionDescriptor; import org.hibernate.query.sqm.spi.SqmCreationContext; import org.hibernate.query.sqm.tree.domain.SqmPath; +import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.expression.SqmEnumLiteral; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral; @@ -92,15 +92,18 @@ public void consumeIdentifier(String identifier, boolean isBase, boolean isTermi @Override public void consumeTreat(String importableName, boolean isTerminal) { - final SqmPath sqmPath = (SqmPath) currentPart; - currentPart = sqmPath.treatAs( treatTarget( importableName ) ); + currentPart = treat( importableName, (SqmPath) currentPart ); } - private Class treatTarget(String typeName) { - final ManagedDomainType managedType = + private SqmTreatedPath treat(String importableName, SqmPath path) { + return path.treatAs( treatTarget( path, importableName ) ); + } + + private Class treatTarget(SqmPath path, String typeName) { + final var javaType = creationState.getCreationContext().getJpaMetamodel() - .managedType( typeName ); - return managedType.getJavaType(); + .managedType( typeName ).getJavaType(); + return javaType.asSubclass( path.getJavaType() ); } protected void reset() { @@ -195,8 +198,7 @@ private SemanticPathPart resolvePath(String identifier, boolean isTerminal, SqmC if ( pathRootByExposedNavigable != null ) { // identifier is an "unqualified attribute reference" validateAsRoot( pathRootByExposedNavigable ); - final SqmPath sqmPath = - pathRootByExposedNavigable.get( identifier, true ); + final var sqmPath = pathRootByExposedNavigable.get( identifier, true ); return isTerminal ? sqmPath : new DomainPathPart( sqmPath ); } @@ -213,7 +215,7 @@ private SemanticPathPart resolveLiteralType(JpaMetamodel jpaMetamodel, NodeBuild return null; } else { - final ManagedDomainType managedType = jpaMetamodel.managedType( importableName ); + final var managedType = jpaMetamodel.managedType( importableName ); if ( managedType instanceof SqmEntityDomainType entityDomainType ) { return new SqmLiteralEntityType<>( entityDomainType, nodeBuilder ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java index 7f2e838a6067..39aeed17f989 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java @@ -4,22 +4,19 @@ */ package org.hibernate.query.hql.internal; -import org.hibernate.metamodel.model.domain.EntityDomainType; -import org.hibernate.metamodel.model.domain.ManagedDomainType; import org.hibernate.query.PathException; import org.hibernate.query.SemanticException; import org.hibernate.query.hql.spi.DotIdentifierConsumer; import org.hibernate.query.hql.spi.SemanticPathPart; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmCreationState; -import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.SqmJoinable; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.spi.SqmCreationHelper; import org.hibernate.query.sqm.tree.SqmJoinType; -import org.hibernate.query.sqm.tree.cte.SqmCteStatement; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor; +import org.hibernate.query.sqm.tree.domain.SqmTreatedFrom; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmCteJoin; import org.hibernate.query.sqm.tree.from.SqmEntityJoin; @@ -27,6 +24,7 @@ import org.hibernate.query.sqm.tree.from.SqmJoin; import org.hibernate.query.sqm.tree.from.SqmRoot; +import org.hibernate.query.sqm.tree.from.SqmTreatedAttributeJoin; import org.jboss.logging.Logger; import static org.hibernate.query.sqm.internal.SqmUtil.findCompatibleFetchJoin; @@ -125,7 +123,7 @@ public void consumeTreat(String entityName, boolean isTerminal) { private ConsumerDelegate resolveBase(String identifier, boolean isTerminal) { final SqmCreationProcessingState processingState = creationState.getCurrentProcessingState(); - final SqmPathRegistry pathRegistry = processingState.getPathRegistry(); + final var pathRegistry = processingState.getPathRegistry(); final SqmFrom pathRootByAlias = pathRegistry.findFromByAlias( identifier, true ); if ( pathRootByAlias != null ) { return resolveAlias( identifier, isTerminal, pathRootByAlias ); @@ -240,7 +238,7 @@ else if ( fetch ) { boolean allowReuse, SqmCreationState creationState, SqmJoinable joinSource) { - final SqmJoin join = joinSource.createSqmJoin( + final var join = joinSource.createSqmJoin( lhs, joinType, isTerminal ? alias : allowReuse ? SqmCreationHelper.IMPLICIT_ALIAS : null, @@ -296,22 +294,39 @@ public void consumeIdentifier(String identifier, boolean isTerminal, boolean all @Override public void consumeTreat(String typeName, boolean isTerminal) { + currentPath = treat( typeName, isTerminal ); + creationState.getCurrentProcessingState().getPathRegistry().register( currentPath ); + } + + private SqmTreatedFrom treat(String typeName, boolean isTerminal) { if ( isTerminal ) { - currentPath = fetch - ? ( (SqmAttributeJoin) currentPath ).treatAs( treatTarget( typeName ), alias, true ) - : currentPath.treatAs( treatTarget( typeName ), alias ); + return fetch + ? treatTerminalFetch( currentPath, typeName ) + : treatTerminal( currentPath, typeName ); } else { - currentPath = currentPath.treatAs( treatTarget( typeName ) ); + return treatNonTerminal( currentPath, typeName ); } - creationState.getCurrentProcessingState().getPathRegistry().register( currentPath ); } - private Class treatTarget(String typeName) { - final ManagedDomainType managedType = creationState.getCreationContext() - .getJpaMetamodel() - .managedType( typeName ); - return managedType.getJavaType(); + private SqmTreatedFrom treatNonTerminal(SqmFrom path, String typeName) { + return path.treatAs( treatTarget( path, typeName ) ); + } + + private SqmTreatedAttributeJoin treatTerminalFetch(SqmFrom path, String typeName) { + final var attributeJoin = (SqmAttributeJoin) path; + return attributeJoin.treatAs( treatTarget( path, typeName ), alias, true ); + } + + private SqmTreatedFrom treatTerminal(SqmFrom path, String typeName) { + return path.treatAs( treatTarget( path, typeName ), alias ); + } + + private Class treatTarget(SqmPath path, String typeName) { + final var javaType = + creationState.getCreationContext().getJpaMetamodel() + .managedType( typeName ).getJavaType(); + return javaType.asSubclass( path.getJavaType() ); } @Override @@ -357,11 +372,11 @@ public void consumeIdentifier(String identifier, boolean isTerminal, boolean all path.append( identifier ); if ( isTerminal ) { final String fullPath = path.toString(); - final EntityDomainType joinedEntityType = + final var joinedEntityType = creationState.getCreationContext().getJpaMetamodel() .getHqlEntityReference( fullPath ); if ( joinedEntityType == null ) { - final SqmCteStatement cteStatement = creationState.findCteStatement( fullPath ); + final var cteStatement = creationState.findCteStatement( fullPath ); if ( cteStatement != null ) { //noinspection rawtypes,unchecked join = new SqmCteJoin( cteStatement, alias, joinType, sqmRoot ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java index b7c294e5577d..cca925e7b74f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java @@ -78,7 +78,7 @@ private static > S copyStatement( final SqmCopyContext context = SqmCopyContext.noParamCopyContext(); // Copy the statement replacing the root's unmapped polymorphic reference with // the concrete mapped descriptor entity domain type. - final SqmRoot path = context.registerCopy( + final var path = context.registerCopy( unmappedPolymorphicReference, new SqmRoot<>( mappedDescriptor, diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index 2e721a911f52..33044d94cabd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -2138,8 +2138,6 @@ public final SqmCrossJoin visitCrossJoin(HqlParser.CrossJoinContext ctx) { protected void consumeCrossJoin(HqlParser.CrossJoinContext parserJoin, SqmRoot sqmRoot) { final String name = getEntityName( parserJoin.entityName() ); -// SqmTreeCreationLogger.LOGGER.tracef( "Handling root path - %s", name ); - final var entityDescriptor = getJpaMetamodel().resolveHqlEntityReference( name ); if ( entityDescriptor instanceof SqmPolymorphicRootDescriptor ) { @@ -2579,8 +2577,8 @@ else if ( r instanceof AnyDiscriminatorSqmPath anyDiscriminatorPath && l inst ); } - private SqmExpression createDiscriminatorValue( - AnyDiscriminatorSqmPath anyDiscriminatorTypeSqmPath, + private SqmExpression createDiscriminatorValue( + AnyDiscriminatorSqmPath anyDiscriminatorTypeSqmPath, HqlParser.ExpressionContext valueExpressionContext) { final var expressible = anyDiscriminatorTypeSqmPath.getExpressible(); return new SqmAnyDiscriminatorValue<>( diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryArguments.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryArguments.java new file mode 100644 index 000000000000..b11e89d9416d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryArguments.java @@ -0,0 +1,127 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.query.internal; + +import jakarta.persistence.metamodel.Type; +import org.hibernate.HibernateException; +import org.hibernate.type.BindingContext; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.spi.EntityJavaType; + +import java.util.Collection; + +import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; + +/** + * @author Gavin King + */ +public class QueryArguments { + + private static boolean isInstance(Object value, JavaType javaType) { + try { + if ( value == null ) { + return true; + } + else if ( javaType instanceof EntityJavaType ) { + // special handling for entity arguments due to + // the possibility of an uninitialized proxy + // (which we don't want or need to fetch) + final var javaTypeClass = javaType.getJavaTypeClass(); + final var initializer = extractLazyInitializer( value ); + final var valueEntityClass = + initializer != null + ? initializer.getPersistentClass() + : value.getClass(); + // accept assignability in either direction + return javaTypeClass.isAssignableFrom( valueEntityClass ) + || valueEntityClass.isAssignableFrom( javaTypeClass ); + } + else { + // require that the argument be assignable to the parameter + return javaType.isInstance( javaType.coerce( value ) ); + } + } + catch (HibernateException ce) { + return false; + } + } + + public static boolean isInstance( + Type parameterType, Object value, + BindingContext bindingContext) { + if ( value == null ) { + return true; + } + final var sqmExpressible = bindingContext.resolveExpressible( parameterType ); + assert sqmExpressible != null; + final var javaType = sqmExpressible.getExpressibleJavaType(); + return isInstance( value, javaType ); + } + + public static boolean areInstances( + Type parameterType, Collection values, + BindingContext bindingContext) { + if ( values.isEmpty() ) { + return true; + } + final var sqmExpressible = bindingContext.resolveExpressible( parameterType ); + assert sqmExpressible != null; + final var javaType = sqmExpressible.getExpressibleJavaType(); + for ( Object value : values ) { + if ( !isInstance( value, javaType ) ) { + return false; + } + } + return true; + } + + public static boolean areInstances( + Type parameterType, Object[] values, + BindingContext bindingContext) { + if ( values.length == 0 ) { + return true; + } + if ( parameterType.getJavaType() + .isAssignableFrom( values.getClass().getComponentType() ) ) { + return true; + } + final var sqmExpressible = bindingContext.resolveExpressible( parameterType ); + assert sqmExpressible != null; + final var javaType = sqmExpressible.getExpressibleJavaType(); + for ( Object value : values ) { + if ( !isInstance( value, javaType ) ) { + return false; + } + } + return true; + } + + public static T cast(Object value, JavaType javaType) { + if ( value == null ) { + return null; + } + else if ( javaType instanceof EntityJavaType ) { + // special handling for entity arguments due to + // the possibility of an uninitialized proxy + // (which we don't want or need to fetch) + if ( isInstance( value, javaType ) ) { + // The proxy might not literally be an + // instance of the entity class represented + // by the unreified type T, but it is an + // instance in spirit + //noinspection unchecked + return (T) value; + } + else { + throw new ClassCastException( "Cannot cast to entity type '" + + javaType.getJavaTypeClass().getTypeName() + "'" ); + } + } + else { + // require that the argument be assignable to the parameter + return javaType.cast( javaType.coerce( value ) ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java index 7c3759472b80..5884213c5fb0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java @@ -18,7 +18,6 @@ import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.query.spi.QueryPlan; import org.hibernate.query.spi.SelectQueryPlan; -import org.hibernate.query.spi.SimpleHqlInterpretationImpl; import org.hibernate.query.sql.spi.ParameterInterpretation; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.service.ServiceRegistry; diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java index 1341b1fdd2ff..3040f24cb964 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java @@ -4,19 +4,22 @@ */ package org.hibernate.query.internal; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.HibernateException; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.query.QueryArgumentException; import org.hibernate.type.BindableType; import org.hibernate.query.QueryParameter; import org.hibernate.query.spi.QueryParameterBinding; import org.hibernate.query.spi.QueryParameterBindingTypeResolver; -import org.hibernate.query.spi.QueryParameterBindingValidator; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.spi.TypeConfiguration; @@ -31,18 +34,18 @@ * * @author Steve Ebersole */ -public class QueryParameterBindingImpl implements QueryParameterBinding, JavaType.CoercionContext { +public class QueryParameterBindingImpl implements QueryParameterBinding { private final QueryParameter queryParameter; private final SessionFactoryImplementor sessionFactory; private boolean isBound; private boolean isMultiValued; - private @Nullable BindableType bindType; + private @Nullable BindableType bindType; private @Nullable MappingModelExpressible type; - private @Nullable TemporalType explicitTemporalPrecision; + private @Nullable @SuppressWarnings("deprecation") TemporalType explicitTemporalPrecision; - private Object bindValue; + private T bindValue; private Collection bindValues; /** @@ -60,7 +63,7 @@ protected QueryParameterBindingImpl( public QueryParameterBindingImpl( QueryParameter queryParameter, SessionFactoryImplementor sessionFactory, - BindableType bindType) { + @Nullable BindableType bindType) { this.queryParameter = queryParameter; this.sessionFactory = sessionFactory; this.bindType = bindType; @@ -70,13 +73,17 @@ private QueryParameterBindingTypeResolver getParameterBindingTypeResolver() { return sessionFactory.getMappingMetamodel(); } + public TypeConfiguration getTypeConfiguration() { + return sessionFactory.getTypeConfiguration(); + } + @Override - public @Nullable BindableType getBindType() { + public @Nullable BindableType getBindType() { return bindType; } @Override - public @Nullable TemporalType getExplicitTemporalPrecision() { + public @Nullable @SuppressWarnings("deprecation") TemporalType getExplicitTemporalPrecision() { return explicitTemporalPrecision; } @@ -107,90 +114,101 @@ public T getBindValue() { if ( isMultiValued ) { throw new IllegalStateException( "Binding is multi-valued; illegal call to #getBindValue" ); } - - //TODO: I believe this cast is unsound due to coercion - return (T) bindValue; + return bindValue; } @Override - public void setBindValue(T value, boolean resolveJdbcTypeIfNecessary) { - if ( !handleAsMultiValue( value, null ) ) { - final Object coerced = coerceIfNotJpa( value ); + public void setBindValue(Object value, boolean resolveJdbcTypeIfNecessary) { + if ( handleAsMultiValue( value, bindType ) ) { + setBindValues( (Collection) value ); + } + else { + final Object coerced = coerce( value ); validate( coerced ); - if ( value == null ) { // needed when setting a null value to the parameter of a native SQL query // TODO: this does not look like a very disciplined way to handle this bindNull( resolveJdbcTypeIfNecessary ); } else { - bindValue( coerced ); + initBindType( value ); + bindSingleValue( coerced ); } } } + @Override + public void setBindValue( + Object value, + @SuppressWarnings("deprecation") + TemporalType temporalTypePrecision) { + if ( handleAsMultiValue( value, bindType ) ) { + setBindValues( (Collection) value, temporalTypePrecision ); + } + else { + final Object coerced = coerce( value ); + validate( coerced ); + initBindType( value ); + bindSingleValue( coerced ); + setExplicitTemporalPrecision( temporalTypePrecision ); + } + } + + @Override + public void setBindValue(A value, @Nullable BindableType clarifiedType) { + // don't coerce, because value is already of the clarified type + validate( value ); + clarifyType( value, clarifiedType ); + bindSingleValue( value ); + } + + private void initBindType(Object value) { + if ( bindType == null ) { + @SuppressWarnings("unchecked") + // If there is no bindType set, then this is effectively a + // parameter of the top type. At least arguably safe cast. + final var self = (QueryParameterBindingImpl) this; + //noinspection UnnecessaryLocalVariable (needed to make javac happy) + final var valueType = + getParameterBindingTypeResolver() + .resolveParameterBindType( value ); + self.bindType = valueType; + } + } + private void bindNull(boolean resolveJdbcTypeIfNecessary) { isBound = true; bindValue = null; if ( resolveJdbcTypeIfNecessary && bindType == null ) { - bindType = (BindableType) + final var nullType = getTypeConfiguration().getBasicTypeRegistry() .getRegisteredType( "null" ); - } - } - - private boolean handleAsMultiValue(T value, @Nullable BindableType bindableType) { - if ( queryParameter.allowsMultiValuedBinding() - && value instanceof Collection - && !( bindableType == null - ? isRegisteredAsBasicType( value.getClass() ) - : bindableType.getJavaType().isInstance( value ) ) ) { //noinspection unchecked - setBindValues( (Collection) value ); - return true; - } - else { - return false; + bindType = (BindableType) nullType; } } - private boolean isRegisteredAsBasicType(Class valueClass) { - return getTypeConfiguration().getBasicTypeForJavaType( valueClass ) != null; + private boolean handleAsMultiValue(Object value, @Nullable BindableType bindableType) { + return queryParameter.allowsMultiValuedBinding() + && value instanceof Collection + && !validInstance( value, bindableType ); } - private void bindValue(Object value) { + private void bindSingleValue(Object value) { + bindValue = cast( value ); + bindValues = null; + isMultiValued = false; isBound = true; - bindValue = value; - if ( canBindValueBeSet( value, bindType ) ) { - bindType = getParameterBindingTypeResolver().resolveParameterBindType( value ); - } } - @Override - public void setBindValue(T value, @Nullable BindableType clarifiedType) { - if ( !handleAsMultiValue( value, clarifiedType ) ) { - if ( clarifiedType != null ) { - bindType = clarifiedType; - } - - final Object coerced = coerce( value ); - validate( coerced, clarifiedType ); - bindValue( coerced ); - } + private boolean validInstance(Object value, @Nullable BindableType bindableType) { + return bindableType == null + ? isRegisteredAsBasicType( value.getClass() ) + : bindableType.getJavaType().isInstance( value ); } - @Override - public void setBindValue(T value, TemporalType temporalTypePrecision) { - if ( !handleAsMultiValue( value, null ) ) { - if ( bindType == null ) { - bindType = queryParameter.getHibernateType(); - } - - final Object coerced = coerceIfNotJpa( value ); - validate( coerced, temporalTypePrecision ); - bindValue( coerced ); - setExplicitTemporalPrecision( temporalTypePrecision ); - } + private boolean isRegisteredAsBasicType(Class valueClass) { + return getTypeConfiguration().getBasicTypeForJavaType( valueClass ) != null; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -205,55 +223,58 @@ public Collection getBindValues() { } @Override - public void setBindValues(Collection values) { - if ( !queryParameter.allowsMultiValuedBinding() ) { - throw new IllegalArgumentException( - "Illegal attempt to bind a collection value to a single-valued parameter" - ); - } - - this.isBound = true; - this.isMultiValued = true; - - this.bindValue = null; - this.bindValues = values; - - final var iterator = values.iterator(); - T value = null; - while ( value == null && iterator.hasNext() ) { - value = iterator.next(); - } - - if ( canBindValueBeSet( value, bindType ) ) { - bindType = getParameterBindingTypeResolver().resolveParameterBindType( value ); - } + public void setBindValues(Collection values) { + assertMultivalued(); + final var coerced = values.stream().map( this::coerce ).toList(); + coerced.forEach( this::validate ); + initBindType( firstNonNull( values ) ); + bindMultipleValues( coerced ); } @Override - public void setBindValues(Collection values, BindableType clarifiedType) { - if ( clarifiedType != null ) { - bindType = clarifiedType; - } + public void setBindValues( + Collection values, + @SuppressWarnings("deprecation") TemporalType temporalTypePrecision) { setBindValues( values ); + setExplicitTemporalPrecision( temporalTypePrecision ); } @Override - public void setBindValues( - Collection values, - TemporalType temporalTypePrecision, - TypeConfiguration typeConfiguration) { - setBindValues( values ); - setExplicitTemporalPrecision( temporalTypePrecision ); + public void setBindValues(Collection values, BindableType clarifiedType) { + assertMultivalued(); + // don't coerce, because value is already of the clarified type + values.forEach( this::validate ); + clarifyType( values, clarifiedType ); + bindMultipleValues( values ); + } + + private void bindMultipleValues(Collection coerced) { + final List list = new ArrayList<>(); + for ( var value : coerced ) { + list.add( cast( value ) ); + } + bindValues = list; + bindValue = null; + isMultiValued = true; + isBound = true; + } + + private void assertMultivalued() { + if ( !queryParameter.allowsMultiValuedBinding() ) { + throw new IllegalArgumentException( + "Illegal attempt to bind a collection value to a single-valued parameter" + ); + } } - private void setExplicitTemporalPrecision(TemporalType precision) { + private void setExplicitTemporalPrecision(@SuppressWarnings("deprecation") TemporalType precision) { explicitTemporalPrecision = precision; if ( bindType == null || isTemporal( determineJavaType( bindType ) ) ) { bindType = resolveTemporalPrecision( precision, bindType, getCriteriaBuilder() ); } } - private JavaType determineJavaType(BindableType bindType) { + private JavaType determineJavaType(BindableType bindType) { return getCriteriaBuilder().resolveExpressible( bindType ).getExpressibleJavaType(); } @@ -269,14 +290,14 @@ public boolean setType(@Nullable MappingModelExpressible type) { if ( bindType == null || bindType.getJavaType() == Object.class || type instanceof ModelPart ) { if ( type instanceof BindableType ) { final boolean changed = bindType != null && type != bindType; - bindType = (BindableType) type; + bindType = (BindableType) type; return changed; } else if ( type instanceof BasicValuedMapping basicValuedMapping ) { final var jdbcMapping = basicValuedMapping.getJdbcMapping(); if ( jdbcMapping instanceof BindableType ) { final boolean changed = bindType != null && jdbcMapping != bindType; - bindType = (BindableType) jdbcMapping; + bindType = (BindableType) jdbcMapping; return changed; } } @@ -284,68 +305,92 @@ else if ( type instanceof BasicValuedMapping basicValuedMapping ) { return false; } - private void validate(Object value) { - QueryParameterBindingValidator.INSTANCE.validate( getBindType(), value, getCriteriaBuilder() ); - } - - private void validate(Object value, BindableType clarifiedType) { - QueryParameterBindingValidator.INSTANCE.validate( clarifiedType, value, getCriteriaBuilder() ); + private void clarifyType(Object valueOrValues, BindableType clarifiedType) { + if ( clarifiedType != null ) { + checkClarifiedType( clarifiedType, valueOrValues ); + @SuppressWarnings("unchecked") // safe + final var newType = (BindableType) clarifiedType; + bindType = newType; + } } - private void validate(Object value, TemporalType clarifiedTemporalType) { - QueryParameterBindingValidator.INSTANCE.validate( getBindType(), value, clarifiedTemporalType, getCriteriaBuilder() ); + private void checkClarifiedType( + @NonNull BindableType clarifiedType, + Object valueOrValues) { + final var parameterType = queryParameter.getParameterType(); + if ( parameterType != null ) { + final var clarifiedJavaType = clarifiedType.getJavaType(); + if ( !parameterType.isAssignableFrom( clarifiedJavaType ) ) { + throw new QueryArgumentException( + "Given type is incompatible with parameter type", + parameterType, clarifiedJavaType, valueOrValues + ); + } + } + else { + assert queryParameter.getHibernateType() == null; + } } - @Override - public TypeConfiguration getTypeConfiguration() { - return sessionFactory.getTypeConfiguration(); + private T cast(Object value) { + if ( value == null ) { + return null; + } + else { + final var bindableType = + getCriteriaBuilder() + .resolveExpressible( bindType ); + if ( bindableType != null ) { + return QueryArguments.cast( value, + bindableType.getExpressibleJavaType() ); + } + else if ( bindType != null ) { + return bindType.getJavaType().cast( value ); + } + else { + // no typing information, but in this + // case we can view this as T = Object + // noinspection unchecked + return (T) value; + } + } } - private Object coerceIfNotJpa(T value) { - return sessionFactory.getSessionFactoryOptions().getJpaCompliance().isLoadByIdComplianceEnabled() - ? value - : coerce( value ); + private void validate(Object value) { + QueryParameterBindingValidator.validate( queryParameter, bindType, value, sessionFactory ); } - private Object coerce(T value) { + private Object coerce(Object value) { try { - if ( canValueBeCoerced( bindType ) ) { + if ( bindType != null ) { return coerce( value, bindType ); } - else if ( canValueBeCoerced( queryParameter.getHibernateType() ) ) { - return coerce( value, queryParameter.getHibernateType() ); - } +// else if ( queryParameter.getHibernateType() != null ) { +// return coerce( value, queryParameter.getHibernateType() ); +// } else { return value; } } catch (HibernateException ex) { - throw new IllegalArgumentException( - String.format( - "Parameter value [%s] did not match expected type [%s]", - value, - bindType - ), - ex - ); + throw new QueryArgumentException( "Argument to query parameter has an incompatible type: " + ex.getMessage(), + queryParameter.getParameterType(), value ); } } - private Object coerce(T value, BindableType parameterType) { - if ( value == null ) { - return null; - } - else { - return getCriteriaBuilder().resolveExpressible( parameterType ) - .getExpressibleJavaType().coerce( value, this ); - } + private Object coerce(Object value, BindableType parameterType) { + return value == null ? null + : getCriteriaBuilder().resolveExpressible( parameterType ) + .getExpressibleJavaType().coerce( value ); } - private static boolean canValueBeCoerced(BindableType bindType) { - return bindType != null; + private static @Nullable T firstNonNull(Collection values) { + final var iterator = values.iterator(); + T value = null; + while ( value == null && iterator.hasNext() ) { + value = iterator.next(); + } + return value; } - private static boolean canBindValueBeSet(Object value, BindableType bindType) { - return value != null && bindType == null; - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingValidator.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingValidator.java new file mode 100644 index 000000000000..5e8b8ca992a9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingValidator.java @@ -0,0 +1,128 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.query.internal; + +import java.util.Collection; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.query.QueryArgumentException; +import org.hibernate.query.QueryParameter; +import org.hibernate.type.BindableType; + +import static org.hibernate.query.internal.QueryArguments.areInstances; +import static org.hibernate.query.internal.QueryArguments.isInstance; + +/** + * @author Andrea Boriero + */ +class QueryParameterBindingValidator { + + private QueryParameterBindingValidator() { + } + + public static void validate( + QueryParameter parameter, + BindableType parameterType, + Object argument, + SessionFactoryImplementor factory) { + if ( argument != null && parameterType != null ) { + final var parameterJavaType = getParameterJavaType( parameterType, factory ); + if ( parameterJavaType != null ) { + final var criteriaBuilder = factory.getQueryEngine().getCriteriaBuilder(); + if ( argument instanceof Collection collection + && !Collection.class.isAssignableFrom( parameterJavaType ) ) { + // We have a collection passed in where we were expecting a non-collection. + // NOTE: This can happen in Hibernate's notion of "parameter list" binding. + // NOTE2: The case of a collection value and an expected collection + // (if that can even happen) will fall through to the main check. + if ( !areInstances( parameterType, collection, criteriaBuilder ) ) { + throw queryArgumentException( parameterJavaType, collection, parameter ); + } + } + else if ( !argument.getClass().isArray() ) { + // assume single-valued argument + if ( !isInstance( parameterType, argument, criteriaBuilder ) ) { + throw queryArgumentException( parameterJavaType, argument, parameter ); + } + } + else { + validateArrayValuedParameterBinding( parameterJavaType, argument, parameter ); + } + } + // else nothing we can check + } + } + + private static Class getParameterJavaType( + BindableType parameterType, SessionFactoryImplementor factory) { + final var javaType = parameterType.getJavaType(); + return javaType != null + ? javaType + : factory.getQueryEngine().getCriteriaBuilder() + .resolveExpressible( parameterType ) + .getJavaType(); + } + + private static @NonNull QueryArgumentException queryArgumentException( + Class parameterJavaType, Object value, QueryParameter parameter) { + if ( parameter.isNamed() ) { + return new QueryArgumentException( "Argument to parameter named '" + + parameter.getName() + "' has an element with an incompatible type", + parameterJavaType, value ); + } + else { + return new QueryArgumentException( "Argument to parameter at position " + + parameter.isOrdinal() + " has an element with an incompatible type", + parameterJavaType, value ); + } + } + + private static @NonNull QueryArgumentException queryArgumentException( + Class parameterJavaType, Collection values, QueryParameter parameter) { + if ( parameter.isNamed() ) { + return new QueryArgumentException( "Collection-values argument to parameter named '" + + parameter.getName() + "' has an incompatible type", + parameterJavaType, values ); + } + else { + return new QueryArgumentException( "Collection-values argument to parameter at position " + + parameter.isOrdinal() + " has has an incompatible type", + parameterJavaType, values ); + } + } + + private static void validateArrayValuedParameterBinding( + Class parameterType, Object value, QueryParameter parameter) { + // TODO: improve the error messages using the given parameter info + if ( !parameterType.isArray() ) { + throw new QueryArgumentException( "Unexpected array-valued parameter binding", + parameterType, value ); + } + + final var componentType = value.getClass().getComponentType(); + final var parameterComponentType = parameterType.getComponentType(); + if ( componentType.isPrimitive() ) { + // We have a primitive array. + // We validate that the actual array has the component type (type of elements) + // that we expect based on the component type of the parameter specification. + if ( !parameterComponentType.isAssignableFrom( componentType ) ) { + throw new QueryArgumentException( "Primitive array-valued argument type did not match parameter type", + parameterType, value ); + } + } + else { + // We have an object array. + // Here we loop over the array and physically check each element against the + // type we expect based on the component type of the parameter specification. + for ( Object element : (Object[]) value ) { + if ( element != null && !parameterComponentType.isInstance( element ) ) { + throw new QueryArgumentException( "Array element did not match parameter element type", + parameterComponentType, element ); + } + } + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java index 281a0b5de19e..686c2266e656 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java @@ -65,10 +65,11 @@ private QueryParameterBindingsImpl( ParameterMetadataImplementor parameterMetadata) { this.parameterMetadata = parameterMetadata; final var queryParameters = parameterMetadata.getRegistrations(); - this.parameterBindingMap = linkedMapOfSize( queryParameters.size() ); - this.parameterBindingMapByNameOrPosition = mapOfSize( queryParameters.size() ); + parameterBindingMap = linkedMapOfSize( queryParameters.size() ); + parameterBindingMapByNameOrPosition = mapOfSize( queryParameters.size() ); for ( var queryParameter : queryParameters ) { - parameterBindingMap.put( queryParameter, createBinding( sessionFactory, parameterMetadata, queryParameter ) ); + parameterBindingMap.put( queryParameter, + createBinding( sessionFactory, parameterMetadata, queryParameter ) ); } for ( var entry : parameterBindingMap.entrySet() ) { final var queryParameter = entry.getKey(); @@ -83,28 +84,30 @@ else if ( queryParameter.isOrdinal() ) { } private static QueryParameterBindingImpl createBinding( - SessionFactoryImplementor factory, ParameterMetadataImplementor parameterMetadata, QueryParameter parameter) { + SessionFactoryImplementor factory, + ParameterMetadataImplementor parameterMetadata, + QueryParameter parameter) { return new QueryParameterBindingImpl<>( parameter, factory, parameterMetadata.getInferredParameterType( parameter ) ); } - private QueryParameterBindingsImpl(QueryParameterBindingsImpl original, SessionFactoryImplementor sessionFactory) { + private QueryParameterBindingsImpl( + QueryParameterBindingsImpl original, + SessionFactoryImplementor sessionFactory) { this.parameterMetadata = original.parameterMetadata; this.parameterBindingMap = linkedMapOfSize( original.parameterBindingMap.size() ); - this.parameterBindingMapByNameOrPosition = mapOfSize( original.parameterBindingMapByNameOrPosition.size() ); - for ( var entry : original.parameterBindingMap.entrySet() ) { - parameterBindingMap.put( entry.getKey(), createBinding( sessionFactory, entry.getValue() ) ); - } - for ( var entry : parameterBindingMap.entrySet() ) { - final var queryParameter = entry.getKey(); - final var parameterBinding = entry.getValue(); + this.parameterBindingMapByNameOrPosition = + mapOfSize( original.parameterBindingMapByNameOrPosition.size() ); + original.parameterBindingMap.forEach( (key, value) -> + parameterBindingMap.put( key, createBinding( sessionFactory, value ) ) ); + parameterBindingMap.forEach( (queryParameter, parameterBinding) -> { if ( queryParameter.isNamed() ) { parameterBindingMapByNameOrPosition.put( queryParameter.getName(), parameterBinding ); } - else if ( queryParameter.getPosition() != null ) { + else if ( queryParameter.isOrdinal() ) { parameterBindingMapByNameOrPosition.put( queryParameter.getPosition(), parameterBinding ); } - } + } ); } private static QueryParameterBindingImpl createBinding( @@ -129,30 +132,32 @@ public

QueryParameterBinding

getBinding(QueryParameterImplementor

para "Cannot create binding for parameter reference [" + parameter + "] - reference is not a parameter of this query" ); } - //noinspection unchecked - return (QueryParameterBinding

) binding; + if ( !binding.getQueryParameter().equals( parameter ) ) { + throw new IllegalStateException( "Parameter binding corrupted for: " + parameter.getName() ); + } + @SuppressWarnings("unchecked") // safe because we checked the parameter + final var castBinding = (QueryParameterBinding

) binding; + return castBinding; } @Override - public

QueryParameterBinding

getBinding(int position) { + public QueryParameterBinding getBinding(int position) { final var binding = parameterBindingMapByNameOrPosition.get( position ); if ( binding == null ) { // Invoke this method to throw the exception parameterMetadata.getQueryParameter( position ); } - //noinspection unchecked - return (QueryParameterBinding

) binding; + return binding; } @Override - public

QueryParameterBinding

getBinding(String name) { + public QueryParameterBinding getBinding(String name) { final var binding = parameterBindingMapByNameOrPosition.get( name ); if ( binding == null ) { // Invoke this method to throw the exception parameterMetadata.getQueryParameter( name ); } - //noinspection unchecked - return (QueryParameterBinding

) binding; + return binding; } @Override @@ -161,10 +166,14 @@ public void validate() { if ( !entry.getValue().isBound() ) { final var queryParameter = entry.getKey(); if ( queryParameter.isNamed() ) { - throw new QueryParameterException( "No argument for named parameter ':" + queryParameter.getName() + "'" ); + throw new QueryParameterException( + "No argument for named parameter ':" + + queryParameter.getName() + "'" ); } else { - throw new QueryParameterException( "No argument for ordinal parameter '?" + queryParameter.getPosition() + "'" ); + throw new QueryParameterException( + "No argument for ordinal parameter '?" + + queryParameter.getPosition() + "'" ); } } } @@ -228,7 +237,8 @@ public QueryKey.ParameterBindingsMemento generateQueryKeyMemento(SharedSessionCo private void handleQueryParameters(SharedSessionContractImplementor session, MutableCacheKeyImpl mutableCacheKey) { final var typeConfiguration = session.getFactory().getTypeConfiguration(); - // We know that parameters are consumed in processing order, this ensures consistency of generated cache keys + // We know that parameters are consumed in processing order; + // this ensures the consistency of generated cache keys for ( var entry : parameterBindingMap.entrySet() ) { final var queryParameter = entry.getKey(); final var binding = entry.getValue(); @@ -298,12 +308,14 @@ else if ( binding.getBindValue() != null ) { if ( bindType == null ) { if ( queryParameter.isNamed() ) { - throw new QueryParameterException( "Could not determine mapping type for named parameter ':" - + queryParameter.getName() + "'" ); + throw new QueryParameterException( + "Could not determine mapping type for named parameter ':" + + queryParameter.getName() + "'" ); } else { - throw new QueryParameterException( "Could not determine mapping type for ordinal parameter '?" - + queryParameter.getPosition() + "'" ); + throw new QueryParameterException( + "Could not determine mapping type for ordinal parameter '?" + + queryParameter.getPosition() + "'" ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoBasicStandard.java b/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoBasicStandard.java index ddba63c2b754..ff6270abf65e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoBasicStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoBasicStandard.java @@ -4,7 +4,7 @@ */ package org.hibernate.query.internal; -import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.function.Consumer; import org.hibernate.metamodel.mapping.BasicValuedMapping; @@ -24,7 +24,7 @@ import jakarta.persistence.AttributeConverter; import jakarta.persistence.ColumnResult; -import static org.hibernate.boot.model.convert.internal.ConverterHelper.extractAttributeConverterParameterizedType; +import static org.hibernate.internal.util.GenericsHelper.typeArguments; /** * Implementation of {@link ResultMementoBasic} for scalar (basic) results. @@ -86,15 +86,16 @@ public ResultMementoBasicStandard( typeConfiguration.getJavaTypeRegistry() .resolveDescriptor( converterClass ); - final var parameterizedType = - extractAttributeConverterParameterizedType( converterBean.getBeanClass() ); + final var typeArguments = + typeArguments( AttributeConverter.class, + converterBean.getBeanClass() ); builder = new CompleteResultBuilderBasicValuedConverted( explicitColumnName, converterBean, converterJtd, - determineDomainJavaType( parameterizedType, typeConfiguration.getJavaTypeRegistry() ), - resolveUnderlyingMapping( parameterizedType, typeConfiguration ) + determineDomainJavaType( typeArguments, typeConfiguration.getJavaTypeRegistry() ), + resolveUnderlyingMapping( typeArguments, typeConfiguration ) ); } else { @@ -139,25 +140,21 @@ else if ( UserType.class.isAssignableFrom( registeredJtd.getJavaTypeClass() ) ) } private BasicJavaType determineDomainJavaType( - ParameterizedType parameterizedType, + Type[] typeArguments, JavaTypeRegistry jtdRegistry) { - final var typeParameters = parameterizedType.getActualTypeArguments(); - final var domainTypeType = typeParameters[ 0 ]; - final var domainClass = (Class) domainTypeType; + final var domainClass = (Class) typeArguments[0]; return (BasicJavaType) jtdRegistry.resolveDescriptor( domainClass ); } private BasicValuedMapping resolveUnderlyingMapping( - ParameterizedType parameterizedType, + Type[] typeArguments, TypeConfiguration typeConfiguration) { - final var typeParameters = parameterizedType.getActualTypeArguments(); - return typeConfiguration.standardBasicTypeForJavaType( typeParameters[ 1 ] ); + return typeConfiguration.standardBasicTypeForJavaType( typeArguments[1] ); } public ResultMementoBasicStandard( String explicitColumnName, - BasicType explicitType, - ResultSetMappingResolutionContext context) { + BasicType explicitType) { this.explicitColumnName = explicitColumnName; this.builder = new CompleteResultBuilderBasicValuedStandard( explicitColumnName, diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/SimpleHqlInterpretationImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/SimpleHqlInterpretationImpl.java similarity index 87% rename from hibernate-core/src/main/java/org/hibernate/query/spi/SimpleHqlInterpretationImpl.java rename to hibernate-core/src/main/java/org/hibernate/query/internal/SimpleHqlInterpretationImpl.java index 9aecd24816db..120bceae71f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/SimpleHqlInterpretationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/SimpleHqlInterpretationImpl.java @@ -2,11 +2,12 @@ * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ -package org.hibernate.query.spi; +package org.hibernate.query.internal; import java.util.concurrent.ConcurrentHashMap; -import org.hibernate.Internal; +import org.hibernate.query.spi.HqlInterpretation; +import org.hibernate.query.spi.ParameterMetadataImplementor; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; @@ -17,13 +18,8 @@ /** * Default implementation if {@link HqlInterpretation}. * - * @apiNote This class is now considered internal implementation - * and will move to an internal package in a future version. - * Application programs should never depend directly on this class. - * * @author Steve Ebersole */ -@Internal public class SimpleHqlInterpretationImpl implements HqlInterpretation { private final SqmStatement sqmStatement; private final ParameterMetadataImplementor parameterMetadata; diff --git a/hibernate-core/src/main/java/org/hibernate/query/specification/internal/ProjectionSpecificationImpl.java b/hibernate-core/src/main/java/org/hibernate/query/specification/internal/ProjectionSpecificationImpl.java index 61954c799819..e97c3e821e31 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/specification/internal/ProjectionSpecificationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/specification/internal/ProjectionSpecificationImpl.java @@ -31,6 +31,8 @@ import java.util.Map; import java.util.function.BiFunction; +import static org.hibernate.internal.util.type.PrimitiveWrappers.cast; + /** * @author Gavin King */ @@ -47,14 +49,14 @@ public ProjectionSpecificationImpl(SelectionSpecification selectionSpecificat public Element select(SingularAttribute attribute) { final int position = specifications.size(); specifications.add( (select, root) -> root.get( attribute ) ); - return tuple -> (X) tuple[position]; + return tuple -> cast( attribute.getJavaType(), tuple[position] ); } @Override public Element select(Path path) { final int position = specifications.size(); specifications.add( (select, root) -> (SqmPath) path.path( root ) ); - return tuple -> (X) tuple[position]; + return tuple -> cast( path.getType(), tuple[position] ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractCommonQueryContract.java b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractCommonQueryContract.java index a82b490e1e55..8fa86412f56e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractCommonQueryContract.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractCommonQueryContract.java @@ -11,6 +11,7 @@ import jakarta.persistence.Parameter; import jakarta.persistence.TemporalType; import jakarta.persistence.metamodel.Type; +import org.checkerframework.checker.nullness.qual.NonNull; import org.hibernate.FlushMode; import org.hibernate.HibernateException; import org.hibernate.Internal; @@ -38,7 +39,6 @@ import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.internal.QueryOptionsImpl; import org.hibernate.query.sqm.NodeBuilder; -import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.tree.expression.SqmLiteral; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; @@ -51,10 +51,12 @@ import java.util.Collection; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; -import static java.util.Arrays.asList; +import static java.lang.Math.round; +import static java.util.Collections.unmodifiableSet; import static java.util.Locale.ROOT; import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER; import static org.hibernate.jpa.HibernateHints.HINT_CACHEABLE; @@ -88,6 +90,8 @@ import static org.hibernate.jpa.internal.util.ConfigurationHelper.getCacheMode; import static org.hibernate.jpa.internal.util.ConfigurationHelper.getInteger; import static org.hibernate.query.QueryLogging.QUERY_MESSAGE_LOGGER; +import static org.hibernate.query.internal.QueryArguments.areInstances; +import static org.hibernate.query.internal.QueryArguments.isInstance; /** * Base implementation of {@link CommonQueryContract}. @@ -141,14 +145,16 @@ protected int getIntegerLiteral(JpaExpression expression, int defaultVal if ( expression == null ) { return defaultValue; } - else if ( expression instanceof SqmLiteral ) { - return ( (SqmLiteral) expression ).getLiteralValue().intValue(); + else if ( expression instanceof SqmLiteral numericLiteral ) { + return numericLiteral.getLiteralValue().intValue(); } - else if ( expression instanceof Parameter ) { - final Number parameterValue = getParameterValue( (Parameter) expression ); - return parameterValue == null ? defaultValue : parameterValue.intValue(); + else if ( expression instanceof SqmParameter parameterExpression ) { + final Number number = getParameterValue( parameterExpression ); + return number == null ? defaultValue : number.intValue(); + } + else { + throw new IllegalArgumentException( "Not an integer literal: " + expression ); } - throw new IllegalArgumentException( "Can't get integer literal value from: " + expression ); } protected int getMaxRows(SqmSelectStatement selectStatement, int size) { @@ -168,11 +174,11 @@ protected int getMaxRows(SqmSelectStatement selectStatement, int size) { } private Number fetchValue(JpaExpression expression) { - if ( expression instanceof SqmLiteral ) { - return ((SqmLiteral) expression).getLiteralValue(); + if ( expression instanceof SqmLiteral numericLiteral ) { + return numericLiteral.getLiteralValue(); } - else if ( expression instanceof SqmParameter ) { - return getParameterValue( (Parameter) expression ); + else if ( expression instanceof SqmParameter numericParameter ) { + return getParameterValue( numericParameter ); } else { throw new IllegalArgumentException( "Can't get max rows value from: " + expression ); @@ -282,7 +288,7 @@ public final boolean applyHint(String hintName, Object value) { //fall through: case HINT_SPEC_QUERY_TIMEOUT: // convert milliseconds to seconds - int timeout = (int) Math.round( getInteger( value ).doubleValue() / 1000.0 ); + final int timeout = (int) round( getInteger( value ).doubleValue() / 1000.0 ); applyTimeoutHint( timeout ); return true; case HINT_COMMENT: @@ -621,15 +627,13 @@ public int getFirstResult() { @Override public abstract ParameterMetadataImplementor getParameterMetadata(); - @SuppressWarnings({"unchecked", "rawtypes"}) public Set> getParameters() { checkOpenNoRollback(); - return (Set) getParameterMetadata().getRegistrations(); + return unmodifiableSet( getParameterMetadata().getRegistrations() ); } public QueryParameterImplementor getParameter(String name) { checkOpenNoRollback(); - try { return getParameterMetadata().getQueryParameter( name ); } @@ -638,21 +642,20 @@ public QueryParameterImplementor getParameter(String name) { } } - @SuppressWarnings("unchecked") public QueryParameterImplementor getParameter(String name, Class type) { checkOpenNoRollback(); - try { - //noinspection rawtypes - final QueryParameterImplementor parameter = getParameterMetadata().getQueryParameter( name ); - if ( !type.isAssignableFrom( parameter.getParameterType() ) ) { + final var parameter = getParameterMetadata().getQueryParameter( name ); + final var parameterType = parameter.getParameterType(); + if ( !type.isAssignableFrom( parameterType ) ) { throw new IllegalArgumentException( - "The type [" + parameter.getParameterType().getName() + - "] associated with the parameter corresponding to name [" + name + - "] is not assignable to requested Java type [" + type.getName() + "]" + "Type specified for parameter named '" + name + "' is incompatible" + + " (" + parameterType.getName() + " is not assignable to " + type.getName() + ")" ); } - return parameter; + @SuppressWarnings("unchecked") // safe, just checked + var castParameter = (QueryParameterImplementor) parameter; + return castParameter; } catch ( HibernateException e ) { throw getExceptionConverter().convert( e ); @@ -661,7 +664,6 @@ public QueryParameterImplementor getParameter(String name, Class type) public QueryParameterImplementor getParameter(int position) { checkOpenNoRollback(); - try { return getParameterMetadata().getQueryParameter( position ); } @@ -670,20 +672,20 @@ public QueryParameterImplementor getParameter(int position) { } } - @SuppressWarnings( {"unchecked", "rawtypes"} ) public QueryParameterImplementor getParameter(int position, Class type) { checkOpenNoRollback(); - try { - final QueryParameterImplementor parameter = getParameterMetadata().getQueryParameter( position ); - if ( !type.isAssignableFrom( parameter.getParameterType() ) ) { + final var parameter = getParameterMetadata().getQueryParameter( position ); + final var parameterType = parameter.getParameterType(); + if ( !type.isAssignableFrom( parameterType ) ) { throw new IllegalArgumentException( - "The type [" + parameter.getParameterType().getName() + - "] associated with the parameter corresponding to position [" + position + - "] is not assignable to requested Java type [" + type.getName() + "]" + "Type specified for parameter at position " + position + " is incompatible" + + " (" + parameterType.getName() + " is not assignable to " + type.getName() + ")" ); } - return parameter; + @SuppressWarnings("unchecked") // safe, just checked + var castParameter = (QueryParameterImplementor) parameter; + return castParameter; } catch ( HibernateException e ) { throw getExceptionConverter().convert( e ); @@ -696,6 +698,7 @@ public QueryParameterImplementor getParameter(int position, Class type @Internal // Made public to work around this bug: https://bugs.openjdk.org/browse/JDK-8340443 public abstract QueryParameterBindings getQueryParameterBindings(); + protected abstract boolean resolveJdbcParameterTypeIfNecessary(); private

JavaType

getJavaType(Class

javaType) { @@ -703,33 +706,45 @@ private

JavaType

getJavaType(Class

javaType) { .resolveDescriptor( javaType ); } - protected

QueryParameterBinding

locateBinding(Parameter

parameter) { - if ( parameter instanceof QueryParameterImplementor

parameterImplementor ) { - return locateBinding( parameterImplementor ); + protected QueryParameterBinding locateBinding(Parameter parameter) { + if ( parameter instanceof QueryParameterImplementor parameterImplementor ) { + getCheckOpen(); + return getQueryParameterBindings().getBinding( getQueryParameter( parameterImplementor ) ); } - else if ( parameter.getName() != null ) { - return locateBinding( parameter.getName() ); - } - else if ( parameter.getPosition() != null ) { - return locateBinding( parameter.getPosition() ); + else { + return locateBinding( parameter.getName(), parameter.getPosition() ); } - - throw getExceptionConverter().convert( - new IllegalArgumentException( "Could not resolve binding for given parameter reference [" + parameter + "]" ) - ); } - protected

QueryParameterBinding

locateBinding(QueryParameterImplementor

parameter) { - getCheckOpen(); - return getQueryParameterBindings().getBinding( getQueryParameter( parameter ) ); + private @NonNull QueryParameterBinding locateBinding(String name, Integer position) { + final var bindings = getQueryParameterBindings(); + if ( name != null ) { + final var binding = bindings.getBinding( name ); + if ( binding == null ) { + // should never occur + throw new IllegalArgumentException( "No binding for given parameter named '" + name + "'" ); + } + return binding; + } + else if ( position != null ) { + final var binding = bindings.getBinding( position ); + if ( binding == null ) { + // should never occur + throw new IllegalArgumentException( "No binding for given parameter at position " + position ); + } + return binding; + } + else { + throw new IllegalArgumentException( "Parameter must have either a name or a position" ); + } } - protected

QueryParameterBinding

locateBinding(String name) { + protected QueryParameterBinding locateBinding(String name) { getCheckOpen(); return getQueryParameterBindings().getBinding( name ); } - protected

QueryParameterBinding

locateBinding(int position) { + protected QueryParameterBinding locateBinding(int position) { getCheckOpen(); return getQueryParameterBindings().getBinding( position ); } @@ -741,23 +756,24 @@ protected

QueryParameterImplementor

getQueryParameter(QueryParameterImple public boolean isBound(Parameter param) { getCheckOpen(); final var parameter = getParameterMetadata().resolve( param ); - return parameter != null && getQueryParameterBindings().isBound( getQueryParameter( parameter ) ); + return parameter != null + && getQueryParameterBindings().isBound( getQueryParameter( parameter ) ); } public T getParameterValue(Parameter param) { checkOpenNoRollback(); - final var parameter = getParameterMetadata().resolve( param ); if ( parameter == null ) { throw new IllegalArgumentException( "The parameter [" + param + "] is not part of this Query" ); } - - final var binding = getQueryParameterBindings().getBinding( getQueryParameter( parameter ) ); + final var binding = + getQueryParameterBindings() + .getBinding( getQueryParameter( parameter ) ); if ( binding == null || !binding.isBound() ) { - throw new IllegalStateException( "Parameter value not yet bound : " + param.toString() ); + throw new IllegalStateException( "Parameter value not yet bound : " + param ); } - if ( binding.isMultiValued() ) { + // TODO: THIS IS UNSOUND, we should really throw in this case //noinspection unchecked return (T) binding.getBindValues(); } @@ -768,54 +784,50 @@ public T getParameterValue(Parameter param) { public Object getParameterValue(String name) { checkOpenNoRollback(); - - final QueryParameterBinding binding = getQueryParameterBindings().getBinding( name ); + final var binding = getQueryParameterBindings().getBinding( name ); if ( !binding.isBound() ) { - throw new IllegalStateException( "The parameter [" + name + "] has not yet been bound" ); - } - - if ( binding.isMultiValued() ) { - return binding.getBindValues(); - } - else { - return binding.getBindValue(); + throw new IllegalStateException( "The parameter named '" + name + "' has no argument" ); } + return binding.isMultiValued() ? binding.getBindValues() : binding.getBindValue(); } public Object getParameterValue(int position) { checkOpenNoRollback(); - - final QueryParameterBinding binding = getQueryParameterBindings().getBinding( position ); + final var binding = getQueryParameterBindings().getBinding( position ); if ( !binding.isBound() ) { - throw new IllegalStateException( "The parameter [" + position + "] has not yet been bound" ); + throw new IllegalStateException( "The parameter at position" + position + " has no argument" ); } - return binding.isMultiValued() ? binding.getBindValues() : binding.getBindValue(); } + @Override public CommonQueryContract setParameter(String name, Object value) { checkOpenNoRollback(); - if ( value instanceof TypedParameterValue typedParameterValue ) { setTypedParameter( name, typedParameterValue ); } else { - final QueryParameterBinding binding = getQueryParameterBindings().getBinding( name ); - if ( multipleBinding( binding.getQueryParameter(), value ) - && value instanceof Collection collectionValue - && !isRegisteredAsBasicType( value.getClass() ) ) { - return setParameterList( name, collectionValue ); + final var binding = getQueryParameterBindings().getBinding( name ); + if ( multipleBinding( binding.getQueryParameter(), value ) ) { + setParameterList( name, (Collection) value ); + } + else { + binding.setBindValue( value, resolveJdbcParameterTypeIfNecessary() ); } - binding.setBindValue( value, resolveJdbcParameterTypeIfNecessary() ); } - return this; } - private boolean multipleBinding(QueryParameter parameter, Object value){ - if ( parameter.allowsMultiValuedBinding() ) { + private boolean multipleBinding(QueryParameter parameter, Object value){ + if ( parameter.allowsMultiValuedBinding() + && value instanceof Collection values + // this check only needed for some native queries + && !isRegisteredAsBasicType( value.getClass() ) ) { final var hibernateType = parameter.getHibernateType(); - return hibernateType == null || value == null || isInstance( hibernateType, value ); + return hibernateType == null + || values.isEmpty() + || !isInstance( hibernateType, value, getNodeBuilder() ) + || isInstance( hibernateType, values.iterator().next(), getNodeBuilder() ); } else { return false; @@ -830,13 +842,7 @@ private void setTypedParameter(int position, TypedParameterValue typedVal setParameter( position, typedValue.value(), typedValue.type() ); } - private boolean isInstance(Type parameterType, Object value) { - final SqmExpressible sqmExpressible = getNodeuilder().resolveExpressible( parameterType ); - assert sqmExpressible != null; - return sqmExpressible.getExpressibleJavaType().isInstance( value ); - } - - private NodeBuilder getNodeuilder() { + private NodeBuilder getNodeBuilder() { return getSessionFactory().getQueryEngine().getCriteriaBuilder(); } @@ -854,31 +860,30 @@ public

CommonQueryContract setParameter(String name, P value, Class

javaT @Override public

CommonQueryContract setParameter(String name, P value, Type

type) { - this.

locateBinding( name ).setBindValue( value, (BindableType

) type ); + locateBinding( name ).setBindValue( value, (BindableType

) type ); return this; } @Override @Deprecated(since = "7") public CommonQueryContract setParameter(String name, Instant value, TemporalType temporalType) { - this.locateBinding( name ).setBindValue( value, temporalType ); + locateBinding( name ).setBindValue( value, temporalType ); return this; } @Override public CommonQueryContract setParameter(int position, Object value) { checkOpenNoRollback(); - if ( value instanceof TypedParameterValue typedParameterValue ) { setTypedParameter( position, typedParameterValue ); } else { - final QueryParameterBinding binding = getQueryParameterBindings().getBinding( position ); - if ( multipleBinding( binding.getQueryParameter(), value ) - && value instanceof Collection collectionValue - && !isRegisteredAsBasicType( value.getClass() ) ) { - return setParameterList( position, collectionValue ); + final var binding = getQueryParameterBindings().getBinding( position ); + if ( multipleBinding( binding.getQueryParameter(), value ) ) { + setParameterList( position, (Collection) value ); + } + else { + binding.setBindValue( value, resolveJdbcParameterTypeIfNecessary() ); } - binding.setBindValue( value, resolveJdbcParameterTypeIfNecessary() ); } return this; } @@ -901,13 +906,13 @@ public

CommonQueryContract setParameter(int position, P value, Class

java @Override public

CommonQueryContract setParameter(int position, P value, Type

type) { - this.

locateBinding( position ).setBindValue( value, (BindableType

) type ); + locateBinding( position ).setBindValue( value, (BindableType

) type ); return this; } @Override @Deprecated(since = "7") public CommonQueryContract setParameter(int position, Instant value, TemporalType temporalType) { - this.locateBinding( position ).setBindValue( value, temporalType ); + locateBinding( position ).setBindValue( value, temporalType ); return this; } @@ -950,7 +955,14 @@ public

CommonQueryContract setParameter(Parameter

parameter, P value) { } @SuppressWarnings("unchecked") // safe, because we just checked final var typedValue = (TypedParameterValue

) value; - setParameter( parameter, typedValue.value(), typedValue.type() ); + final var castValue = typedValue.value(); + final var castType = typedValue.type(); + if ( parameter instanceof QueryParameter

queryParameter ) { + setParameter( queryParameter, castValue, castType ); + } + else { + locateBinding( parameter ).setBindValue( castValue, castType ); + } } else { locateBinding( parameter ).setBindValue( value, resolveJdbcParameterTypeIfNecessary() ); @@ -958,23 +970,6 @@ public

CommonQueryContract setParameter(Parameter

parameter, P value) { return this; } - private

void setParameter(Parameter

parameter, P value, Type

type) { - if ( parameter instanceof QueryParameter

queryParameter ) { - setParameter( queryParameter, value, type ); - } - else if ( value == null ) { - locateBinding( parameter ).setBindValue( null, (BindableType

) type ); - } - else if ( value instanceof Collection ) { - //TODO: this looks wrong to me: how can value be both a P and a (Collection

)? - locateBinding( parameter ).setBindValues( (Collection

) value ); - } - else { - locateBinding( parameter ).setBindValue( value, (BindableType

) type ); - } - } - - @Override @Deprecated public CommonQueryContract setParameter(Parameter param, Calendar value, TemporalType temporalType) { locateBinding( param ).setBindValue( value, temporalType ); @@ -1013,12 +1008,12 @@ public CommonQueryContract setParameter(int position, Date value, TemporalType t @Override public CommonQueryContract setParameterList(String name, @SuppressWarnings("rawtypes") Collection values) { - locateBinding( name ).setBindValues( values ); + getQueryParameterBindings().getBinding( name ).setBindValues( values ); return this; } public

CommonQueryContract setParameterList(String name, Collection values, Class

javaType) { - final JavaType

javaDescriptor = getJavaType( javaType ); + final var javaDescriptor = getJavaType( javaType ); if ( javaDescriptor == null ) { setParameterList( name, values ); } @@ -1028,19 +1023,31 @@ public

CommonQueryContract setParameterList(String name, Collection CommonQueryContract setParameterList(String name, Collection values, Type

type) { - this.

locateBinding( name ).setBindValues( values, (BindableType

) type ); + locateBinding( name ).setBindValues( values, (BindableType

) type ); return this; } @Override public CommonQueryContract setParameterList(String name, Object[] values) { - locateBinding( name ).setBindValues( asList( values ) ); + final var binding = getQueryParameterBindings().getBinding( name ); + setParameterValues( values, binding ); return this; } + private

void setParameterValues(Object[] values, QueryParameterBinding

binding) { + final var parameterType = binding.getBindType(); + if ( parameterType != null + && !areInstances( parameterType, values, getNodeBuilder() ) ) { + throw new QueryArgumentException( "Argument to query parameter has an incompatible type", + parameterType.getJavaType(), values.getClass().getComponentType(), values ); + } + @SuppressWarnings("unchecked") // safe, just checked + final var castArray = (P[]) values; + binding.setBindValues( List.of( castArray ) ); + } + @Override public

CommonQueryContract setParameterList(String name, P[] values, Class

javaType) { final var javaDescriptor = getJavaType( javaType ); @@ -1054,13 +1061,14 @@ public

CommonQueryContract setParameterList(String name, P[] values, Class

CommonQueryContract setParameterList(String name, P[] values, Type

type) { - this.

locateBinding( name ).setBindValues( asList( values ), (BindableType

) type ); + locateBinding( name ).setBindValues( List.of( values ), (BindableType

) type ); return this; } @Override public CommonQueryContract setParameterList(int position, @SuppressWarnings("rawtypes") Collection values) { - locateBinding( position ).setBindValues( values ); + //TODO: type checking? + getQueryParameterBindings().getBinding( position ).setBindValues( values ); return this; } @@ -1098,13 +1106,14 @@ private

Type

getParamType(Class

javaType) { @Override public

CommonQueryContract setParameterList(int position, Collection values, Type

type) { - this.

locateBinding( position ).setBindValues( values, (BindableType

) type ); + locateBinding( position ).setBindValues( values, (BindableType

) type ); return this; } @Override public CommonQueryContract setParameterList(int position, Object[] values) { - locateBinding( position ).setBindValues( asList( values ) ); + final var binding = getQueryParameterBindings().getBinding( position ); + setParameterValues( values, binding ); return this; } @@ -1117,13 +1126,11 @@ public

CommonQueryContract setParameterList(int position, P[] values, Class< else { setParameterList( position, values, getParamType( javaType ) ); } - return this; } - @SuppressWarnings({ "unchecked", "rawtypes" }) public

CommonQueryContract setParameterList(int position, P[] values, Type

type) { - locateBinding( position ).setBindValues( asList( values ), (BindableType) type ); + locateBinding( position ).setBindValues( List.of( values ), (BindableType

) type ); return this; } @@ -1154,7 +1161,7 @@ public

CommonQueryContract setParameterList(QueryParameter

parameter, Col @Override public

CommonQueryContract setParameterList(QueryParameter

parameter, P[] values) { - locateBinding( parameter ).setBindValues( values == null ? null : asList( values ) ); + locateBinding( parameter ).setBindValues( List.of( values ) ); return this; } @@ -1167,13 +1174,12 @@ public

CommonQueryContract setParameterList(QueryParameter

parameter, P[] else { setParameterList( parameter, values, getParamType( javaType ) ); } - return this; } @Override public

CommonQueryContract setParameterList(QueryParameter

parameter, P[] values, Type

type) { - locateBinding( parameter ).setBindValues( asList( values ), (BindableType

) type ); + locateBinding( parameter ).setBindValues( List.of( values ), (BindableType

) type ); return this; } @@ -1183,7 +1189,8 @@ public CommonQueryContract setProperties(@SuppressWarnings("rawtypes") Map map) final Object object = map.get( paramName ); if ( object == null ) { if ( map.containsKey( paramName ) ) { - setParameter( paramName, null, determineType( paramName, null ) ); + setParameter( paramName, null, + determineType( paramName, null ) ); } } else { @@ -1194,7 +1201,8 @@ else if ( object instanceof Object[] array ) { setParameterList( paramName, array ); } else { - setParameter( paramName, object, determineType( paramName, object.getClass() ) ); + setParameter( paramName, object, + determineType( paramName, object.getClass() ) ); } } } @@ -1202,14 +1210,15 @@ else if ( object instanceof Object[] array ) { } protected Type determineType(String namedParam, Class retType) { - BindableType type = locateBinding( namedParam ).getBindType(); + var type = getQueryParameterBindings().getBinding( namedParam ).getBindType(); if ( type == null ) { type = getParameterMetadata().getQueryParameter( namedParam ).getHibernateType(); } if ( type == null && retType != null ) { type = getSessionFactory().getMappingMetamodel().resolveParameterBindType( retType ); } - if ( retType!= null && !retType.isAssignableFrom( type.getJavaType() ) ) { + if ( retType != null && !retType.isAssignableFrom( type.getJavaType() ) ) { + // TODO: is this really the correct exception type? throw new IllegalStateException( "Parameter not of expected type: " + retType.getName() ); } //noinspection unchecked diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java index b6d2f7cc540f..c38a929bc353 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java @@ -147,7 +147,7 @@ protected void applyOptions(NamedQueryMemento memento) { @Override public List list() { - final HashSet fetchProfiles = beforeQueryHandlingFetchProfiles(); + final var fetchProfiles = beforeQueryHandlingFetchProfiles(); boolean success = false; try { final List result = doList(); @@ -174,22 +174,32 @@ protected HashSet beforeQueryHandlingFetchProfiles() { protected void beforeQuery() { getQueryParameterBindings().validate(); - final var session = getSession(); final var options = getQueryOptions(); - session.prepareForQueryExecution( requiresTxn( options.getLockOptions().getLockMode() ) ); prepareForExecution(); + prepareSessionFlushMode( session ); + prepareSessionCacheMode( session ); + } - assert sessionFlushMode == null; - assert sessionCacheMode == null; + /* + * Used by Hibernate Reactive + */ + protected void prepareSessionFlushMode(SharedSessionContractImplementor session) { + assert sessionFlushMode == null; final var effectiveFlushMode = getQueryOptions().getFlushMode(); if ( effectiveFlushMode != null && session instanceof SessionImplementor statefulSession ) { sessionFlushMode = statefulSession.getHibernateFlushMode(); statefulSession.setHibernateFlushMode( effectiveFlushMode ); } + } + /* + * Used by Hibernate Reactive + */ + protected void prepareSessionCacheMode(SharedSessionContractImplementor session) { + assert sessionCacheMode == null; final var effectiveCacheMode = getCacheMode(); if ( effectiveCacheMode != null ) { sessionCacheMode = session.getCacheMode(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/ProcedureParameterMetadataImplementor.java b/hibernate-core/src/main/java/org/hibernate/query/spi/ProcedureParameterMetadataImplementor.java index 4382ae09c303..25c86cac9e96 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/ProcedureParameterMetadataImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/ProcedureParameterMetadataImplementor.java @@ -6,9 +6,25 @@ import java.util.List; +import jakarta.persistence.Parameter; +import org.hibernate.procedure.spi.ParameterStrategy; import org.hibernate.procedure.spi.ProcedureParameterImplementor; public interface ProcedureParameterMetadataImplementor extends ParameterMetadataImplementor { + ParameterStrategy getParameterStrategy(); + + @Override + ProcedureParameterImplementor getQueryParameter(String name); + + @Override + ProcedureParameterImplementor getQueryParameter(int positionLabel); + + @Override +

ProcedureParameterImplementor

resolve(Parameter

parameter); + + void registerParameter(ProcedureParameterImplementor parameter); + List> getRegistrationsAsList(); + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBinding.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBinding.java index 703d64f0324a..0b1b0a6a2c67 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBinding.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBinding.java @@ -10,21 +10,35 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.Incubating; import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.query.QueryArgumentException; import org.hibernate.query.QueryParameter; import org.hibernate.type.BindableType; -import org.hibernate.type.spi.TypeConfiguration; /** - * The value/type binding information for a particular query parameter. Supports - * both single-valued and multivalued binds + * The value and type binding information for a particular query parameter. + * Can represent both single-valued and multivalued bindings of arguments + * to a parameter. + *

+ * The operations of this interface attempt to assign argument values to + * the parameter of type {@code T}. If the argument cannot be coerced to + * {@code T}, the operation fails and throws {@link QueryArgumentException}. + * + * @param The type of the query parameter * * @author Steve Ebersole */ @Incubating public interface QueryParameterBinding { /** - * Is any value (including {@code null}) bound? Asked another way, - * were any of the `#set` methods called? + * The query parameter associated with this binding. + */ + QueryParameter getQueryParameter(); + + /** + * Is any argument (even a {@code null} argument) currently + * bound to the parameter? That is, was one of the + * {@link #setBindValue} or {@link #setBindValues} methods + * execute successfully? */ boolean isBound(); @@ -33,88 +47,129 @@ public interface QueryParameterBinding { */ boolean isMultiValued(); - QueryParameter getQueryParameter(); - /** - * Get the Type currently associated with this binding. + * Get the type currently associated with this binding. + * By default, this is the type inferred from the parameter. + * It may be modified via a call to {@link #setBindValue} or + * {@link #setBindValues}. * - * @return The currently associated Type + * @return The currently associated {@link BindableType} */ - @Nullable BindableType getBindType(); + @Nullable BindableType getBindType(); /** - * If the parameter represents a temporal type, return the explicitly - * specified precision - if one. + * If the parameter is of a temporal type, return the explicitly + * specified precision, if any. */ - @Nullable TemporalType getExplicitTemporalPrecision(); + @Nullable @SuppressWarnings("deprecation") TemporalType getExplicitTemporalPrecision(); /** - * Sets the parameter binding value. The inherent parameter type (if known) is assumed + * Set argument. If the given value is a {@link Collection}, + * it might be interpreted as multiple arguments. Use the + * inherent type of the parameter. + * + * @throws QueryArgumentException + * if the value cannot be bound to the parameter */ - default void setBindValue(T value) { + default void setBindValue(Object value) { setBindValue( value, false ); } /** - * Sets the parameter binding value. The inherent parameter type (if known) is assumed. - * The flag controls whether the parameter type should be resolved if necessary. + * Set argument. If the given value is a {@link Collection}, + * it might be interpreted as multiple arguments. Use the + * inherent type of the parameter. + * + * @param resolveJdbcTypeIfNecessary + * Controls whether the parameter type should be + * resolved if necessary. + * @throws QueryArgumentException + * if the value cannot be bound to the parameter */ - void setBindValue(T value, boolean resolveJdbcTypeIfNecessary); + void setBindValue(Object value, boolean resolveJdbcTypeIfNecessary); /** - * Sets the parameter binding value using the explicit Type. - * @param value The bind value - * @param clarifiedType The explicit Type to use + * Set the argument, specifying an explicit {@link BindableType}. + * + * @param value The argument + * @param clarifiedType The explicit type */ - void setBindValue(T value, @Nullable BindableType clarifiedType); + void setBindValue(A value, @Nullable BindableType clarifiedType); /** - * Sets the parameter binding value using the explicit TemporalType. - * @param value The bind value - * @param temporalTypePrecision The temporal type to use + * Set the argument, specifying an explicit {@link TemporalType}. + * If the given value is a {@link Collection}, it might be interpreted + * as multiple arguments. + * + * @param value The argument + * @param temporalTypePrecision The explicit temporal type + * @throws QueryArgumentException + * if the value cannot be bound to the parameter */ - void setBindValue(T value, TemporalType temporalTypePrecision); + void setBindValue(Object value, @SuppressWarnings("deprecation") TemporalType temporalTypePrecision); /** - * Get the value current bound. + * Get the argument currently bound to the parameter. * - * @return The currently bound value + * @return The argument currently bound + * @throws IllegalStateException + * if the parameter is multivalued */ T getBindValue(); /** - * Sets the parameter binding values. The inherent parameter type (if known) is assumed in regards to the - * individual values. - * @param values The bind values + * Attempt to set multiple arguments to the parameter. Use the + * inherent type of the parameter. * + * @param values The arguments + * @throws IllegalArgumentException if the parameter is not multivalued + * @throws QueryArgumentException + * if one of the values cannot be bound to the parameter */ - void setBindValues(Collection values); + void setBindValues(Collection values); /** - * Sets the parameter binding values using the explicit Type in regards to the individual values. - * @param values The bind values - * @param clarifiedType The explicit Type to use + * Attempt to set multiple arguments to the parameter, specifying + * an explicit {@link BindableType}. + * + * @param values The arguments + * @param clarifiedType The explicit type + * @throws IllegalArgumentException + * if the parameter is not multivalued + * @throws QueryArgumentException + * if one of the values cannot be bound to the parameter */ - void setBindValues(Collection values, BindableType clarifiedType); + void setBindValues(Collection values, BindableType clarifiedType); - /**Sets the parameter binding value using the explicit TemporalType in regards to the individual values. + /** + * Attempt to set multiple arguments to the parameter, specifying + * an explicit {@link TemporalType}. * - * @param values The bind values - * @param temporalTypePrecision The temporal type to use + * @param values The arguments + * @param temporalTypePrecision The explicit temporal type + * @throws IllegalArgumentException + * if the parameter is not multivalued + * @throws QueryArgumentException + * if one of the values cannot be bound to the parameter */ - void setBindValues(Collection values, TemporalType temporalTypePrecision, TypeConfiguration typeConfiguration); + void setBindValues( + Collection values, + @SuppressWarnings("deprecation") + TemporalType temporalTypePrecision); /** - * Get the values currently bound. + * Get the arguments currently bound to the parameter. * - * @return The currently bound values + * @return The arguments currently bound + * @throws IllegalArgumentException if the parameter is not multivalued */ Collection getBindValues(); /** - * Returns the inferred mapping model expressible i.e. the model reference against which this parameter is compared. + * Returns the inferred mapping model expressible, i.e., + * the model reference against which this parameter is compared. * - * @return the inferred mapping model expressible or null + * @return the inferred mapping model expressible or {@code null} */ @Nullable MappingModelExpressible getType(); @@ -122,7 +177,7 @@ default void setBindValue(T value) { * Sets the mapping model expressible for this parameter. * * @param type The mapping model expressible - * @return Whether the bind type was changed + * @return Whether the binding type was actually changed */ boolean setType(@Nullable MappingModelExpressible type); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingValidator.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingValidator.java deleted file mode 100644 index 83d1d482e522..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingValidator.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.query.spi; - -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; - -import org.hibernate.query.QueryArgumentException; -import org.hibernate.query.sqm.SqmBindableType; -import org.hibernate.type.BindableType; -import org.hibernate.type.BindingContext; -import org.hibernate.type.descriptor.java.JavaType; - -import jakarta.persistence.TemporalType; - -/** - * @author Andrea Boriero - */ -public class QueryParameterBindingValidator { - - public static final QueryParameterBindingValidator INSTANCE = new QueryParameterBindingValidator(); - - private QueryParameterBindingValidator() { - } - - public void validate(BindableType paramType, Object bind, BindingContext bindingContext) { - validate( paramType, bind, null, bindingContext ); - } - - public void validate( - BindableType paramType, - Object bind, - TemporalType temporalPrecision, - BindingContext bindingContext) { - if ( bind == null || paramType == null ) { - // nothing we can check - return; - } - - final SqmBindableType sqmExpressible = bindingContext.resolveExpressible( paramType ); - final Class parameterJavaType = - paramType.getJavaType() != null - ? paramType.getJavaType() - : sqmExpressible.getJavaType(); - - if ( parameterJavaType != null ) { - if ( bind instanceof Collection collection - && !Collection.class.isAssignableFrom( parameterJavaType ) ) { - // we have a collection passed in where we are expecting a non-collection. - // NOTE : this can happen in Hibernate's notion of "parameter list" binding - // NOTE2 : the case of a collection value and an expected collection (if that can even happen) - // will fall through to the main check. - validateCollectionValuedParameterBinding( - parameterJavaType, - collection, - temporalPrecision - ); - } - else if ( bind.getClass().isArray() ) { - validateArrayValuedParameterBinding( - parameterJavaType, - bind, - temporalPrecision - ); - } - else if ( !isValidBindValue( - sqmExpressible.getExpressibleJavaType(), - parameterJavaType, - bind, - temporalPrecision - ) ) { - throw new QueryArgumentException( - String.format( - "Argument [%s] of type [%s] did not match parameter type [%s (%s)]", - bind, - bind.getClass().getName(), - parameterJavaType.getName(), - extractName( temporalPrecision ) - ), - parameterJavaType, - bind - ); - } - } - // else nothing we can check - } - - private String extractName(TemporalType temporalType) { - return temporalType == null ? "n/a" : temporalType.name(); - } - - private void validateCollectionValuedParameterBinding( - Class parameterType, - Collection value, - TemporalType temporalType) { - // validate the elements... - for ( Object element : value ) { - if ( !isValidBindValue( parameterType, element, temporalType ) ) { - throw new QueryArgumentException( - String.format( - "Parameter value element [%s] did not match expected type [%s (%s)]", - element, - parameterType.getName(), - extractName( temporalType ) - ) - , - parameterType, - element - ); - } - } - } - - private static boolean isValidBindValue( - JavaType expectedJavaType, - Class expectedType, - Object value, - TemporalType temporalType) { - if ( value == null ) { - return true; - } - else if ( expectedJavaType.isInstance( value ) ) { - return true; - } - else if ( temporalType != null ) { - final boolean parameterDeclarationIsTemporal = Date.class.isAssignableFrom( expectedType ) - || Calendar.class.isAssignableFrom( expectedType ); - final boolean bindIsTemporal = value instanceof Date - || value instanceof Calendar; - - return parameterDeclarationIsTemporal && bindIsTemporal; - } - - return false; - } - - private static boolean isValidBindValue( - Class expectedType, - Object value, - TemporalType temporalType) { - if ( expectedType.isPrimitive() ) { - if ( expectedType == boolean.class ) { - return value instanceof Boolean; - } - else if ( expectedType == char.class ) { - return value instanceof Character; - } - else if ( expectedType == byte.class ) { - return value instanceof Byte; - } - else if ( expectedType == short.class ) { - return value instanceof Short; - } - else if ( expectedType == int.class ) { - return value instanceof Integer; - } - else if ( expectedType == long.class ) { - return value instanceof Long; - } - else if ( expectedType == float.class ) { - return value instanceof Float; - } - else if ( expectedType == double.class ) { - return value instanceof Double; - } - return false; - } - else if ( value == null) { - return true; - } - else if ( expectedType.isInstance( value ) ) { - return true; - } - else if ( temporalType != null ) { - final boolean parameterDeclarationIsTemporal = Date.class.isAssignableFrom( expectedType ) - || Calendar.class.isAssignableFrom( expectedType ); - final boolean bindIsTemporal = value instanceof Date - || value instanceof Calendar; - - return parameterDeclarationIsTemporal && bindIsTemporal; - } - - return false; - } - - private void validateArrayValuedParameterBinding( - Class parameterType, - Object value, - TemporalType temporalType) { - if ( !parameterType.isArray() ) { - throw new QueryArgumentException( - String.format( - "Encountered array-valued parameter binding, but was expecting [%s (%s)]", - parameterType.getName(), - extractName( temporalType ) - ), - parameterType, - value - ); - } - - if ( value.getClass().getComponentType().isPrimitive() ) { - // we have a primitive array. we validate that the actual array has the component type (type of elements) - // we expect based on the component type of the parameter specification - if ( !parameterType.getComponentType().isAssignableFrom( value.getClass().getComponentType() ) ) { - throw new QueryArgumentException( - String.format( - "Primitive array-valued parameter bind value type [%s] did not match expected type [%s (%s)]", - value.getClass().getComponentType().getName(), - parameterType.getName(), - extractName( temporalType ) - ), - parameterType, - value - ); - } - } - else { - // we have an object array. Here we loop over the array and physically check each element against - // the type we expect based on the component type of the parameter specification - final Object[] array = (Object[]) value; - for ( Object element : array ) { - if ( !isValidBindValue( parameterType.getComponentType(), element, temporalType ) ) { - throw new QueryArgumentException( - String.format( - "Array-valued parameter value element [%s] did not match expected type [%s (%s)]", - element, - parameterType.getName(), - extractName( temporalType ) - ), - parameterType, - array - ); - } - } - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindings.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindings.java index 782eecfc15ff..be32890f6251 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindings.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindings.java @@ -58,7 +58,7 @@ default

QueryParameterBinding

getBinding(QueryParameter

parameter) { * * @return The binding, or {@code null} if not yet bound */ -

QueryParameterBinding

getBinding(String name); + QueryParameterBinding getBinding(String name); /** * Access to the binding via position @@ -67,7 +67,7 @@ default

QueryParameterBinding

getBinding(QueryParameter

parameter) { * * @return The binding, or {@code null} if not yet bound */ -

QueryParameterBinding

getBinding(int position); + QueryParameterBinding getBinding(int position); /** * Validate the bindings. Called just before execution @@ -94,15 +94,14 @@ default

QueryParameterBinding

getBinding(QueryParameter

parameter) { * Currently unused and can be safely removed. */ @Deprecated(forRemoval = true, since = "6.6") - @SuppressWarnings({"rawtypes", "unchecked"}) QueryParameterBindings NO_PARAM_BINDINGS = new QueryParameterBindings() { @Override - public boolean isBound(QueryParameterImplementor parameter) { + public boolean isBound(QueryParameterImplementor parameter) { return false; } @Override - public QueryParameterBinding getBinding(QueryParameterImplementor parameter) { + public

QueryParameterBinding

getBinding(QueryParameterImplementor

parameter) { return null; } @@ -117,7 +116,7 @@ public QueryParameterBinding getBinding(int position) { } @Override - public void visitBindings(BiConsumer action) { + public void visitBindings(BiConsumer, ? super QueryParameterBinding> action) { } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/SqlOmittingQueryOptions.java b/hibernate-core/src/main/java/org/hibernate/query/spi/SqlOmittingQueryOptions.java index 14aed357672e..93f2493b6ed4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/SqlOmittingQueryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/SqlOmittingQueryOptions.java @@ -16,7 +16,7 @@ * * @author Christian Beikov */ -@Internal +@Internal // used by Hibernate Reactive public class SqlOmittingQueryOptions extends DelegatingQueryOptions { private final boolean omitLimit; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java index b960851b3c3a..6fae0b5323e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java @@ -127,7 +127,7 @@ import static org.hibernate.internal.util.collections.CollectionHelper.makeCopy; import static org.hibernate.internal.util.collections.CollectionHelper.toSmallList; import static org.hibernate.internal.util.collections.CollectionHelper.toSmallMap; -import static org.hibernate.internal.util.type.PrimitiveWrapperHelper.getDescriptorByPrimitiveType; +import static org.hibernate.internal.util.type.PrimitiveWrappers.canonicalize; import static org.hibernate.jpa.HibernateHints.HINT_NATIVE_LOCK_MODE; import static org.hibernate.query.results.internal.Builders.resultClassBuilder; import static org.hibernate.query.results.ResultSetMapping.resolveResultSetMapping; @@ -384,11 +384,7 @@ private boolean validConstructorFoundForResultType(Class resultType, ResultSe } private static boolean constructorParameterMatches(ResultBuilder resultBuilder, Class paramType) { - final var parameterClass = - paramType.isPrimitive() - ? getDescriptorByPrimitiveType( paramType ).getWrapperClass() - : paramType; - return resultBuilder.getJavaType() == parameterClass; + return resultBuilder.getJavaType() == canonicalize( paramType ); } protected void setTupleTransformerForResultType(Class resultClass) { @@ -554,6 +550,14 @@ public boolean hasCallbackActions() { return callback != null && callback.hasAfterLoadActions(); } + /* + * Used by Hibernate Reactive + */ + @Override + protected void resetCallback() { + callback = null; + } + @Override public QueryParameterBindings getQueryParameterBindings() { return parameterBindings; @@ -722,14 +726,17 @@ protected void prepareForExecution() { getSession().flush(); } // Reset the callback before every execution - callback = null; + resetCallback(); } // Otherwise, the application specified query spaces via the Hibernate // SynchronizeableQuery and so the query will already perform a partial // flush according to the defined query spaces - no need for a full flush. } - private boolean shouldFlush() { + /* + * Used by Hibernate Reactive + */ + protected boolean shouldFlush() { if ( getSession().isTransactionInProgress() ) { final var flushMode = getQueryOptions().getFlushMode(); return switch ( flushMode == null ? getSession().getHibernateFlushMode() : flushMode ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java index 0ed5efced51c..0780ce1cd8d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java @@ -16,6 +16,7 @@ import org.hibernate.LockMode; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.loader.internal.AliasConstantsHelper; +import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; @@ -260,16 +261,19 @@ private List columnNames( private NavigablePath determineNavigablePath(LegacyFetchBuilder fetchBuilder) { final var ownerResult = alias2Return.get( fetchBuilder.getOwnerAlias() ); - final NavigablePath path; + final NavigablePath basePath; if ( ownerResult instanceof NativeQuery.RootReturn rootReturn ) { - path = rootReturn.getNavigablePath(); + basePath = rootReturn.getNavigablePath(); } else if ( ownerResult instanceof DynamicFetchBuilderLegacy dynamicFetchBuilderLegacy ) { - path = determineNavigablePath( dynamicFetchBuilderLegacy ); + basePath = determineNavigablePath( dynamicFetchBuilderLegacy ); } else { throw new AssertionFailure( "Unexpected fetch builder" ); } + final NavigablePath path = alias2CollectionPersister.containsKey( fetchBuilder.getOwnerAlias() ) + ? basePath.append( CollectionPart.Nature.ELEMENT.getName() ) + : basePath; return path.append( fetchBuilder.getFetchable().getFetchableName() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java index bcf7134ccb6d..b0b634b50776 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java @@ -84,6 +84,7 @@ * Adapts the JPA CriteriaBuilder to generate SQM nodes. * * @author Steve Ebersole + * @author Yoobin Yoon */ public interface NodeBuilder extends HibernateCriteriaBuilder, SqmCreationContext { default JpaMetamodel getDomainModel() { @@ -246,6 +247,24 @@ SqmExpression arrayAgg( @Override SqmExpression arrayTrim(Expression arrayExpression, Integer elementCount); + @Override + SqmExpression arrayReverse(Expression arrayExpression); + + @Override + SqmExpression arraySort(Expression arrayExpression); + + @Override + SqmExpression arraySort(Expression arrayExpression, boolean descending); + + @Override + SqmExpression arraySort(Expression arrayExpression, Expression descendingExpression); + + @Override + SqmExpression arraySort(Expression arrayExpression, boolean descending, boolean nullsFirst); + + @Override + SqmExpression arraySort(Expression arrayExpression, Expression descendingExpression, Expression nullsFirstExpression); + @Override SqmExpression arrayFill(Expression elementExpression, Expression elementCountExpression); @@ -498,6 +517,32 @@ default SqmPredicate arrayOverlapsNullable(T[] array1, Expression array @Override > SqmExpression collectionTrim(Expression arrayExpression, Integer elementCount); + @Override + > SqmExpression collectionReverse(Expression collectionExpression); + + @Override + > SqmExpression collectionSort(Expression collectionExpression); + + @Override + > SqmExpression collectionSort(Expression collectionExpression, boolean descending); + + @Override + > SqmExpression collectionSort( + Expression collectionExpression, + Expression descendingExpression); + + @Override + > SqmExpression collectionSort( + Expression collectionExpression, + boolean descending, + boolean nullsFirst); + + @Override + > SqmExpression collectionSort( + Expression collectionExpression, + Expression descendingExpression, + Expression nullsFirstExpression); + @Override SqmExpression> collectionFill(Expression elementExpression, Expression elementCountExpression); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmPathSource.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmPathSource.java index b6634190173f..8416108acfa8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmPathSource.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmPathSource.java @@ -88,7 +88,7 @@ default SqmPathSource getSubPathSource(String name) { * @throws IllegalArgumentException if the subPathSource is not found */ default SqmPathSource getSubPathSource(String name, boolean subtypes) { - final SqmPathSource subPathSource = findSubPathSource( name, subtypes ); + final var subPathSource = findSubPathSource( name, subtypes ); if ( subPathSource == null ) { throw new PathElementException( String.format( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmAggregateFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmAggregateFunction.java index 0465d242a535..87426e00faa3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmAggregateFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmAggregateFunction.java @@ -47,7 +47,7 @@ public SelfRenderingSqmAggregateFunction( @Override public SelfRenderingSqmAggregateFunction copy(SqmCopyContext context) { - final SelfRenderingSqmAggregateFunction existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java index ea201483a9c3..16a0720ded37 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java @@ -59,7 +59,7 @@ public SelfRenderingSqmFunction( @Override public SelfRenderingSqmFunction copy(SqmCopyContext context) { - final SelfRenderingSqmFunction existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmOrderedSetAggregateFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmOrderedSetAggregateFunction.java index 0853d952cc82..f566f16cbd9d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmOrderedSetAggregateFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmOrderedSetAggregateFunction.java @@ -63,7 +63,7 @@ public SelfRenderingSqmOrderedSetAggregateFunction( @Override public SelfRenderingSqmOrderedSetAggregateFunction copy(SqmCopyContext context) { - final SelfRenderingSqmOrderedSetAggregateFunction existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmSetReturningFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmSetReturningFunction.java index 07050852dc99..c1fcae0e7fe3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmSetReturningFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmSetReturningFunction.java @@ -63,7 +63,7 @@ public SelfRenderingSqmSetReturningFunction( @Override public SelfRenderingSqmSetReturningFunction copy(SqmCopyContext context) { - final SelfRenderingSqmSetReturningFunction existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmWindowFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmWindowFunction.java index 242fa87c2e7d..3bddb3a703ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmWindowFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmWindowFunction.java @@ -53,7 +53,7 @@ public SelfRenderingSqmWindowFunction( @Override public SelfRenderingSqmWindowFunction copy(SqmCopyContext context) { - final SelfRenderingSqmWindowFunction existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java index 520cbf830274..1a9bfa01965d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java @@ -9,8 +9,8 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; +import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.expression.ValueBindJpaCriteriaParameter; -import org.hibernate.type.BindableType; import org.hibernate.query.KeyedPage; import org.hibernate.query.KeyedResultList; import org.hibernate.query.Page; @@ -20,7 +20,6 @@ import org.hibernate.query.spi.HqlInterpretation; import org.hibernate.query.spi.MutableQueryOptions; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterBinding; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.SelectQueryPlan; import org.hibernate.query.sqm.spi.NamedSqmQueryMemento; @@ -98,65 +97,68 @@ protected void errorOrLogForPaginationWithCollectionFetch() { } public abstract SqmStatement getSqmStatement(); - protected abstract void setSqmStatement(SqmSelectStatement statement); +// protected abstract void setSqmStatement(SqmSelectStatement statement); public abstract DomainParameterXref getDomainParameterXref(); public abstract TupleMetadata getTupleMetadata(); - - protected void copyParameterBindings(QueryParameterBindings oldParameterBindings) { - final var parameterBindings = getQueryParameterBindings(); - oldParameterBindings.visitBindings( (queryParameter, binding) -> { - if ( binding.isBound() ) { - //noinspection unchecked - final var newBinding = (QueryParameterBinding) parameterBindings.getBinding( queryParameter ); - //noinspection unchecked - final var bindType = (BindableType) binding.getBindType(); - final var explicitTemporalPrecision = binding.getExplicitTemporalPrecision(); - if ( binding.isMultiValued() ) { - final var bindValues = binding.getBindValues(); - if ( explicitTemporalPrecision != null ) { - newBinding.setBindValues( bindValues, explicitTemporalPrecision, getTypeConfiguration() ); - } - else if ( bindType != null ) { - newBinding.setBindValues( bindValues, bindType ); - } - else { - newBinding.setBindValues( bindValues ); - } - } - else { - final var bindValue = binding.getBindValue(); - if ( explicitTemporalPrecision != null ) { - newBinding.setBindValue( bindValue, explicitTemporalPrecision ); - } - else if ( bindType != null ) { - newBinding.setBindValue( bindValue, bindType ); - } - else { - newBinding.setBindValue( bindValue ); - } - } - } - } ); - - // Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here - bindValueBindCriteriaParameters( getDomainParameterXref(), parameterBindings ); - } +// +// protected void copyParameterBindings(QueryParameterBindings oldParameterBindings) { +// final var parameterBindings = getQueryParameterBindings(); +// oldParameterBindings.visitBindings( (queryParameter, binding) -> { +// if ( binding.isBound() ) { +// final var newBinding = parameterBindings.getBinding( queryParameter ); +// final var bindType = (BindableType) binding.getBindType(); +// final var explicitTemporalPrecision = binding.getExplicitTemporalPrecision(); +// if ( binding.isMultiValued() ) { +// final var bindValues = binding.getBindValues(); +// if ( explicitTemporalPrecision != null ) { +// newBinding.setBindValues( bindValues, explicitTemporalPrecision, getTypeConfiguration() ); +// } +// else if ( bindType != null ) { +// newBinding.setBindValues( bindValues, bindType ); +// } +// else { +// newBinding.setBindValues( bindValues ); +// } +// } +// else { +// final var bindValue = binding.getBindValue(); +// if ( explicitTemporalPrecision != null ) { +// newBinding.setBindValue( bindValue, explicitTemporalPrecision ); +// } +// else if ( bindType != null ) { +// newBinding.setBindValue( bindValue, bindType ); +// } +// else { +// newBinding.setBindValue( bindValue ); +// } +// } +// } +// } ); +// +// // Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here +// bindValueBindCriteriaParameters( getDomainParameterXref(), parameterBindings ); +// } protected static void bindValueBindCriteriaParameters( DomainParameterXref domainParameterXref, QueryParameterBindings bindings) { for ( var entry : domainParameterXref.getQueryParameters().entrySet() ) { - final var sqmParameter = entry.getValue().get( 0 ); - if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper wrapper ) { - @SuppressWarnings("unchecked") - final var criteriaParameter = (JpaCriteriaParameter) wrapper.getJpaCriteriaParameter(); - if ( criteriaParameter instanceof ValueBindJpaCriteriaParameter ) { - // Use the anticipated type for binding the value if possible - //noinspection unchecked - final var parameter = (QueryParameterImplementor) entry.getKey(); - bindings.getBinding( parameter ) - .setBindValue( criteriaParameter.getValue(), criteriaParameter.getAnticipatedType() ); - } + bindValueToCriteriaParameter( bindings, entry.getKey(), + entry.getValue().get( 0 ) ); + } + } + + private static void bindValueToCriteriaParameter( + QueryParameterBindings bindings, + QueryParameterImplementor queryParameterImplementor, + SqmParameter sqmParameter) { + if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper wrapper ) { + final var criteriaParameter = wrapper.getJpaCriteriaParameter(); + if ( criteriaParameter instanceof ValueBindJpaCriteriaParameter ) { + // Use the anticipated type for binding the value if possible + bindings.getBinding( queryParameterImplementor ) + .setBindValue( criteriaParameter.getValue(), + criteriaParameter.getAnticipatedType() ); } } } @@ -164,11 +166,14 @@ protected static void bindValueBindCriteriaParameters( @Override protected

QueryParameterImplementor

getQueryParameter(QueryParameterImplementor

parameter) { if ( parameter instanceof JpaCriteriaParameter criteriaParameter ) { - final var parameterWrapper = getDomainParameterXref().getParameterResolutions() - .getJpaCriteriaParamResolutions() - .get( criteriaParameter ); + final var parameterWrapper = + getDomainParameterXref().getParameterResolutions() + .getJpaCriteriaParamResolutions() + .get( criteriaParameter ); //noinspection unchecked - return (QueryParameterImplementor

) getDomainParameterXref().getQueryParameter( parameterWrapper ); + return (QueryParameterImplementor

) + getDomainParameterXref() + .getQueryParameter( parameterWrapper ); } else { return parameter; @@ -176,9 +181,9 @@ protected

QueryParameterImplementor

getQueryParameter(QueryParameterImple } public int @Nullable [] unnamedParameterIndices() { - final var domainParameterXref = getDomainParameterXref(); final var jpaCriteriaParamResolutions = - domainParameterXref.getParameterResolutions().getJpaCriteriaParamResolutions(); + getDomainParameterXref().getParameterResolutions() + .getJpaCriteriaParamResolutions(); if ( jpaCriteriaParamResolutions.isEmpty() ) { return null; } @@ -188,7 +193,8 @@ protected

QueryParameterImplementor

getQueryParameter(QueryParameterImple } final var unnamedParameterIndices = new int[maxId + 1]; for ( var entry : jpaCriteriaParamResolutions.entrySet() ) { - unnamedParameterIndices[entry.getValue().getCriteriaParameterId()] = entry.getValue().getUnnamedParameterId(); + unnamedParameterIndices[entry.getValue().getCriteriaParameterId()] = + entry.getValue().getUnnamedParameterId(); } return unnamedParameterIndices; } @@ -353,12 +359,14 @@ private TupleMetadata getTupleMetadata(List> selections) { } private static TupleElement[] buildTupleElementArray(List> selections) { - if ( selections.size() == 1 ) { + final int selectionsSize = selections.size(); + if ( selectionsSize == 1 ) { final var selectableNode = selections.get( 0 ).getSelectableNode(); if ( selectableNode instanceof CompoundSelection ) { final var selectionItems = selectableNode.getSelectionItems(); - final var elements = new TupleElement[ selectionItems.size() ]; - for ( int i = 0; i < selectionItems.size(); i++ ) { + final int itemsSize = selectionItems.size(); + final var elements = new TupleElement[itemsSize]; + for ( int i = 0; i < itemsSize; i++ ) { elements[i] = selectionItems.get( i ); } return elements; @@ -368,8 +376,8 @@ private static TupleElement[] buildTupleElementArray(List> se } } else { - final var elements = new TupleElement[ selections.size() ]; - for ( int i = 0; i < selections.size(); i++ ) { + final var elements = new TupleElement[selectionsSize]; + for ( int i = 0; i < selectionsSize; i++ ) { elements[i] = selections.get( i ).getSelectableNode(); } return elements; @@ -377,12 +385,14 @@ private static TupleElement[] buildTupleElementArray(List> se } private static String[] buildTupleAliasArray(List> selections) { - if ( selections.size() == 1 ) { + final int selectionsSize = selections.size(); + if ( selectionsSize == 1 ) { final var selectableNode = selections.get(0).getSelectableNode(); if ( selectableNode instanceof CompoundSelection ) { final var selectionItems = selectableNode.getSelectionItems(); - final String[] elements = new String[ selectionItems.size() ]; - for ( int i = 0; i < selectionItems.size(); i++ ) { + final int itemsSize = selectionItems.size(); + final var elements = new String[itemsSize]; + for ( int i = 0; i < itemsSize; i++ ) { elements[i] = selectionItems.get( i ).getAlias(); } return elements; @@ -392,8 +402,8 @@ private static String[] buildTupleAliasArray(List> selections) { } } else { - final String[] elements = new String[ selections.size() ]; - for ( int i = 0; i < selections.size(); i++ ) { + final String[] elements = new String[selectionsSize]; + for ( int i = 0; i < selectionsSize; i++ ) { elements[i] = selections.get( i ).getAlias(); } return elements; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/DomainParameterXref.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/DomainParameterXref.java index fb8b74af46b8..b9d9cbd5b22c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/DomainParameterXref.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/DomainParameterXref.java @@ -4,12 +4,7 @@ */ package org.hibernate.query.sqm.internal; -import java.util.ArrayList; -import java.util.IdentityHashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - +import org.checkerframework.checker.nullness.qual.NonNull; import org.hibernate.query.internal.QueryParameterIdentifiedImpl; import org.hibernate.query.internal.QueryParameterNamedImpl; import org.hibernate.query.internal.QueryParameterPositionalImpl; @@ -20,6 +15,13 @@ import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.type.BasicCollectionType; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + import static java.util.Collections.emptyList; /** @@ -29,67 +31,19 @@ */ public class DomainParameterXref { - public static final DomainParameterXref EMPTY = new DomainParameterXref( - new LinkedHashMap<>( 0 ), - new IdentityHashMap<>( 0 ), - SqmStatement.ParameterResolutions.empty() - ); + public static final DomainParameterXref EMPTY = new DomainParameterXref(); /** * Create a DomainParameterXref for the parameters defined in the SQM statement */ public static DomainParameterXref from(SqmStatement sqmStatement) { - final SqmStatement.ParameterResolutions parameterResolutions = sqmStatement.resolveParameters(); - if ( parameterResolutions.getSqmParameters().isEmpty() ) { - return EMPTY; - } - else { - final int sqmParamCount = parameterResolutions.getSqmParameters().size(); - final LinkedHashMap, List>> sqmParamsByQueryParam = - new LinkedHashMap<>( sqmParamCount ); - final IdentityHashMap, QueryParameterImplementor> queryParamBySqmParam = - new IdentityHashMap<>( sqmParamCount ); - - for ( SqmParameter sqmParameter : parameterResolutions.getSqmParameters() ) { - if ( sqmParameter instanceof JpaCriteriaParameter ) { - // see discussion on `SqmJpaCriteriaParameterWrapper#accept` - throw new UnsupportedOperationException( - "Unexpected JpaCriteriaParameter in SqmStatement#getSqmParameters. Criteria parameters " + - "should be represented as SqmJpaCriteriaParameterWrapper references in this collection" - ); - } - - final QueryParameterImplementor queryParameter; - if ( sqmParameter.getName() != null ) { - queryParameter = QueryParameterNamedImpl.fromSqm( sqmParameter ); - } - else if ( sqmParameter.getPosition() != null ) { - queryParameter = QueryParameterPositionalImpl.fromSqm( sqmParameter ); - } - else if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper criteriaParameter ) { - if ( sqmParameter.allowMultiValuedBinding() - && sqmParameter.getExpressible() != null - && sqmParameter.getExpressible().getSqmType() instanceof BasicCollectionType ) { - // The wrapper parameter was inferred to be of a basic collection type, - // so we disallow multivalued bindings, because binding a list of collections isn't useful - criteriaParameter.getJpaCriteriaParameter().disallowMultiValuedBinding(); - } - queryParameter = QueryParameterIdentifiedImpl.fromSqm( criteriaParameter ); - } - else { - throw new UnsupportedOperationException( - "Unexpected SqmParameter type : " + sqmParameter ); - } - - sqmParamsByQueryParam.computeIfAbsent( queryParameter, impl -> new ArrayList<>() ).add( sqmParameter ); - queryParamBySqmParam.put( sqmParameter, queryParameter ); - } - - return new DomainParameterXref( sqmParamsByQueryParam, queryParamBySqmParam, parameterResolutions ); - } + final var parameterResolutions = sqmStatement.resolveParameters(); + final var parameters = parameterResolutions.getSqmParameters(); + return parameters.isEmpty() + ? EMPTY + : new DomainParameterXref( parameterResolutions, parameters ); } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Instance state @@ -100,28 +54,78 @@ else if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper criteriaPara private Map,List>> expansions; - private DomainParameterXref( - LinkedHashMap, List>> sqmParamsByQueryParam, - IdentityHashMap, QueryParameterImplementor> queryParamBySqmParam, - SqmStatement.ParameterResolutions parameterResolutions) { - this.sqmParamsByQueryParam = sqmParamsByQueryParam; - this.queryParamBySqmParam = queryParamBySqmParam; - this.parameterResolutions = parameterResolutions; + private DomainParameterXref() { + sqmParamsByQueryParam = new LinkedHashMap<>( 0 ); + queryParamBySqmParam = new IdentityHashMap<>( 0 ); + parameterResolutions = SqmStatement.ParameterResolutions.empty(); } - public DomainParameterXref copy() { + private DomainParameterXref(DomainParameterXref that) { + sqmParamsByQueryParam = that.sqmParamsByQueryParam; //noinspection unchecked - final var clone = + queryParamBySqmParam = (IdentityHashMap, QueryParameterImplementor>) - queryParamBySqmParam.clone(); - return new DomainParameterXref( sqmParamsByQueryParam, clone, parameterResolutions ); + that.queryParamBySqmParam.clone(); + parameterResolutions = that.parameterResolutions; + } + + private DomainParameterXref( + SqmStatement.ParameterResolutions resolutions, + Set> parameters) { + parameterResolutions = resolutions; + final int sqmParamCount = parameters.size(); + sqmParamsByQueryParam = new LinkedHashMap<>( sqmParamCount ); + queryParamBySqmParam = new IdentityHashMap<>( sqmParamCount ); + + for ( var parameter : parameters ) { + if ( parameter instanceof JpaCriteriaParameter ) { + // see discussion on `SqmJpaCriteriaParameterWrapper#accept` + throw new UnsupportedOperationException( + "Unexpected JpaCriteriaParameter (criteria parameters should be represented as SqmJpaCriteriaParameterWrapper references in this collection)" + ); + } + + final var queryParameter = fromSqm( parameter ); + sqmParamsByQueryParam.computeIfAbsent( queryParameter, impl -> new ArrayList<>() ) + .add( parameter ); + queryParamBySqmParam.put( parameter, queryParameter ); + } + } + + private static @NonNull QueryParameterImplementor fromSqm(SqmParameter sqmParameter) { + if ( sqmParameter.getName() != null ) { + return QueryParameterNamedImpl.fromSqm( sqmParameter ); + } + else if ( sqmParameter.getPosition() != null ) { + return QueryParameterPositionalImpl.fromSqm( sqmParameter ); + } + else if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper criteriaParameter ) { + if ( sqmParameter.allowMultiValuedBinding() ) { + final var expressible = sqmParameter.getExpressible(); + if ( expressible != null && expressible.getSqmType() instanceof BasicCollectionType ) { + // The wrapper parameter was inferred to be of a basic + // collection type, so we disallow multivalued bindings, + // because binding a list of collections isn't useful + criteriaParameter.getJpaCriteriaParameter().disallowMultiValuedBinding(); + } + } + return QueryParameterIdentifiedImpl.fromSqm( criteriaParameter ); + } + else { + throw new UnsupportedOperationException( "Unexpected SqmParameter type: " + sqmParameter ); + } + } + + public DomainParameterXref copy() { + return new DomainParameterXref( this ); } /** * Does this xref contain any parameters? */ public boolean hasParameters() { - return sqmParamsByQueryParam != null && ! sqmParamsByQueryParam.isEmpty(); + return sqmParamsByQueryParam != null + && ! sqmParamsByQueryParam.isEmpty(); } /** @@ -141,13 +145,6 @@ public int getSqmParameterCount() { return queryParamBySqmParam.size(); } -// public int getNumberOfSqmParameters(QueryParameterImplementor queryParameter) { -// final List> sqmParameters = sqmParamsByQueryParam.get( queryParameter ); -// return sqmParameters == null -// ? 0 // this should maybe be an exception instead -// : sqmParameters.size(); -// } - public SqmStatement.ParameterResolutions getParameterResolutions() { return parameterResolutions; } @@ -157,12 +154,9 @@ public List> getSqmParameters(QueryParameterImplementor query } public QueryParameterImplementor getQueryParameter(SqmParameter sqmParameter) { - if ( sqmParameter instanceof QueryParameterImplementor parameterImplementor ) { - return parameterImplementor; - } - else { - return queryParamBySqmParam.get( sqmParameter ); - } + return sqmParameter instanceof QueryParameterImplementor parameterImplementor + ? parameterImplementor + : queryParamBySqmParam.get( sqmParameter ); } public void addExpansion( @@ -182,15 +176,15 @@ public List> getExpansions(SqmParameter sqmParameter) { return emptyList(); } else { - final List> sqmParameters = expansions.get( sqmParameter ); + final var sqmParameters = expansions.get( sqmParameter ); return sqmParameters == null ? emptyList() : sqmParameters; } } public void clearExpansions() { if ( expansions != null ) { - for ( List> expansionList : expansions.values() ) { - for ( SqmParameter expansion : expansionList ) { + for ( var expansionList : expansions.values() ) { + for ( var expansion : expansionList ) { queryParamBySqmParam.remove( expansion ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java index c9678488ca50..9836a47b589c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java @@ -43,7 +43,6 @@ import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityVersionMapping; -import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.JpaMetamodel; import org.hibernate.metamodel.model.domain.PersistentAttribute; import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource; @@ -80,7 +79,6 @@ import org.hibernate.query.sqm.SetOperator; import org.hibernate.query.sqm.SqmBindableType; import org.hibernate.query.sqm.SqmExpressible; -import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.SqmQuerySource; import org.hibernate.query.common.TemporalUnit; import org.hibernate.query.sqm.TrimSpec; @@ -92,7 +90,6 @@ import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver; import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; import org.hibernate.query.sqm.tree.SqmQuery; -import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.cte.SqmCteStatement; import org.hibernate.query.sqm.tree.cte.SqmCteTableColumn; @@ -143,7 +140,6 @@ import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.spi.TypeConfiguration; import jakarta.persistence.Tuple; @@ -168,6 +164,7 @@ import static jakarta.persistence.metamodel.Type.PersistenceType.BASIC; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static org.hibernate.internal.CoreMessageLogger.CORE_LOGGER; import static org.hibernate.internal.util.collections.CollectionHelper.determineProperSizing; import static org.hibernate.query.internal.QueryHelper.highestPrecedenceType; @@ -181,6 +178,7 @@ * using SQM nodes as the JPA Criteria nodes * * @author Steve Ebersole + * @author Yoobin Yoon */ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable { @@ -218,7 +216,7 @@ public SqmCriteriaNodeBuilder( private Map, HibernateCriteriaBuilder> loadExtensions() { // load registered criteria builder extensions final Map, HibernateCriteriaBuilder> extensions = new HashMap<>(); - for ( CriteriaBuilderExtension extension : ServiceLoader.load( CriteriaBuilderExtension.class ) ) { + for ( var extension : ServiceLoader.load( CriteriaBuilderExtension.class ) ) { extensions.put( extension.getRegistrationKey(), extension.extend( this ) ); } return extensions; @@ -267,7 +265,7 @@ public BasicType getBooleanType() { @Override public BasicType getIntegerType() { - final BasicType integerType = this.integerType; + final var integerType = this.integerType; if ( integerType == null ) { return this.integerType = getTypeConfiguration().getBasicTypeRegistry() @@ -278,7 +276,7 @@ public BasicType getIntegerType() { @Override public BasicType getLongType() { - final BasicType longType = this.longType; + final var longType = this.longType; if ( longType == null ) { return this.longType = getTypeConfiguration().getBasicTypeRegistry() @@ -289,7 +287,7 @@ public BasicType getLongType() { @Override public BasicType getCharacterType() { - final BasicType characterType = this.characterType; + final var characterType = this.characterType; if ( characterType == null ) { return this.characterType = getTypeConfiguration().getBasicTypeRegistry() @@ -300,7 +298,7 @@ public BasicType getCharacterType() { @Override public BasicType getStringType() { - final BasicType stringType = this.stringType; + final var stringType = this.stringType; if ( stringType == null ) { return this.stringType = getTypeConfiguration().getBasicTypeRegistry() @@ -310,17 +308,19 @@ public BasicType getStringType() { } public FunctionReturnTypeResolver getSumReturnTypeResolver() { - final FunctionReturnTypeResolver resolver = sumReturnTypeResolver; + final var resolver = sumReturnTypeResolver; if ( resolver == null ) { - return this.sumReturnTypeResolver = new SumReturnTypeResolver( getTypeConfiguration() ); + return sumReturnTypeResolver = + new SumReturnTypeResolver( getTypeConfiguration() ); } return resolver; } public FunctionReturnTypeResolver getAvgReturnTypeResolver() { - final FunctionReturnTypeResolver resolver = avgReturnTypeResolver; + final var resolver = avgReturnTypeResolver; if ( resolver == null ) { - return this.avgReturnTypeResolver = new AvgFunction.ReturnTypeResolver( getTypeConfiguration() ); + return avgReturnTypeResolver = + new AvgFunction.ReturnTypeResolver( getTypeConfiguration() ); } return resolver; } @@ -351,10 +351,9 @@ public SqmSelectStatement createQuery(Class resultClass) { @Override public SqmSelectStatement createQuery(String hql, Class resultClass) { - final SqmStatement statement = - queryEngine.getHqlTranslator().translate( hql, resultClass ); - if ( statement instanceof SqmSelectStatement ) { - return new SqmSelectStatement<>( (SqmSelectStatement) statement ); + if ( queryEngine.getHqlTranslator().translate( hql, resultClass ) + instanceof SqmSelectStatement selectStatement ) { + return new SqmSelectStatement<>( selectStatement ); } else { throw new IllegalArgumentException("Not a 'select' statement"); @@ -417,10 +416,16 @@ public JpaCriteriaSelect union(CriteriaSelect left, Criteria if ( left instanceof Subquery ) { assert right instanceof Subquery; //noinspection unchecked - return setOperation( SetOperator.UNION, (Subquery) left, (Subquery) right ); + return setOperation( SetOperator.UNION, + (Subquery) left, + (Subquery) right ); + } + else { + //noinspection unchecked + return setOperation( SetOperator.UNION, + (JpaCriteriaQuery) left, + (JpaCriteriaQuery) right ); } - //noinspection unchecked - return setOperation( SetOperator.UNION, (JpaCriteriaQuery) left, (JpaCriteriaQuery) right ); } @Override @@ -435,8 +440,12 @@ public CriteriaSelect unionAll(CriteriaSelect left, Criteria //noinspection unchecked return setOperation( SetOperator.UNION_ALL, (Subquery) left, (Subquery) right ); } - //noinspection unchecked - return setOperation( SetOperator.UNION_ALL, (JpaCriteriaQuery) left, (JpaCriteriaQuery) right ); + else { + //noinspection unchecked + return setOperation( SetOperator.UNION_ALL, + (JpaCriteriaQuery) left, + (JpaCriteriaQuery) right ); + } } @Override @@ -449,10 +458,16 @@ public CriteriaSelect except(CriteriaSelect left, CriteriaSelect ri if ( left instanceof Subquery ) { assert right instanceof Subquery; //noinspection unchecked - return setOperation( SetOperator.EXCEPT, (Subquery) left, (Subquery) right ); + return setOperation( SetOperator.EXCEPT, + (Subquery) left, + (Subquery) right ); + } + else { + //noinspection unchecked + return setOperation( SetOperator.EXCEPT, + (JpaCriteriaQuery) left, + (JpaCriteriaQuery) right ); } - //noinspection unchecked - return setOperation( SetOperator.EXCEPT, (JpaCriteriaQuery) left, (JpaCriteriaQuery) right ); } @Override @@ -460,10 +475,16 @@ public CriteriaSelect exceptAll(CriteriaSelect left, CriteriaSelect if ( left instanceof Subquery ) { assert right instanceof Subquery; //noinspection unchecked - return setOperation( SetOperator.EXCEPT_ALL, (Subquery) left, (Subquery) right ); + return setOperation( SetOperator.EXCEPT_ALL, + (Subquery) left, + (Subquery) right ); + } + else { + //noinspection unchecked + return setOperation( SetOperator.EXCEPT_ALL, + (JpaCriteriaQuery) left, + (JpaCriteriaQuery) right ); } - //noinspection unchecked - return setOperation( SetOperator.EXCEPT_ALL, (JpaCriteriaQuery) left, (JpaCriteriaQuery) right ); } @Override @@ -476,12 +497,12 @@ private JpaCriteriaQuery setOperation( SetOperator operator, CriteriaQuery criteriaQuery, CriteriaQuery... queries) { - final Class resultType = (Class) criteriaQuery.getResultType(); + final var resultType = (Class) criteriaQuery.getResultType(); final List> queryParts = new ArrayList<>( queries.length + 1 ); final Map> cteStatements = new LinkedHashMap<>(); - final SqmSelectStatement selectStatement = (SqmSelectStatement) criteriaQuery; + final var selectStatement = (SqmSelectStatement) criteriaQuery; collectQueryPartsAndCtes( selectStatement, queryParts, cteStatements ); - for ( CriteriaQuery query : queries ) { + for ( var query : queries ) { if ( query.getResultType() != resultType ) { throw new IllegalArgumentException( "Result type of all operands must match" ); } @@ -501,12 +522,12 @@ private JpaSubQuery setOperation( SetOperator operator, Subquery subquery, Subquery... queries) { - final Class resultType = (Class) subquery.getResultType(); - final SqmQuery parent = (SqmQuery) subquery.getParent(); + final var resultType = (Class) subquery.getResultType(); + final var parent = (SqmQuery) subquery.getParent(); final List> queryParts = new ArrayList<>( queries.length + 1 ); final Map> cteStatements = new LinkedHashMap<>(); collectQueryPartsAndCtes( (SqmSelectQuery) subquery, queryParts, cteStatements ); - for ( Subquery query : queries ) { + for ( var query : queries ) { if ( query.getResultType() != resultType ) { throw new IllegalArgumentException( "Result type of all operands must match" ); } @@ -529,9 +550,9 @@ private void collectQueryPartsAndCtes( List> queryParts, Map> cteStatements) { queryParts.add( query.getQueryPart() ); - for ( SqmCteStatement cteStatement : query.getCteStatements() ) { + for ( var cteStatement : query.getCteStatements() ) { final String name = cteStatement.getCteTable().getCteName(); - final SqmCteStatement old = cteStatements.put( name, cteStatement ); + final var old = cteStatements.put( name, cteStatement ); if ( old != null && old != cteStatement ) { throw new IllegalArgumentException( String.format( "Different CTE with same name [%s] found in different set operands!", name ) @@ -547,7 +568,7 @@ public SqmExpression cast(JpaExpression expression, Class castTa @Override public SqmExpression cast(JpaExpression expression, JpaCastTarget castTarget) { - final SqmCastTarget sqmCastTarget = (SqmCastTarget) castTarget; + final var sqmCastTarget = (SqmCastTarget) castTarget; return getFunctionDescriptor( "cast" ).generateSqmExpression( asList( (SqmTypedNode) expression, sqmCastTarget ), sqmCastTarget.getType(), @@ -570,8 +591,10 @@ public SqmCastTarget castTarget(Class castTargetJavaType, int precisio return castTarget( castTargetJavaType, null, precision, scale ); } - private SqmCastTarget castTarget(Class castTargetJavaType, @Nullable Long length, @Nullable Integer precision, @Nullable Integer scale) { - final BasicType type = getTypeConfiguration().standardBasicTypeForJavaType( castTargetJavaType ); + private SqmCastTarget castTarget( + Class castTargetJavaType, + @Nullable Long length, @Nullable Integer precision, @Nullable Integer scale) { + final var type = getTypeConfiguration().standardBasicTypeForJavaType( castTargetJavaType ); return new SqmCastTarget<>( type, length, precision, scale, this ); } @@ -588,12 +611,13 @@ public final SqmPredicate wrap(Expression... expressions) { if ( expressions.length == 1 ) { return wrap( expressions[0] ); } - - final List predicates = new ArrayList<>( expressions.length ); - for ( Expression expression : expressions ) { - predicates.add( wrap( expression ) ); + else { + final List predicates = new ArrayList<>( expressions.length ); + for ( var expression : expressions ) { + predicates.add( wrap( expression ) ); + } + return new SqmJunctionPredicate( Predicate.BooleanOperator.AND, predicates, this ); } - return new SqmJunctionPredicate( Predicate.BooleanOperator.AND, predicates, this ); } @Override @@ -601,12 +625,13 @@ public SqmPredicate wrap(List> restrictions) { if ( restrictions.size() == 1 ) { return wrap( restrictions.get( 0 ) ); } - - final List predicates = new ArrayList<>( restrictions.size() ); - for ( Expression expression : restrictions ) { - predicates.add( wrap( expression ) ); + else { + final List predicates = new ArrayList<>( restrictions.size() ); + for ( var expression : restrictions ) { + predicates.add( wrap( expression ) ); + } + return new SqmJunctionPredicate( Predicate.BooleanOperator.AND, predicates, this ); } - return new SqmJunctionPredicate( Predicate.BooleanOperator.AND, predicates, this ); } @Override @SuppressWarnings("unchecked") @@ -620,8 +645,8 @@ public T unwrap(Class clazz) { @Override public SqmPath fk(Path path) { - final SqmPath sqmPath = (SqmPath) path; - final SqmPathSource toOneReference = sqmPath.getReferencedPathSource(); + final var sqmPath = (SqmPath) path; + final var toOneReference = sqmPath.getReferencedPathSource(); final boolean validToOneRef = toOneReference.getBindableType() == Bindable.BindableType.SINGULAR_ATTRIBUTE && toOneReference instanceof EntitySqmPathSource; @@ -685,9 +710,10 @@ private static JpaCriteriaQuery createUnionSet( SetOperator operator, CriteriaQuery left, CriteriaQuery right) { - assert operator == SetOperator.UNION || operator == SetOperator.UNION_ALL; - final SqmSelectStatement leftSqm = (SqmSelectStatement) left; - final SqmSelectStatement rightSqm = (SqmSelectStatement) right; + assert operator == SetOperator.UNION + || operator == SetOperator.UNION_ALL; + final var leftSqm = (SqmSelectStatement) left; + final var rightSqm = (SqmSelectStatement) right; // SqmQueryGroup is the UNION ALL between the two final SqmQueryGroup sqmQueryGroup = new SqmQueryGroup( @@ -720,9 +746,10 @@ private static JpaCriteriaQuery createIntersectSet( SetOperator operator, CriteriaQuery left, CriteriaQuery right) { - assert operator == SetOperator.INTERSECT || operator == SetOperator.INTERSECT_ALL; - final SqmSelectStatement leftSqm = (SqmSelectStatement) left; - final SqmSelectStatement rightSqm = (SqmSelectStatement) right; + assert operator == SetOperator.INTERSECT + || operator == SetOperator.INTERSECT_ALL; + final var leftSqm = (SqmSelectStatement) left; + final var rightSqm = (SqmSelectStatement) right; // SqmQueryGroup is the UNION ALL between the two final SqmQueryGroup sqmQueryGroup = new SqmQueryGroup( @@ -755,9 +782,10 @@ private static JpaCriteriaQuery createExceptSet( SetOperator operator, CriteriaQuery left, CriteriaQuery right) { - assert operator == SetOperator.EXCEPT || operator == SetOperator.EXCEPT_ALL; - final SqmSelectStatement leftSqm = (SqmSelectStatement) left; - final SqmSelectStatement rightSqm = (SqmSelectStatement) right; + assert operator == SetOperator.EXCEPT + || operator == SetOperator.EXCEPT_ALL; + final var leftSqm = (SqmSelectStatement) left; + final var rightSqm = (SqmSelectStatement) right; // SqmQueryGroup is the UNION ALL between the two final SqmQueryGroup sqmQueryGroup = new SqmQueryGroup( @@ -933,7 +961,7 @@ public SqmTuple tuple(SqmExpressible tupleType, SqmExpression... ex @Override @Deprecated(since = "7", forRemoval = true) public SqmTuple tuple(Class tupleType, List> expressions) { @SuppressWarnings("unchecked") - final SqmExpressible expressibleType = + final var expressibleType = tupleType == null || tupleType == Object[].class ? (SqmDomainType) getTypeConfiguration().resolveTupleType( expressions ) : (SqmEmbeddableDomainType) getDomainModel().embeddable( tupleType ); @@ -1019,7 +1047,7 @@ else if ( Map.class.equals( resultClass ) ) { */ private void checkMultiselect(List> selections) { final HashSet aliases = new HashSet<>( determineProperSizing( selections.size() ) ); - for ( Selection selection : selections ) { + for ( var selection : selections ) { if ( selection.isCompoundSelection() ) { final Class javaType = selection.getJavaType(); if ( javaType.isArray() ) { @@ -1052,7 +1080,7 @@ public SqmExpression avg(Expression argument) { @Override @SuppressWarnings("unchecked") public SqmExpression sum(Expression argument) { - final SqmTypedNode typedNode = (SqmTypedNode) argument; + final var typedNode = (SqmTypedNode) argument; return getFunctionDescriptor( "sum" ).generateSqmExpression( typedNode, (ReturnableType) typedNode.getExpressible().getSqmType(), @@ -1218,10 +1246,9 @@ public JpaExpression truncate(Expression x, Integer n) @Override public SqmExpression neg(Expression x) { - final SqmExpression sqmExpression = (SqmExpression) x; return new SqmUnaryOperation<>( UnaryArithmeticOperator.UNARY_MINUS, - sqmExpression, + (SqmExpression) x, getNodeBuilder() ); } @@ -1355,7 +1382,7 @@ private SqmExpression createSqmArithmeticNode( BinaryArithmeticOperator operator, SqmExpression leftHandExpression, SqmExpression rightHandExpression) { - final SqmExpressible arithmeticType = + final var arithmeticType = getTypeConfiguration() .resolveArithmeticType( leftHandExpression.getNodeType(), @@ -1550,17 +1577,16 @@ public SqmLiteral literal(@Nullable T value, @Nullable SqmExpression SqmLiteral createLiteral(T value, SqmBindableType expressible) { - if ( expressible.getExpressibleJavaType().isInstance( value ) ) { + final var javaType = expressible.getExpressibleJavaType(); + if ( javaType.isInstance( value ) ) { return new SqmLiteral<>( value, expressible, this ); } else { // Just like in HQL, we allow coercion of literal values to the inferred type - final T coercedValue = - expressible.getExpressibleJavaType() - .coerce( value, this::getTypeConfiguration ); - // ignore typeInferenceSource and fallback the value type - return expressible.getExpressibleJavaType().isInstance( coercedValue ) - ? new SqmLiteral<>( coercedValue, expressible, this ) + final Object coercedValue = javaType.coerce( value ); + // ignore typeInferenceSource and fall back to the value type + return javaType.isInstance( coercedValue ) + ? new SqmLiteral<>( javaType.cast( coercedValue ), expressible, this ) : literal( value ); } } @@ -1579,11 +1605,12 @@ else if ( value == null ) { } private BasicType resolveInferredType(T value) { - final TypeConfiguration typeConfiguration = getTypeConfiguration(); - final Class type = ReflectHelper.getClass( value ); - final BasicType result = typeConfiguration.getBasicTypeForJavaType( type ); + final var typeConfiguration = getTypeConfiguration(); + final var type = ReflectHelper.getClass( value ); + final var result = typeConfiguration.getBasicTypeForJavaType( type ); if ( result == null && value instanceof Enum enumValue ) { - return (BasicType) resolveEnumType( typeConfiguration, enumValue ); + return (BasicType) + resolveEnumType( typeConfiguration, enumValue ); } else { return result; @@ -1591,9 +1618,9 @@ private BasicType resolveInferredType(T value) { } private static > BasicType resolveEnumType(TypeConfiguration configuration, Enum enumValue) { - final EnumJavaType javaType = new EnumJavaType<>( ReflectHelper.getClass( enumValue ) ); - final JdbcType jdbcType = javaType.getRecommendedJdbcType( configuration.getCurrentBaseSqlTypeIndicators() ); - return configuration.getBasicTypeRegistry().resolve( javaType, jdbcType ); + final var enumJavaType = new EnumJavaType<>( ReflectHelper.getClass( enumValue ) ); + final var jdbcType = enumJavaType.getRecommendedJdbcType( configuration.getCurrentBaseSqlTypeIndicators() ); + return configuration.getBasicTypeRegistry().resolve( enumJavaType, jdbcType ); } @Override @@ -1617,27 +1644,29 @@ public MappingMetamodelImplementor getMappingMetamodel() { @Override public List> literals(T[] values) { if ( values == null || values.length == 0 ) { - return Collections.emptyList(); + return emptyList(); } - - final List> literals = new ArrayList<>(); - for ( T value : values ) { - literals.add( literal( value ) ); + else { + final List> literals = new ArrayList<>(); + for ( T value : values ) { + literals.add( literal( value ) ); + } + return literals; } - return literals; } @Override public List> literals(List values) { if ( values == null || values.isEmpty() ) { - return Collections.emptyList(); + return emptyList(); } - - final List> literals = new ArrayList<>(); - for ( T value : values ) { - literals.add( literal( value ) ); + else { + final List> literals = new ArrayList<>(); + for ( T value : values ) { + literals.add( literal( value ) ); + } + return literals; } - return literals; } @Override @@ -1647,9 +1676,10 @@ public SqmExpression nullLiteral(Class resultClass) { return new SqmLiteralNull<>( this ); } else { - final BasicType basicTypeForJavaType = getTypeConfiguration().getBasicTypeForJavaType( resultClass ); + final var basicTypeForJavaType = + getTypeConfiguration().getBasicTypeForJavaType( resultClass ); // if there's no basic type, it might be an entity type - final SqmBindableType sqmExpressible = + final var sqmExpressible = basicTypeForJavaType == null ? resolveExpressible( getDomainModel().managedType( resultClass ) ) : basicTypeForJavaType; @@ -1692,9 +1722,9 @@ public JpaCriteriaParameter parameter(Class paramClass) { @Override public JpaCriteriaParameter parameter(Class paramClass, @Nullable String name) { - final BasicType basicType = getTypeConfiguration().getBasicTypeForJavaType( paramClass ); - boolean notBasic = basicType == null; - final BindableType parameterType = + final var basicType = getTypeConfiguration().getBasicTypeForJavaType( paramClass ); + final boolean notBasic = basicType == null; + final var parameterType = notBasic && Collection.class.isAssignableFrom( paramClass ) // a Collection-valued, multi-valued parameter ? new MultiValueParameterType<>( (Class) Collection.class ) @@ -1709,7 +1739,7 @@ public JpaParameterExpression> listParameter(Class paramClass) { @Override public JpaParameterExpression> listParameter(Class paramClass, @Nullable String name) { - final BindableType> parameterType = new MultiValueParameterType<>( (Class>) (Class) List.class ); + final var parameterType = new MultiValueParameterType<>( (Class>) (Class) List.class ); return new JpaCriteriaParameter<>( name, parameterType, true, this ); } @@ -1725,10 +1755,8 @@ public SqmExpression concat(List> expressions) { @Override public SqmExpression concat(Expression x, Expression y) { - final SqmExpression xSqmExpression = (SqmExpression) x; - final SqmExpression ySqmExpression = (SqmExpression) y; return getFunctionDescriptor( "concat" ).generateSqmExpression( - asList( xSqmExpression, ySqmExpression ), + asList( (SqmExpression) x, (SqmExpression) y ), null, getQueryEngine() ); @@ -1736,11 +1764,8 @@ public SqmExpression concat(Expression x, Expression y) @Override public SqmExpression concat(Expression x, String y) { - final SqmExpression xSqmExpression = (SqmExpression) x; - final SqmExpression ySqmExpression = value( y, xSqmExpression ); - return getFunctionDescriptor( "concat" ).generateSqmExpression( - asList( xSqmExpression, ySqmExpression ), + asList( (SqmExpression) x, value( y, (SqmExpression) x ) ), null, getQueryEngine() ); @@ -1748,11 +1773,8 @@ public SqmExpression concat(Expression x, String y) { @Override public SqmExpression concat(String x, Expression y) { - final SqmExpression ySqmExpression = (SqmExpression) y; - final SqmExpression xSqmExpression = value( x, ySqmExpression ); - return getFunctionDescriptor( "concat" ).generateSqmExpression( - asList( xSqmExpression, ySqmExpression ), + asList( value( x, (SqmExpression) y ), (SqmExpression) y ), null, getQueryEngine() ); @@ -1760,11 +1782,8 @@ public SqmExpression concat(String x, Expression y) { @Override public SqmExpression concat(String x, String y) { - final SqmExpression xSqmExpression = value( x ); - final SqmExpression ySqmExpression = value( y, xSqmExpression ); - return getFunctionDescriptor( "concat" ).generateSqmExpression( - asList( xSqmExpression, ySqmExpression ), + asList( value( x ), value( y, value( x ) ) ), null, getQueryEngine() ); @@ -1913,23 +1932,10 @@ private SqmFunction createLocateFunctionNode( SqmExpression source, SqmExpression pattern, @Nullable SqmExpression startPosition) { - final List> arguments; - if ( startPosition == null ) { - arguments = asList( - pattern, - source - ); - } - else { - arguments = asList( - pattern, - source, - startPosition - ); - } - return getFunctionDescriptor("locate").generateSqmExpression( - arguments, + startPosition == null + ? asList( pattern, source ) + : asList( pattern, source, startPosition ), null, getQueryEngine() ); @@ -2039,7 +2045,7 @@ public SqmPath version(Path path) { @Override public SqmFunction function(String name, Class type, Expression[] args) { - final BasicType resultType = getTypeConfiguration().standardBasicTypeForJavaType( type ); + final var resultType = getTypeConfiguration().standardBasicTypeForJavaType( type ); return getFunctionTemplate( name, resultType ).generateSqmExpression( expressionList( args ), resultType, @@ -2048,7 +2054,7 @@ public SqmFunction function(String name, Class type, Expression[] a } private SqmFunctionDescriptor getFunctionTemplate(String name, BasicType resultType) { - final SqmFunctionDescriptor functionTemplate = getFunctionDescriptor( name ); + final var functionTemplate = getFunctionDescriptor( name ); if ( functionTemplate == null ) { return new NamedSqmFunctionDescriptor( name, @@ -2065,14 +2071,15 @@ private SqmFunctionDescriptor getFunctionTemplate(String name, BasicType private static List> expressionList(Expression[] jpaExpressions) { if ( jpaExpressions == null || jpaExpressions.length == 0 ) { - return Collections.emptyList(); + return emptyList(); } - - final ArrayList> sqmExpressions = new ArrayList<>(); - for ( Expression jpaExpression : jpaExpressions ) { - sqmExpressions.add( (SqmExpression) jpaExpression ); + else { + final ArrayList> sqmExpressions = new ArrayList<>(); + for ( var jpaExpression : jpaExpressions ) { + sqmExpressions.add( (SqmExpression) jpaExpression ); + } + return sqmExpressions; } - return sqmExpressions; } @Override @@ -2127,8 +2134,11 @@ public SqmExpression value(@Nullable T value, @Nullable SqmExpression SqmExpression> collectionValue(Collection value, SqmExpression typeInferenceSource) { - return inlineValue( value ) ? collectionLiteral( value.toArray() ) : collectionValueParameter( value, typeInferenceSource ); + private SqmExpression> collectionValue( + Collection value, SqmExpression typeInferenceSource) { + return inlineValue( value ) + ? collectionLiteral( value.toArray() ) + : collectionValueParameter( value, typeInferenceSource ); } @Override @@ -2166,15 +2176,16 @@ else if ( bindableType instanceof SqmExpressible expressible ) { @Nullable X value, @Nullable SqmExpression typeInferenceSource, TypeConfiguration typeConfiguration) { - if ( typeInferenceSource != null ) { if ( typeInferenceSource instanceof BindableType ) { //noinspection unchecked return (BindableType) typeInferenceSource; } - final SqmBindableType nodeType = typeInferenceSource.getExpressible(); - if ( nodeType != null ) { - return nodeType; + else { + final var nodeType = typeInferenceSource.getExpressible(); + if ( nodeType != null ) { + return nodeType; + } } } @@ -2195,36 +2206,44 @@ private ValueBindJpaCriteriaParameter valueParameter(@Nullable T value, @ final var widerType = (BindableType) bindableType; return new ValueBindJpaCriteriaParameter<>( widerType, value, this ); } - final T coercedValue = - resolveExpressible( bindableType ).getExpressibleJavaType() - .coerce( value, this::getTypeConfiguration ); - // ignore typeInferenceSource and fall back the value type - if ( isInstance( bindableType, coercedValue ) ) { - @SuppressWarnings("unchecked") // safe, we just checked - final var widerType = (BindableType) bindableType; - return new ValueBindJpaCriteriaParameter<>( widerType, coercedValue, this ); + else { + final var javaType = resolveExpressible( bindableType ).getExpressibleJavaType(); + final Object coercedValue = javaType.coerce( value ); + // ignore typeInferenceSource and fall back to the value type + if ( isInstance( bindableType, coercedValue ) ) { + @SuppressWarnings("unchecked") // safe, we just checked + final var widerType = (BindableType) bindableType; + return new ValueBindJpaCriteriaParameter<>( widerType, javaType.cast( coercedValue ), this ); + } + else { + return new ValueBindJpaCriteriaParameter<>( getParameterBindType( value ), value, this ); + } } - return new ValueBindJpaCriteriaParameter<>( getParameterBindType( value ), value, this ); } private ValueBindJpaCriteriaParameter> collectionValueParameter(Collection value, SqmExpression elementTypeInferenceSource) { - BindableType bindableType = null; + final var elementType = + resolveExpressible( bindableType( elementTypeInferenceSource ) ) + .getSqmType(); + if ( elementType == null ) { + throw new UnsupportedOperationException( "Can't infer collection type based on element expression: " + elementTypeInferenceSource ); + } + final var collectionType = DdlTypeHelper.resolveListType( elementType, getTypeConfiguration() ); + //noinspection unchecked + return new ValueBindJpaCriteriaParameter<>( (BasicType>) collectionType, value, this ); + } + + private static BindableType bindableType(SqmExpression elementTypeInferenceSource) { if ( elementTypeInferenceSource != null ) { if ( elementTypeInferenceSource instanceof BindableType ) { //noinspection unchecked - bindableType = (BindableType) elementTypeInferenceSource; + return (BindableType) elementTypeInferenceSource; } else if ( elementTypeInferenceSource.getNodeType() != null ) { - bindableType = elementTypeInferenceSource.getNodeType(); + return elementTypeInferenceSource.getNodeType(); } } - final DomainType elementType = resolveExpressible( bindableType ).getSqmType(); - if ( elementType == null ) { - throw new UnsupportedOperationException( "Can't infer collection type based on element expression: " + elementTypeInferenceSource ); - } - final BasicType collectionType = DdlTypeHelper.resolveListType( elementType, getTypeConfiguration() ); - //noinspection unchecked - return new ValueBindJpaCriteriaParameter<>( (BasicType>) collectionType, value, this ); + return null; } private ValueBindJpaCriteriaParameter valueParameter(T value) { @@ -2287,16 +2306,11 @@ public SqmExpression nullif(Expression x, Y y) { } private SqmExpression createNullifFunctionNode(SqmExpression first, SqmExpression second) { - final SqmBindableType bindableType = - highestPrecedenceType( first.getExpressible(), second.getExpressible() ); + final var bindableType = highestPrecedenceType( first.getExpressible(), second.getExpressible() ); @SuppressWarnings("unchecked") - final ReturnableType resultType = - bindableType == null ? null : (ReturnableType) bindableType.getSqmType(); - return getFunctionDescriptor( "nullif" ).generateSqmExpression( - asList( first, second ), - resultType, - getQueryEngine() - ); + final var resultType = bindableType == null ? null : (ReturnableType) bindableType.getSqmType(); + return getFunctionDescriptor( "nullif" ) + .generateSqmExpression( asList( first, second ), resultType, getQueryEngine() ); } private SqmFunctionDescriptor getFunctionDescriptor(String name) { @@ -2350,12 +2364,13 @@ public SqmPredicate and(Predicate... restrictions) { if ( restrictions == null || restrictions.length == 0 ) { return conjunction(); } - - final List predicates = new ArrayList<>( restrictions.length ); - for ( Predicate expression : restrictions ) { - predicates.add( (SqmPredicate) expression ); + else { + final List predicates = new ArrayList<>( restrictions.length ); + for ( var expression : restrictions ) { + predicates.add( (SqmPredicate) expression ); + } + return new SqmJunctionPredicate( Predicate.BooleanOperator.AND, predicates, this ); } - return new SqmJunctionPredicate( Predicate.BooleanOperator.AND, predicates, this ); } @Override @@ -2363,12 +2378,13 @@ public SqmPredicate and(List restrictions) { if ( restrictions == null || restrictions.isEmpty() ) { return conjunction(); } - - final List predicates = new ArrayList<>( restrictions.size() ); - for ( Predicate expression : restrictions ) { - predicates.add( (SqmPredicate) expression ); + else { + final List predicates = new ArrayList<>( restrictions.size() ); + for ( var expression : restrictions ) { + predicates.add( (SqmPredicate) expression ); + } + return new SqmJunctionPredicate( Predicate.BooleanOperator.AND, predicates, this ); } - return new SqmJunctionPredicate( Predicate.BooleanOperator.AND, predicates, this ); } @Override @@ -2386,12 +2402,13 @@ public SqmPredicate or(Predicate... restrictions) { if ( restrictions == null || restrictions.length == 0 ) { return disjunction(); } - - final List predicates = new ArrayList<>( restrictions.length ); - for ( Predicate expression : restrictions ) { - predicates.add( (SqmPredicate) expression ); + else { + final List predicates = new ArrayList<>( restrictions.length ); + for ( var expression : restrictions ) { + predicates.add( (SqmPredicate) expression ); + } + return new SqmJunctionPredicate( Predicate.BooleanOperator.OR, predicates, this ); } - return new SqmJunctionPredicate( Predicate.BooleanOperator.OR, predicates, this ); } @Override @@ -2399,12 +2416,13 @@ public SqmPredicate or(List restrictions) { if ( restrictions == null || restrictions.isEmpty() ) { return disjunction(); } - - final List predicates = new ArrayList<>( restrictions.size() ); - for ( Predicate expression : restrictions ) { - predicates.add( (SqmPredicate) expression ); + else { + final List predicates = new ArrayList<>( restrictions.size() ); + for ( var expression : restrictions ) { + predicates.add( (SqmPredicate) expression ); + } + return new SqmJunctionPredicate( Predicate.BooleanOperator.OR, predicates, this ); } - return new SqmJunctionPredicate( Predicate.BooleanOperator.OR, predicates, this ); } @Override @@ -2465,13 +2483,11 @@ public > SqmPredicate between(Expression> SqmPredicate between(Expression value, Y lower, Y upper) { - final SqmExpression valueExpression = (SqmExpression) value; - final SqmExpression lowerExpr = value( lower, valueExpression ); - final SqmExpression upperExpr = value( upper, valueExpression ); + final var valueExpression = (SqmExpression) value; return new SqmBetweenPredicate( valueExpression, - lowerExpr, - upperExpr, + value( lower, valueExpression ), + value( upper, valueExpression ), false, this ); @@ -2489,7 +2505,7 @@ public SqmPredicate equal(Expression x, Expression y) { @Override public SqmPredicate equal(Expression x, Object y) { - final SqmExpression yExpr = value( y, (SqmExpression) x ); + final var yExpr = value( y, (SqmExpression) x ); return new SqmComparisonPredicate( (SqmExpression) x, ComparisonOperator.EQUAL, @@ -2510,7 +2526,7 @@ public SqmPredicate notEqual(Expression x, Expression y) { @Override public SqmPredicate notEqual(Expression x, Object y) { - final SqmExpression yExpr = value( y, (SqmExpression) x ); + final var yExpr = value( y, (SqmExpression) x ); return new SqmComparisonPredicate( (SqmExpression) x, ComparisonOperator.NOT_EQUAL, @@ -2531,7 +2547,7 @@ public SqmPredicate distinctFrom(Expression x, Expression y) { @Override public SqmPredicate distinctFrom(Expression x, Object y) { - final SqmExpression yExpr = value( y, (SqmExpression) x ); + final var yExpr = value( y, (SqmExpression) x ); return new SqmComparisonPredicate( (SqmExpression) x, ComparisonOperator.DISTINCT_FROM, @@ -2552,7 +2568,7 @@ public SqmPredicate notDistinctFrom(Expression x, Expression y) { @Override public SqmPredicate notDistinctFrom(Expression x, Object y) { - final SqmExpression yExpr = value( y, (SqmExpression) x ); + final var yExpr = value( y, (SqmExpression) x ); return new SqmComparisonPredicate( (SqmExpression) x, ComparisonOperator.NOT_DISTINCT_FROM, @@ -2573,7 +2589,7 @@ public > SqmPredicate greaterThan(Expression> SqmPredicate greaterThan(Expression x, Y y) { - final SqmExpression yExpr = value( y, (SqmExpression) x ); + final var yExpr = value( y, (SqmExpression) x ); return new SqmComparisonPredicate( (SqmExpression) x, ComparisonOperator.GREATER_THAN, @@ -2594,7 +2610,7 @@ public > SqmPredicate greaterThanOrEqualTo(Expre @Override public > SqmPredicate greaterThanOrEqualTo(Expression x, Y y) { - final SqmExpression yExpr = value( y, (SqmExpression) x ); + final var yExpr = value( y, (SqmExpression) x ); return new SqmComparisonPredicate( (SqmExpression) x, ComparisonOperator.GREATER_THAN_OR_EQUAL, @@ -2615,7 +2631,7 @@ public > SqmPredicate lessThan(Expression> SqmPredicate lessThan(Expression x, Y y) { - final SqmExpression yExpr = value( y, (SqmExpression) x ); + final var yExpr = value( y, (SqmExpression) x ); return new SqmComparisonPredicate( (SqmExpression) x, ComparisonOperator.LESS_THAN, @@ -2636,7 +2652,7 @@ public > SqmPredicate lessThanOrEqualTo(Expressi @Override public > SqmPredicate lessThanOrEqualTo(Expression x, Y y) { - final SqmExpression yExpr = value( y, (SqmExpression) x ); + final var yExpr = value( y, (SqmExpression) x ); return new SqmComparisonPredicate( (SqmExpression) x, ComparisonOperator.LESS_THAN_OR_EQUAL, @@ -2657,7 +2673,7 @@ public SqmPredicate gt(Expression x, Expression x, Number y) { - final SqmExpression yExpr = value( y, (SqmExpression) x ); + final var yExpr = value( y, (SqmExpression) x ); return new SqmComparisonPredicate( (SqmExpression) x, ComparisonOperator.GREATER_THAN, @@ -2678,7 +2694,7 @@ public SqmPredicate ge(Expression x, Expression x, Number y) { - final SqmExpression yExpr = value( y, (SqmExpression) x ); + final var yExpr = value( y, (SqmExpression) x ); return new SqmComparisonPredicate( (SqmExpression) x, ComparisonOperator.GREATER_THAN_OR_EQUAL, @@ -2699,7 +2715,7 @@ public SqmPredicate lt(Expression x, Expression x, Number y) { - final SqmExpression yExpr = value( y, (SqmExpression) x ); + final var yExpr = value( y, (SqmExpression) x ); return new SqmComparisonPredicate( (SqmExpression) x, ComparisonOperator.LESS_THAN, @@ -2720,7 +2736,7 @@ public SqmPredicate le(Expression x, Expression x, Number y) { - final SqmExpression yExpr = value( y, (SqmExpression) x ); + final var yExpr = value( y, (SqmExpression) x ); return new SqmComparisonPredicate( (SqmExpression) x, ComparisonOperator.LESS_THAN_OR_EQUAL, @@ -3029,7 +3045,7 @@ public SqmInPredicate in(Expression expression) { @SuppressWarnings("unchecked") public SqmInPredicate in(Expression expression, Expression... values) { final List> listExpressions = new ArrayList<>( values.length ); - for ( Expression value : values ) { + for ( var value : values ) { listExpressions.add( (SqmExpression) value ); } return new SqmInListPredicate<>( (SqmExpression) expression, listExpressions, this ); @@ -3038,7 +3054,7 @@ public SqmInPredicate in(Expression expression, Expression SqmInPredicate in(Expression expression, T... values) { - final SqmExpression sqmExpression = (SqmExpression) expression; + final var sqmExpression = (SqmExpression) expression; final List> listExpressions = new ArrayList<>( values.length ); for ( T value : values ) { listExpressions.add( value( value, sqmExpression ) ); @@ -3049,7 +3065,7 @@ public SqmInPredicate in(Expression expression, T... values) @Override @SuppressWarnings("unchecked") public SqmInPredicate in(Expression expression, Collection values) { - final SqmExpression sqmExpression = (SqmExpression) expression; + final var sqmExpression = (SqmExpression) expression; final List> listExpressions = new ArrayList<>( values.size() ); for ( T value : values ) { listExpressions.add( value( value, sqmExpression ) ); @@ -3129,7 +3145,7 @@ public SqmFunction sql(String pattern, Class type, Expression... ar @Override public SqmFunction format(Expression datetime, String pattern) { - final SqmFormat sqmFormat = new SqmFormat( pattern, getStringType(), this ); + final var sqmFormat = new SqmFormat( pattern, getStringType(), this ); return getFunctionDescriptor( "format" ).generateSqmExpression( asList( (SqmExpression) datetime, sqmFormat ), null, @@ -3330,9 +3346,9 @@ public SqmFunction overlay( Expression replacement, Expression start, @Nullable Expression length) { - SqmExpression sqmString = (SqmExpression) string; - SqmExpression sqmReplacement = (SqmExpression) replacement; - SqmExpression sqmStart = (SqmExpression) start; + final var sqmString = (SqmExpression) string; + final var sqmReplacement = (SqmExpression) replacement; + final var sqmStart = (SqmExpression) start; return getFunctionDescriptor( "overlay" ).generateSqmExpression( ( length == null ? asList( sqmString, sqmReplacement, sqmStart ) @@ -3403,9 +3419,9 @@ public SqmFunction pad( Expression x, Expression length, @Nullable Expression padChar) { - SqmExpression source = (SqmExpression) x; - SqmExpression sqmLength = (SqmExpression) length; - SqmTrimSpecification padSpec = new SqmTrimSpecification( + final var source = (SqmExpression) x; + final var sqmLength = (SqmExpression) length; + final var padSpec = new SqmTrimSpecification( ts == null ? TrimSpec.TRAILING : fromCriteriaTrimSpec( ts ), this ); @@ -3884,24 +3900,21 @@ public SqmExpression functionWithinGroup( @Nullable JpaPredicate filter, @Nullable JpaWindow window, Expression... args) { - SqmOrderByClause withinGroupClause = new SqmOrderByClause(); + final var withinGroupClause = new SqmOrderByClause(); if ( order != null ) { withinGroupClause.addSortSpecification( (SqmSortSpecification) order ); } - SqmPredicate sqmFilter = filter != null ? (SqmPredicate) filter : null; - SqmExpression function = getFunctionDescriptor( name ).generateOrderedSetAggregateSqmExpression( - expressionList( args ), - sqmFilter, - withinGroupClause, - null, - queryEngine - ); - if ( window == null ) { - return function; - } - else { - return new SqmOver<>( function, (SqmWindow) window ); - } + final var sqmFilter = filter != null ? (SqmPredicate) filter : null; + final SqmExpression function = + getFunctionDescriptor( name ) + .generateOrderedSetAggregateSqmExpression( + expressionList( args ), + sqmFilter, + withinGroupClause, + null, + queryEngine + ); + return window == null ? function : new SqmOver<>( function, (SqmWindow) window ); } @Override @@ -4692,6 +4705,67 @@ public SqmExpression arrayTrim( ); } + @Override + public SqmExpression arrayReverse(Expression arrayExpression) { + return getFunctionDescriptor( "array_reverse" ).generateSqmExpression( + Collections.singletonList( (SqmExpression) arrayExpression ), + null, + queryEngine + ); + } + + @Override + public SqmExpression arraySort(Expression arrayExpression) { + return getFunctionDescriptor( "array_sort" ).generateSqmExpression( + Collections.singletonList( (SqmExpression) arrayExpression ), + null, + queryEngine + ); + } + + @Override + public SqmExpression arraySort(Expression arrayExpression, boolean descending) { + return getFunctionDescriptor( "array_sort" ).generateSqmExpression( + asList( (SqmExpression) arrayExpression, value( descending ) ), + null, + queryEngine + ); + } + + @Override + public SqmExpression arraySort(Expression arrayExpression, Expression descendingExpression) { + return getFunctionDescriptor( "array_sort" ).generateSqmExpression( + asList( (SqmExpression) arrayExpression, (SqmExpression) descendingExpression ), + null, + queryEngine + ); + } + + @Override + public SqmExpression arraySort(Expression arrayExpression, boolean descending, boolean nullsFirst) { + return getFunctionDescriptor( "array_sort" ).generateSqmExpression( + asList( (SqmExpression) arrayExpression, value( descending ), value( nullsFirst ) ), + null, + queryEngine + ); + } + + @Override + public SqmExpression arraySort( + Expression arrayExpression, + Expression descendingExpression, + Expression nullsFirstExpression) { + return getFunctionDescriptor( "array_sort" ).generateSqmExpression( + asList( + (SqmExpression) arrayExpression, + (SqmExpression) descendingExpression, + (SqmExpression) nullsFirstExpression + ), + null, + queryEngine + ); + } + @Override public SqmExpression arrayTrim(Expression arrayExpression, Integer elementCount) { return getFunctionDescriptor( "array_trim" ).generateSqmExpression( @@ -5378,6 +5452,74 @@ public > SqmExpression collectionTrim( ); } + @Override + public > SqmExpression collectionReverse(Expression collectionExpression) { + return getFunctionDescriptor( "array_reverse" ).generateSqmExpression( + Collections.singletonList( (SqmExpression) collectionExpression ), + null, + queryEngine + ); + } + + @Override + public > SqmExpression collectionSort(Expression collectionExpression) { + return getFunctionDescriptor( "array_sort" ).generateSqmExpression( + Collections.singletonList( (SqmExpression) collectionExpression ), + null, + queryEngine + ); + } + + @Override + public > SqmExpression collectionSort( + Expression collectionExpression, + boolean descending) { + return getFunctionDescriptor( "array_sort" ).generateSqmExpression( + asList( (SqmExpression) collectionExpression, value( descending ) ), + null, + queryEngine + ); + } + + @Override + public > SqmExpression collectionSort( + Expression collectionExpression, + Expression descendingExpression) { + return getFunctionDescriptor( "array_sort" ).generateSqmExpression( + asList( (SqmExpression) collectionExpression, (SqmExpression) descendingExpression ), + null, + queryEngine + ); + } + + @Override + public > SqmExpression collectionSort( + Expression collectionExpression, + boolean descending, + boolean nullsFirst) { + return getFunctionDescriptor( "array_sort" ).generateSqmExpression( + asList( (SqmExpression) collectionExpression, value( descending ), value( nullsFirst ) ), + null, + queryEngine + ); + } + + @Override + public > SqmExpression collectionSort( + Expression collectionExpression, + Expression descendingExpression, + Expression nullsFirstExpression) { + return getFunctionDescriptor( "array_sort" ).generateSqmExpression( + asList( + (SqmExpression) collectionExpression, + (SqmExpression) descendingExpression, + (SqmExpression) nullsFirstExpression + ), + null, + queryEngine + ); + } + @Override public SqmExpression> collectionFill( Expression elementExpression, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmQueryImpl.java index 6c93e3fca5e4..80751516bee5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmQueryImpl.java @@ -111,13 +111,13 @@ public class SqmQueryImpl implements SqmQueryImplementor, InterpretationsKeySource, DomainQueryExecutionContext { private final String hql; - private Object queryStringCacheKey; - private SqmStatement sqm; + private final Object queryStringCacheKey; + private final SqmStatement sqm; - private ParameterMetadataImplementor parameterMetadata; - private DomainParameterXref domainParameterXref; + private final ParameterMetadataImplementor parameterMetadata; + private final DomainParameterXref domainParameterXref; - private QueryParameterBindings parameterBindings; + private final QueryParameterBindings parameterBindings; private final Class resultType; private final TupleMetadata tupleMetadata; @@ -260,20 +260,20 @@ public SqmStatement getSqmStatement() { return sqm; } - @Override - protected void setSqmStatement(SqmSelectStatement sqm) { - this.sqm = sqm; - this.queryStringCacheKey = sqm; - - final var oldParameterBindings = parameterBindings; - domainParameterXref = DomainParameterXref.from( sqm ); - parameterMetadata = - domainParameterXref.hasParameters() - ? new ParameterMetadataImpl( domainParameterXref.getQueryParameters() ) - : ParameterMetadataImpl.EMPTY; - parameterBindings = parameterMetadata.createBindings( getSessionFactory() ); - copyParameterBindings( oldParameterBindings ); - } +// @Override +// protected void setSqmStatement(SqmSelectStatement sqm) { +// this.sqm = sqm; +// this.queryStringCacheKey = sqm; +// +// final var oldParameterBindings = parameterBindings; +// domainParameterXref = DomainParameterXref.from( sqm ); +// parameterMetadata = +// domainParameterXref.hasParameters() +// ? new ParameterMetadataImpl( domainParameterXref.getQueryParameters() ) +// : ParameterMetadataImpl.EMPTY; +// parameterBindings = parameterMetadata.createBindings( getSessionFactory() ); +// copyParameterBindings( oldParameterBindings ); +// } @Override public DomainParameterXref getDomainParameterXref() { @@ -516,28 +516,27 @@ protected int doExecuteUpdate() { private NonSelectQueryPlan resolveNonSelectQueryPlan() { // resolve (or make) the QueryPlan. - NonSelectQueryPlan queryPlan = null; - final var cacheKey = generateNonSelectKey( this ); final var interpretationCache = getInterpretationCache(); if ( cacheKey != null ) { - queryPlan = interpretationCache.getNonSelectQueryPlan( cacheKey ); - } - - if ( queryPlan == null ) { - queryPlan = buildNonSelectQueryPlan(); - if ( cacheKey != null ) { - interpretationCache.cacheNonSelectQueryPlan( cacheKey, queryPlan ); + final var queryPlan = interpretationCache.getNonSelectQueryPlan( cacheKey ); + if ( queryPlan != null ) { + return queryPlan; } } + + final var queryPlan = buildNonSelectQueryPlan(); + if ( cacheKey != null ) { + interpretationCache.cacheNonSelectQueryPlan( cacheKey, queryPlan ); + } return queryPlan; } private NonSelectQueryPlan buildNonSelectQueryPlan() { // to get here the SQM statement has already been validated to be // a non-select variety... - final SqmStatement sqmStatement = getSqmStatement(); + final var sqmStatement = getSqmStatement(); if ( sqmStatement instanceof SqmDeleteStatement ) { return buildDeleteQueryPlan(); } @@ -562,11 +561,11 @@ private NonSelectQueryPlan buildDeleteQueryPlan() { private NonSelectQueryPlan buildConcreteDeleteQueryPlan(SqmDeleteStatement deleteStatement) { final var entityDomainType = deleteStatement.getTarget().getModel(); - String entityDomainType1 = entityDomainType.getHibernateEntityName(); - final var persister = getMappingMetamodel().getEntityDescriptor( entityDomainType1 ); + final String entityName = entityDomainType.getHibernateEntityName(); + final var persister = getMappingMetamodel().getEntityDescriptor( entityName ); final var multiTableStrategy = persister.getSqmMultiTableMutationStrategy(); return multiTableStrategy != null - // NOTE : MultiTableDeleteQueryPlan and SqmMultiTableMutationStrategy already handle soft-deletes internally + // NOTE: MultiTableDeleteQueryPlan and SqmMultiTableMutationStrategy already handle soft-deletes internally ? new MultiTableDeleteQueryPlan( deleteStatement, domainParameterXref, multiTableStrategy ) : new SimpleDeleteQueryPlan( persister, deleteStatement, domainParameterXref ); } @@ -581,8 +580,8 @@ private NonSelectQueryPlan buildAggregatedDeleteQueryPlan(SqmDeleteStatement[ private NonSelectQueryPlan buildUpdateQueryPlan() { final var sqmUpdate = (SqmUpdateStatement) getSqmStatement(); - String entityDomainType = sqmUpdate.getTarget().getModel().getHibernateEntityName(); - final var persister = getMappingMetamodel().getEntityDescriptor( entityDomainType ); + final String entityName = sqmUpdate.getTarget().getModel().getHibernateEntityName(); + final var persister = getMappingMetamodel().getEntityDescriptor( entityName ); final var multiTableStrategy = persister.getSqmMultiTableMutationStrategy(); return multiTableStrategy == null ? new SimpleNonSelectQueryPlan( sqmUpdate, domainParameterXref ) @@ -591,8 +590,8 @@ private NonSelectQueryPlan buildUpdateQueryPlan() { private NonSelectQueryPlan buildInsertQueryPlan() { final var sqmInsert = (SqmInsertStatement) getSqmStatement(); - String entityDomainType = sqmInsert.getTarget().getModel().getHibernateEntityName(); - final var persister = getMappingMetamodel().getEntityDescriptor( entityDomainType ); + final String entityName = sqmInsert.getTarget().getModel().getHibernateEntityName(); + final var persister = getMappingMetamodel().getEntityDescriptor( entityName ); if ( useMultiTableInsert( persister, sqmInsert ) ) { return new MultiTableInsertQueryPlan( sqmInsert, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java index 073421682a0b..a01366873ae1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java @@ -41,7 +41,6 @@ import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl; import org.hibernate.query.internal.DelegatingDomainQueryExecutionContext; import org.hibernate.query.internal.ParameterMetadataImpl; -import org.hibernate.type.BindableType; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.spi.HqlInterpretation; import org.hibernate.query.spi.ParameterMetadataImplementor; @@ -85,12 +84,12 @@ public class SqmSelectionQueryImpl extends AbstractSqmSelectionQuery implements SqmSelectionQueryImplementor, InterpretationsKeySource { private final String hql; - private Object queryStringCacheKey; - private SqmSelectStatement sqm; + private final Object queryStringCacheKey; + private final SqmSelectStatement sqm; - private ParameterMetadataImplementor parameterMetadata; - private DomainParameterXref domainParameterXref; - private QueryParameterBindings parameterBindings; + private final ParameterMetadataImplementor parameterMetadata; + private final DomainParameterXref domainParameterXref; + private final QueryParameterBindings parameterBindings; private final Class expectedResultType; private final Class resultType; @@ -262,16 +261,14 @@ private void setBindValues(QueryParameter parameter, QueryParameterBindin final var explicitTemporalPrecision = binding.getExplicitTemporalPrecision(); if ( explicitTemporalPrecision != null ) { if ( binding.isMultiValued() ) { - parameterBinding.setBindValues( binding.getBindValues(), explicitTemporalPrecision, - getTypeConfiguration() ); + parameterBinding.setBindValues( binding.getBindValues(), explicitTemporalPrecision ); } else { parameterBinding.setBindValue( binding.getBindValue(), explicitTemporalPrecision ); } } else { - //noinspection unchecked - final var bindType = (BindableType) binding.getBindType(); + final var bindType = binding.getBindType(); if ( binding.isMultiValued() ) { parameterBinding.setBindValues( binding.getBindValues(), bindType ); } @@ -324,20 +321,20 @@ public SqmSelectStatement getSqmStatement() { return sqm; } - @Override - protected void setSqmStatement(SqmSelectStatement sqm) { - this.sqm = sqm; - this.queryStringCacheKey = sqm; - - final QueryParameterBindings oldParameterBindings = parameterBindings; - domainParameterXref = DomainParameterXref.from( sqm ); - parameterMetadata = - domainParameterXref.hasParameters() - ? new ParameterMetadataImpl( domainParameterXref.getQueryParameters() ) - : ParameterMetadataImpl.EMPTY; - parameterBindings = parameterMetadata.createBindings( getSessionFactory() ); - copyParameterBindings( oldParameterBindings ); - } +// @Override +// protected void setSqmStatement(SqmSelectStatement sqm) { +// this.sqm = sqm; +// this.queryStringCacheKey = sqm; +// +// final QueryParameterBindings oldParameterBindings = parameterBindings; +// domainParameterXref = DomainParameterXref.from( sqm ); +// parameterMetadata = +// domainParameterXref.hasParameters() +// ? new ParameterMetadataImpl( domainParameterXref.getQueryParameters() ) +// : ParameterMetadataImpl.EMPTY; +// parameterBindings = parameterMetadata.createBindings( getSessionFactory() ); +// copyParameterBindings( oldParameterBindings ); +// } @Override public DomainParameterXref getDomainParameterXref() { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java index 46c3af8fb73f..c789c4911cef 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java @@ -36,6 +36,7 @@ import java.time.temporal.TemporalAmount; import java.util.Objects; +import static org.hibernate.internal.util.type.PrimitiveWrappers.canonicalize; import static org.hibernate.type.descriptor.java.JavaTypeHelper.isUnknown; /** @@ -383,26 +384,12 @@ private static boolean isConvertedType(SqmExpressible type) { return type.getSqmType() instanceof ConvertedBasicType; } - private static Class canonicalize(Class lhs) { - return switch (lhs.getCanonicalName()) { - case "boolean" -> Boolean.class; - case "byte" -> Byte.class; - case "short" -> Short.class; - case "int" -> Integer.class; - case "long" -> Long.class; - case "float" -> Float.class; - case "double" -> Double.class; - case "char" -> Character.class; - default -> lhs; - }; - } - private static boolean isMappedSuperclassTypeAssignable( MappedSuperclassDomainType lhsType, EntityType rhsType, BindingContext bindingContext) { - for ( ManagedDomainType candidate : lhsType.getSubTypes() ) { + for ( var candidate : lhsType.getSubTypes() ) { if ( candidate instanceof EntityType candidateEntityType && isEntityTypeAssignable( candidateEntityType, rhsType, bindingContext ) ) { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java index 9d66ce48869e..1d683398789d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java @@ -4,10 +4,6 @@ */ package org.hibernate.query.sqm.mutation.internal; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; @@ -44,18 +40,21 @@ import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.sql.results.internal.RowTransformerArrayImpl; import org.hibernate.sql.results.internal.RowTransformerSingularReturnImpl; import org.hibernate.sql.results.spi.ListResultsConsumer; import org.hibernate.sql.results.spi.RowTransformer; - import org.jboss.logging.Logger; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + /** * Helper used to generate the SELECT for selection of an entity's identifier, here specifically intended to be used * as the SELECT portion of a multi-table SQM mutation @@ -162,7 +161,7 @@ public static SqmSelectStatement generateMatchingIdSelectStatement( * Centralized selection of ids matching the restriction of the DELETE * or UPDATE SQM query */ - public static CacheableSqmInterpretation createMatchingIdsSelect( + public static CacheableSqmInterpretation createMatchingIdsSelect( SqmDeleteOrUpdateStatement sqmMutationStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext executionContext, @@ -215,7 +214,7 @@ public static CacheableSqmInterpretation translation = translator.translate(); final JdbcServices jdbcServices = factory.getJdbcServices(); final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); - final SqlAstTranslator sqlAstSelectTranslator = jdbcEnvironment + final SqlAstTranslator sqlAstSelectTranslator = jdbcEnvironment .getSqlAstTranslatorFactory() .buildSelectTranslator( factory, translation.getSqlAst() ); @@ -267,13 +266,13 @@ public static List selectMatchingIds( DomainParameterXref domainParameterXref, DomainQueryExecutionContext executionContext) { final MutableObject jdbcParameterBindings = new MutableObject<>(); - final CacheableSqmInterpretation interpretation = + final CacheableSqmInterpretation interpretation = createMatchingIdsSelect( sqmMutationStatement, domainParameterXref, executionContext, jdbcParameterBindings ); return selectMatchingIds( interpretation, jdbcParameterBindings.get(), executionContext ); } public static List selectMatchingIds( - CacheableSqmInterpretation interpretation, + CacheableSqmInterpretation interpretation, JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext executionContext) { final RowTransformer rowTransformer; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java index d36d4c9b1892..59b3f969b731 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java @@ -4,12 +4,6 @@ */ package org.hibernate.query.sqm.mutation.internal.cte; -import java.util.ArrayList; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; - import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.engine.jdbc.spi.JdbcServices; @@ -26,9 +20,9 @@ import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.mutation.internal.AbstractMutationHandler; import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.AbstractMutationHandler; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.expression.SqmExpression; @@ -54,15 +48,21 @@ import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.select.SelectClause; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.sql.results.internal.RowTransformerSingularReturnImpl; import org.hibernate.sql.results.internal.SqlSelectionImpl; import org.hibernate.sql.results.spi.ListResultsConsumer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + /** * Defines how identifier values are selected from the updatable/deletable tables. * @@ -76,7 +76,7 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler private final Map, Map, List>> jdbcParamsXref; private final Map, MappingModelExpressible> resolvedParameterMappingModelTypes; - private final JdbcOperationQuerySelect select; + private final JdbcSelect select; public AbstractCteMutationHandler( CteTable cteTable, @@ -140,7 +140,7 @@ public AbstractCteMutationHandler( final List> domainResults = new ArrayList<>( 1 ); final SelectStatement statement = new SelectStatement( querySpec, domainResults ); final JdbcServices jdbcServices = factory.getJdbcServices(); - final SqlAstTranslator translator = jdbcServices.getJdbcEnvironment() + final SqlAstTranslator translator = jdbcServices.getJdbcEnvironment() .getSqlAstTranslatorFactory() .buildSelectTranslator( factory, statement ); @@ -232,7 +232,7 @@ public int execute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecu } // For Hibernate Reactive - protected JdbcOperationQuerySelect getSelect() { + protected JdbcSelect getSelect() { return select; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java index ffe734e4e842..e34122067812 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java @@ -91,9 +91,9 @@ import org.hibernate.sql.ast.tree.select.SortSpecification; import org.hibernate.sql.ast.tree.update.Assignment; import org.hibernate.sql.ast.tree.update.UpdateStatement; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.sql.results.internal.RowTransformerSingularReturnImpl; @@ -124,7 +124,7 @@ public class CteInsertHandler implements InsertHandler { private final Map, Map, List>> jdbcParamsXref; private final Map, MappingModelExpressible> resolvedParameterMappingModelTypes; - private final JdbcOperationQuerySelect select; + private final JdbcSelect select; public CteInsertHandler( CteTable cteTable, @@ -552,7 +552,7 @@ else if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) // Execute the statement final JdbcServices jdbcServices = factory.getJdbcServices(); - final SqlAstTranslator translator = jdbcServices.getJdbcEnvironment() + final SqlAstTranslator translator = jdbcServices.getJdbcEnvironment() .getSqlAstTranslatorFactory() .buildSelectTranslator( factory, statement ); @@ -622,7 +622,7 @@ public int execute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecu } // For Hibernate Reactive - protected JdbcOperationQuerySelect getSelect() { + protected JdbcSelect getSelect() { return select; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/AbstractInlineHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/AbstractInlineHandler.java index f31e0028fb97..8f532d58c44e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/AbstractInlineHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/AbstractInlineHandler.java @@ -20,8 +20,8 @@ import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelect; /** @@ -34,7 +34,7 @@ public abstract class AbstractInlineHandler implements Handler { private final MatchingIdRestrictionProducer matchingIdsPredicateProducer; private final EntityPersister entityDescriptor; private final DomainParameterXref domainParameterXref; - private final CacheableSqmInterpretation matchingIdsInterpretation; + private final CacheableSqmInterpretation matchingIdsInterpretation; protected AbstractInlineHandler( MatchingIdRestrictionProducer matchingIdsPredicateProducer, @@ -95,7 +95,7 @@ protected DomainParameterXref getDomainParameterXref() { return domainParameterXref; } - protected CacheableSqmInterpretation getMatchingIdsInterpretation() { + protected CacheableSqmInterpretation getMatchingIdsInterpretation() { return matchingIdsInterpretation; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java index da2b5ec53728..6026f939bc87 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java @@ -5,19 +5,15 @@ package org.hibernate.query.sqm.mutation.internal.temptable; import org.hibernate.LockMode; -import org.hibernate.LockOptions; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.dialect.temptable.TemporaryTableColumn; import org.hibernate.dialect.temptable.TemporaryTableHelper; import org.hibernate.dialect.temptable.TemporaryTableHelper.TemporaryTableCreationWork; import org.hibernate.dialect.temptable.TemporaryTableHelper.TemporaryTableDropWork; -import org.hibernate.dialect.temptable.TemporaryTableSessionUidColumn; import org.hibernate.dialect.temptable.TemporaryTableStrategy; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; -import org.hibernate.engine.jdbc.spi.JdbcCoordinator; -import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.jdbc.AbstractReturningWork; +import org.hibernate.jdbc.AbstractWork; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.sqm.ComparisonOperator; @@ -32,12 +28,10 @@ import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.from.NamedTableReference; import org.hibernate.sql.ast.tree.from.StandardTableGroup; -import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; @@ -45,7 +39,6 @@ import org.hibernate.sql.results.internal.SqlSelectionImpl; import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; import java.util.UUID; @@ -68,30 +61,30 @@ public static CacheableSqmInterpretation { - querySpec.getFromClause().visitTableJoins( - tableJoin -> { - if ( tableJoin.isInitialized() - && tableJoin.getJoinType() != SqlAstJoinType.INNER ) { - lockOptions.setLockMode( lockMode ); - } - } - ); - } - ); - } - final var jdbcInsert = jdbcEnvironment.getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, idTableInsert ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - lockOptions.setLockMode( lockMode ); - - return new CacheableSqmInterpretation<>( - idTableInsert, - jdbcInsert, - Map.of(), - Map.of() - ); + return createTemporaryTableInsert( idTableInsert, jdbcParameterBindings, executionContext ); } public static CacheableSqmInterpretation createTemporaryTableInsert( @@ -164,74 +123,67 @@ public static CacheableSqmInterpretation { - querySpec.getFromClause().visitTableJoins( - tableJoin -> { - if ( tableJoin.isInitialized() - && tableJoin.getJoinType() != SqlAstJoinType.INNER ) { - lockOptions.setLockMode( lockMode ); - } - } - ); + sourceSelectStatement.visitQuerySpecs( querySpec -> { + querySpec.getFromClause().visitTableJoins( tableJoin -> { + if ( tableJoin.isInitialized() + && tableJoin.getJoinType() != SqlAstJoinType.INNER ) { + lockOptions.setLockMode( lockMode ); } - ); + } ); + } ); } - final var jdbcInsert = jdbcEnvironment.getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, temporaryTableInsert ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); lockOptions.setLockMode( lockMode ); return new CacheableSqmInterpretation<>( temporaryTableInsert, - jdbcInsert, + jdbcEnvironment.getSqlAstTranslatorFactory() + .buildMutationTranslator( factory, temporaryTableInsert ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ), Map.of(), Map.of() ); } + @Deprecated(forRemoval = true, since = "7.3") // no longer used public static int saveIntoTemporaryTable( InsertSelectStatement temporaryTableInsert, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { final var factory = executionContext.getSession().getFactory(); - final JdbcServices jdbcServices = factory.getJdbcServices(); - final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); - final LockOptions lockOptions = executionContext.getQueryOptions().getLockOptions(); - final LockMode lockMode = lockOptions.getLockMode(); + final var jdbcEnvironment = factory.getJdbcServices().getJdbcEnvironment(); + final var lockOptions = executionContext.getQueryOptions().getLockOptions(); + final var lockMode = lockOptions.getLockMode(); // Acquire a WRITE lock for the rows that are about to be modified lockOptions.setLockMode( LockMode.WRITE ); // Visit the table joins and reset the lock mode if we encounter OUTER joins that are not supported - final QueryPart sourceSelectStatement = temporaryTableInsert.getSourceSelectStatement(); + final var sourceSelectStatement = temporaryTableInsert.getSourceSelectStatement(); if ( sourceSelectStatement != null && !jdbcEnvironment.getDialect().supportsOuterJoinForUpdate() ) { - sourceSelectStatement.visitQuerySpecs( - querySpec -> { - querySpec.getFromClause().visitTableJoins( - tableJoin -> { - if ( tableJoin.isInitialized() - && tableJoin.getJoinType() != SqlAstJoinType.INNER ) { - lockOptions.setLockMode( lockMode ); - } - } - ); + sourceSelectStatement.visitQuerySpecs( querySpec -> { + querySpec.getFromClause().visitTableJoins( tableJoin -> { + if ( tableJoin.isInitialized() + && tableJoin.getJoinType() != SqlAstJoinType.INNER ) { + lockOptions.setLockMode( lockMode ); } - ); + } ); + } ); } - final var jdbcInsert = jdbcEnvironment.getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, temporaryTableInsert ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); lockOptions.setLockMode( lockMode ); - return saveIntoTemporaryTable( jdbcInsert, jdbcParameterBindings, executionContext ); + return saveIntoTemporaryTable( + jdbcEnvironment.getSqlAstTranslatorFactory() + .buildMutationTranslator( factory, temporaryTableInsert ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ), + jdbcParameterBindings, + executionContext + ); } public static int saveIntoTemporaryTable( @@ -253,7 +205,13 @@ public static QuerySpec createIdTableSelectQuerySpec( JdbcParameter sessionUidParameter, EntityMappingType entityDescriptor, ExecutionContext executionContext) { - return createIdTableSelectQuerySpec( idTable, null, sessionUidParameter, entityDescriptor, executionContext ); + return createIdTableSelectQuerySpec( + idTable, + null, + sessionUidParameter, + entityDescriptor, + executionContext + ); } public static QuerySpec createIdTableSelectQuerySpec( @@ -262,14 +220,14 @@ public static QuerySpec createIdTableSelectQuerySpec( JdbcParameter sessionUidParameter, EntityMappingType entityDescriptor, ExecutionContext executionContext) { - final QuerySpec querySpec = new QuerySpec( false ); + final var querySpec = new QuerySpec( false ); - final NamedTableReference idTableReference = new NamedTableReference( + final var idTableReference = new NamedTableReference( idTable.getTableExpression(), TemporaryTable.DEFAULT_ALIAS, true ); - final TableGroup idTableGroup = new StandardTableGroup( + final var idTableGroup = new StandardTableGroup( true, new NavigablePath( idTableReference.getTableExpression() ), entityDescriptor, @@ -282,7 +240,7 @@ public static QuerySpec createIdTableSelectQuerySpec( querySpec.getFromClause().addRoot( idTableGroup ); applyIdTableSelections( querySpec, idTableReference, idTable, fkModelPart, entityDescriptor ); - applyIdTableRestrictions( querySpec, idTableReference, idTable, sessionUidParameter, executionContext ); + applyIdTableRestrictions( querySpec, idTableReference, idTable, sessionUidParameter ); return querySpec; } @@ -314,22 +272,20 @@ private static void applyIdTableSelections( } } else { - fkModelPart.forEachSelectable( - (i, selectableMapping) -> { - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - i, - new ColumnReference( - tableReference, - selectableMapping.getSelectionExpression(), - false, - null, - selectableMapping.getJdbcMapping() - ) + fkModelPart.forEachSelectable( (i, selectableMapping) -> { + querySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( + i, + new ColumnReference( + tableReference, + selectableMapping.getSelectionExpression(), + false, + null, + selectableMapping.getJdbcMapping() ) - ); - } - ); + ) + ); + } ); } } @@ -337,17 +293,17 @@ private static void applyIdTableRestrictions( QuerySpec querySpec, TableReference idTableReference, TemporaryTable idTable, - JdbcParameter sessionUidParameter, - ExecutionContext executionContext) { - if ( idTable.getSessionUidColumn() != null ) { + JdbcParameter sessionUidParameter) { + final var sessionUidColumn = idTable.getSessionUidColumn(); + if ( sessionUidColumn != null ) { querySpec.applyPredicate( new ComparisonPredicate( new ColumnReference( idTableReference, - idTable.getSessionUidColumn().getColumnName(), + sessionUidColumn.getColumnName(), false, null, - idTable.getSessionUidColumn().getJdbcMapping() + sessionUidColumn.getJdbcMapping() ), ComparisonOperator.EQUAL, sessionUidParameter @@ -362,7 +318,8 @@ public static void performBeforeTemporaryTableUseActions( ExecutionContext executionContext) { performBeforeTemporaryTableUseActions( temporaryTable, - executionContext.getSession().getDialect().getTemporaryTableBeforeUseAction(), + executionContext.getSession().getDialect() + .getTemporaryTableBeforeUseAction(), executionContext ); } @@ -383,36 +340,21 @@ private static boolean performBeforeTemporaryTableUseActions( BeforeUseAction beforeUseAction, ExecutionContext executionContext) { final var factory = executionContext.getSession().getFactory(); - final Dialect dialect = factory.getJdbcServices().getDialect(); - if ( beforeUseAction == BeforeUseAction.CREATE ) { - final var temporaryTableCreationWork = - new TemporaryTableCreationWork( temporaryTable, factory ); - final var ddlTransactionHandling = dialect.getTemporaryTableDdlTransactionHandling(); - if ( ddlTransactionHandling == NONE ) { - return executionContext.getSession().doReturningWork( temporaryTableCreationWork ); - } - else { - final var isolationDelegate = - executionContext.getSession().getJdbcCoordinator().getJdbcSessionOwner() - .getTransactionCoordinator().createIsolationDelegate(); - return isolationDelegate.delegateWork( temporaryTableCreationWork, - ddlTransactionHandling == ISOLATE_AND_TRANSACT ); - } - } - else { - return false; - } + final var dialect = factory.getJdbcServices().getDialect(); + return beforeUseAction == BeforeUseAction.CREATE + && doWork( executionContext, dialect, + new TemporaryTableCreationWork( temporaryTable, factory ) ); } - public static int[] loadInsertedRowNumbers( - TemporaryTable temporaryTable, - Function sessionUidAccess, - int rows, - ExecutionContext executionContext) { - final String sqlSelect = - createInsertedRowNumbersSelectSql( temporaryTable, sessionUidAccess, executionContext ); - return loadInsertedRowNumbers( sqlSelect, temporaryTable, sessionUidAccess, rows, executionContext ); - } +// public static int[] loadInsertedRowNumbers( +// TemporaryTable temporaryTable, +// Function sessionUidAccess, +// int rows, +// ExecutionContext executionContext) { +// final String sqlSelect = +// createInsertedRowNumbersSelectSql( temporaryTable, sessionUidAccess, executionContext ); +// return loadInsertedRowNumbers( sqlSelect, temporaryTable, sessionUidAccess, rows, executionContext ); +// } public static int[] loadInsertedRowNumbers( String sqlSelect, @@ -420,9 +362,9 @@ public static int[] loadInsertedRowNumbers( Function sessionUidAccess, int rows, ExecutionContext executionContext) { - final TemporaryTableSessionUidColumn sessionUidColumn = temporaryTable.getSessionUidColumn(); - final SharedSessionContractImplementor session = executionContext.getSession(); - final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); + final var sessionUidColumn = temporaryTable.getSessionUidColumn(); + final var session = executionContext.getSession(); + final var jdbcCoordinator = session.getJdbcCoordinator(); PreparedStatement preparedStatement = null; try { preparedStatement = jdbcCoordinator.getStatementPreparer().prepareStatement( sqlSelect ); @@ -435,8 +377,8 @@ public static int[] loadInsertedRowNumbers( session ); } - final ResultSet resultSet = jdbcCoordinator.getResultSetReturn().execute( preparedStatement, sqlSelect ); - final int[] rowNumbers = new int[rows]; + final var resultSet = jdbcCoordinator.getResultSetReturn().execute( preparedStatement, sqlSelect ); + final var rowNumbers = new int[rows]; try { int rowIndex = 0; while (resultSet.next()) { @@ -466,18 +408,19 @@ public static int[] loadInsertedRowNumbers( public static String createInsertedRowNumbersSelectSql( TemporaryTable temporaryTable, - Function sessionUidAccess, ExecutionContext executionContext) { - final TemporaryTableSessionUidColumn sessionUidColumn = temporaryTable.getSessionUidColumn(); + final var sessionUidColumn = temporaryTable.getSessionUidColumn(); - final TemporaryTableColumn rowNumberColumn = temporaryTable.getColumns() - .get( temporaryTable.getColumns().size() - (sessionUidColumn == null ? 1 : 2 ) ); + final var rowNumberColumn = + temporaryTable.getColumns() + .get( temporaryTable.getColumns().size() - (sessionUidColumn == null ? 1 : 2 ) ); assert rowNumberColumn != null; - final SharedSessionContractImplementor session = executionContext.getSession(); - final SimpleSelect simpleSelect = new SimpleSelect( session.getFactory() ) - .setTableName( temporaryTable.getQualifiedTableName() ) - .addColumn( rowNumberColumn.getColumnName() ); + final var session = executionContext.getSession(); + final var simpleSelect = + new SimpleSelect( session.getFactory() ) + .setTableName( temporaryTable.getQualifiedTableName() ) + .addColumn( rowNumberColumn.getColumnName() ); if ( sessionUidColumn != null ) { simpleSelect.addRestriction( sessionUidColumn.getColumnName() ); } @@ -490,7 +433,7 @@ public static void performAfterTemporaryTableUseActions( AfterUseAction afterUseAction, ExecutionContext executionContext) { final var factory = executionContext.getSession().getFactory(); - final Dialect dialect = factory.getJdbcServices().getDialect(); + final var dialect = factory.getJdbcServices().getDialect(); switch ( afterUseAction ) { case CLEAN: TemporaryTableHelper.cleanTemporaryTableRows( @@ -501,18 +444,43 @@ public static void performAfterTemporaryTableUseActions( ); break; case DROP: - final var temporaryTableDropWork = new TemporaryTableDropWork( temporaryTable, factory ); - final var ddlTransactionHandling = dialect.getTemporaryTableDdlTransactionHandling(); - if ( ddlTransactionHandling == NONE ) { - executionContext.getSession().doWork( temporaryTableDropWork ); - } - else { - final var isolationDelegate = - executionContext.getSession().getJdbcCoordinator().getJdbcSessionOwner() - .getTransactionCoordinator().createIsolationDelegate(); - isolationDelegate.delegateWork( temporaryTableDropWork, + doWork( executionContext, dialect, + new TemporaryTableDropWork( temporaryTable, factory ) ); + } + } + + private static T doWork( + ExecutionContext executionContext, Dialect dialect, + AbstractReturningWork temporaryTableCreationWork) { + final var ddlTransactionHandling = dialect.getTemporaryTableDdlTransactionHandling(); + if ( ddlTransactionHandling == NONE ) { + return executionContext.getSession().doReturningWork( temporaryTableCreationWork ); + } + else { + // this branch is obsolete, since + // dialect.getTemporaryTableDdlTransactionHandling() + // now always returns NONE + return executionContext.getSession().getJdbcCoordinator().getJdbcSessionOwner() + .getTransactionCoordinator().createIsolationDelegate() + .delegateWork( temporaryTableCreationWork, + ddlTransactionHandling == ISOLATE_AND_TRANSACT ); + } + } + private static void doWork( + ExecutionContext executionContext, Dialect dialect, + AbstractWork temporaryTableDropWork) { + final var ddlTransactionHandling = dialect.getTemporaryTableDdlTransactionHandling(); + if ( ddlTransactionHandling == NONE ) { + executionContext.getSession().doWork( temporaryTableDropWork ); + } + else { + // this branch is obsolete, since + // dialect.getTemporaryTableDdlTransactionHandling() + // now always returns NONE + executionContext.getSession().getJdbcCoordinator().getJdbcSessionOwner() + .getTransactionCoordinator().createIsolationDelegate() + .delegateWork( temporaryTableDropWork, ddlTransactionHandling == ISOLATE_AND_TRANSACT ); - } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java index aa39b4c288c7..0941785e37a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java @@ -4,18 +4,6 @@ */ package org.hibernate.query.sqm.mutation.internal.temptable; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.function.BiConsumer; -import java.util.function.Function; -import java.util.stream.IntStream; - import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableColumn; @@ -95,18 +83,29 @@ import org.hibernate.sql.exec.internal.SqlTypedMappingJdbcParameter; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBinder; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.internal.SqlSelectionImpl; import org.hibernate.sql.results.spi.ListResultsConsumer; import org.hibernate.type.BasicType; - import org.hibernate.type.descriptor.ValueBinder; import org.jboss.logging.Logger; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.stream.IntStream; + import static org.hibernate.generator.EventType.INSERT; import static org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper.isId; @@ -476,7 +475,7 @@ private void collectTableReference( } protected record RootTableInserter( - @Nullable JdbcOperationQuerySelect temporaryTableIdentitySelect, + @Nullable JdbcSelect temporaryTableIdentitySelect, @Nullable JdbcOperationQueryMutation temporaryTableIdUpdate, @Nullable String temporaryTableRowNumberSelectSql, JdbcOperationQueryMutation rootTableInsert, @@ -541,7 +540,7 @@ private RootTableInserter createRootTableInserter( applyAssignments( assignments, insertStatement, temporaryTableReference, getEntityDescriptor() ); final JdbcServices jdbcServices = getSessionFactory().getJdbcServices(); final SharedSessionContractImplementor session = executionContext.getSession(); - final JdbcOperationQuerySelect temporaryTableIdentitySelect; + final JdbcSelect temporaryTableIdentitySelect; final JdbcOperationQueryMutation temporaryTableIdUpdate; final String temporaryTableRowNumberSelectSql; final JdbcOperationQueryMutation rootTableInsert; @@ -678,7 +677,6 @@ private RootTableInserter createRootTableInserter( .translate( null, executionContext.getQueryOptions() ); temporaryTableRowNumberSelectSql = ExecuteWithTemporaryTableHelper.createInsertedRowNumbersSelectSql( entityTable, - sessionUidAccess, executionContext ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index 07e04313402b..0b7d8ed24318 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -105,7 +105,6 @@ import org.hibernate.query.sqm.CastType; import org.hibernate.query.sqm.ComparisonOperator; import org.hibernate.query.sqm.DiscriminatorSqmPath; -import org.hibernate.query.sqm.DynamicInstantiationNature; import org.hibernate.query.sqm.InterpretationException; import org.hibernate.query.sqm.SqmBindableType; import org.hibernate.query.sqm.SqmExpressible; @@ -252,7 +251,6 @@ import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; import org.hibernate.query.sqm.tree.select.SqmAliasedNode; import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation; -import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiationArgument; import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiationTarget; import org.hibernate.query.sqm.tree.select.SqmJpaCompoundSelection; import org.hibernate.query.sqm.tree.select.SqmOrderByClause; @@ -1632,12 +1630,12 @@ public Values visitValues(SqmValues sqmValues) { @Override public SelectStatement visitSelectStatement(SqmSelectStatement statement) { - final CteContainer oldCteContainer = cteContainer; - final CteContainer cteContainer = this.visitCteContainer( statement ); - final SqmStatement oldSqmStatement = this.currentSqmStatement; + final var oldCteContainer = cteContainer; + final var cteContainer = this.visitCteContainer( statement ); + final var oldSqmStatement = this.currentSqmStatement; this.currentSqmStatement = statement; - final QueryPart queryPart = visitQueryPart( statement.getQueryPart() ); + final var queryPart = visitQueryPart( statement.getQueryPart() ); final List> domainResults = queryPart.isRoot() ? this.domainResults : emptyList(); try { return new SelectStatement( cteContainer, queryPart, domainResults ); @@ -1651,30 +1649,24 @@ public SelectStatement visitSelectStatement(SqmSelectStatement statement) { @Override public DynamicInstantiation visitDynamicInstantiation(SqmDynamicInstantiation sqmDynamicInstantiation) { - final SqmDynamicInstantiationTarget instantiationTarget = sqmDynamicInstantiation.getInstantiationTarget(); - final DynamicInstantiationNature instantiationNature = instantiationTarget.getNature(); - final JavaType targetTypeDescriptor = interpretInstantiationTarget( instantiationTarget ); - - final DynamicInstantiation dynamicInstantiation = - new DynamicInstantiation<>( instantiationNature, targetTypeDescriptor ); - - for ( SqmDynamicInstantiationArgument sqmArgument : sqmDynamicInstantiation.getArguments() ) { + final var instantiationTarget = sqmDynamicInstantiation.getInstantiationTarget(); + final var dynamicInstantiation = + new DynamicInstantiation<>( instantiationTarget.getNature(), + interpretInstantiationTarget( instantiationTarget ) ); + for ( var sqmArgument : sqmDynamicInstantiation.getArguments() ) { if ( sqmArgument.getSelectableNode() instanceof SqmPath sqmPath ) { prepareForSelection( sqmPath ); } - final DomainResultProducer argumentResultProducer = (DomainResultProducer) sqmArgument.accept( this ); - - dynamicInstantiation.addArgument( sqmArgument.getAlias(), argumentResultProducer, this ); + dynamicInstantiation.addArgument( sqmArgument.getAlias(), + (DomainResultProducer) sqmArgument.accept( this ) ); } - dynamicInstantiation.complete(); - return dynamicInstantiation; } - private JavaType interpretInstantiationTarget(SqmDynamicInstantiationTarget instantiationTarget) { + private JavaType interpretInstantiationTarget(SqmDynamicInstantiationTarget instantiationTarget) { return getCreationContext().getTypeConfiguration().getJavaTypeRegistry() - .getDescriptor( switch ( instantiationTarget.getNature() ) { + .resolveDescriptor( switch ( instantiationTarget.getNature() ) { case LIST -> List.class; case MAP -> Map.class; default -> instantiationTarget.getJavaType(); @@ -5812,10 +5804,7 @@ else if ( Character.class.isAssignableFrom( valueConverter.getRelationalJavaType // so we allow coercion between the number types else if ( Number.class.isAssignableFrom( valueConverter.getRelationalJavaType().getJavaTypeClass() ) && value instanceof Number ) { - return valueConverter.getRelationalJavaType().coerce( - value, - creationContext::getTypeConfiguration - ); + return valueConverter.getRelationalJavaType().coerce( value ); } else { throw new SemanticException( @@ -7825,19 +7814,44 @@ public Predicate visitMemberOfPredicate(SqmMemberOfPredicate predicate) { this ) ); + subQuerySpec.applyPredicate( + new ComparisonPredicate( + toSingleExpression( subQuerySpec.getSelectClause().getSqlSelections(), lhs ), + ComparisonOperator.EQUAL, + lhs + ) + ); + subQuerySpec.getSelectClause().getSqlSelections().clear(); + subQuerySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( new QueryLiteral<>( 1, basicType( Integer.class ) ) ) + ); } finally { popProcessingStateStack(); } - return new InSubQueryPredicate( - lhs, + return new ExistsPredicate( new SelectStatement( subQuerySpec ), predicate.isNegated(), getBooleanType() ); } + private Expression toSingleExpression(List sqlSelections, Expression inferenceSource) { + assert !sqlSelections.isEmpty(); + + if ( sqlSelections.size() == 1 ) { + return sqlSelections.get( 0 ).getExpression(); + } + else { + final var expressions = new ArrayList( sqlSelections.size() ); + for ( SqlSelection sqlSelection : sqlSelections ) { + expressions.add( sqlSelection.getExpression() ); + } + return new SqlTuple( expressions, (MappingModelExpressible) inferenceSource.getExpressionType() ); + } + } + @Override public NegatedPredicate visitNegatedPredicate(SqmNegatedPredicate predicate) { return new NegatedPredicate( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java index 6e88b7a10c9c..a4a5a48dbdd5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java @@ -25,6 +25,8 @@ import java.util.Set; import java.util.function.Function; +import static java.lang.Character.isAlphabetic; + /** * @author Steve Ebersole */ @@ -56,17 +58,19 @@ public AbstractSqmDmlStatement( } protected Map> copyCteStatements(SqmCopyContext context) { - final Map> cteStatements = new LinkedHashMap<>( this.cteStatements.size() ); - for ( Map.Entry> entry : this.cteStatements.entrySet() ) { - cteStatements.put( entry.getKey(), entry.getValue().copy( context ) ); + final Map> copy = + new LinkedHashMap<>( cteStatements.size() ); + for ( var entry : cteStatements.entrySet() ) { + copy.put( entry.getKey(), entry.getValue().copy( context ) ); } - return cteStatements; + return copy; } protected void putAllCtes(SqmCteContainer cteContainer) { - for ( SqmCteStatement cteStatement : cteContainer.getCteStatements() ) { - if ( cteStatements.putIfAbsent( cteStatement.getCteTable().getCteName(), cteStatement ) != null ) { - throw new IllegalArgumentException( "A CTE with the label " + cteStatement.getCteTable().getCteName() + " already exists" ); + for ( var cteStatement : cteContainer.getCteStatements() ) { + final String cteName = cteStatement.getCteTable().getCteName(); + if ( cteStatements.putIfAbsent( cteName, cteStatement ) != null ) { + throw new IllegalArgumentException( "A CTE with the label " + cteName + " already exists" ); } } } @@ -138,7 +142,7 @@ private String validateCteName(String name) { if ( name == null || name.isBlank() ) { throw new IllegalArgumentException( "Illegal empty CTE name" ); } - if ( !Character.isAlphabetic( name.charAt( 0 ) ) ) { + if ( !isAlphabetic( name.charAt( 0 ) ) ) { throw new IllegalArgumentException( String.format( "Illegal CTE name [%s]. Names must start with an alphabetic character!", @@ -150,7 +154,7 @@ private String validateCteName(String name) { } private JpaCteCriteria withInternal(String name, AbstractQuery criteria) { - final SqmCteStatement cteStatement = new SqmCteStatement<>( + final var cteStatement = new SqmCteStatement<>( name, (SqmSelectQuery) criteria, this, @@ -167,7 +171,7 @@ private JpaCteCriteria withInternal( AbstractQuery baseCriteria, boolean unionDistinct, Function, AbstractQuery> recursiveCriteriaProducer) { - final SqmCteStatement cteStatement = new SqmCteStatement<>( + final var cteStatement = new SqmCteStatement<>( name, (SqmSelectQuery) baseCriteria, unionDistinct, @@ -199,7 +203,7 @@ public SqmSubQuery subquery(Class type) { protected void appendHqlCteString(StringBuilder sb, SqmRenderContext context) { if ( !cteStatements.isEmpty() ) { sb.append( "with " ); - for ( SqmCteStatement value : cteStatements.values() ) { + for ( var value : cteStatements.values() ) { value.appendHqlString( sb, context ); sb.append( ", " ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmRestrictedDmlStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmRestrictedDmlStatement.java index 82531d198607..44185863958d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmRestrictedDmlStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmRestrictedDmlStatement.java @@ -60,7 +60,7 @@ protected AbstractSqmRestrictedDmlStatement( return null; } else { - final SqmPredicate predicate = whereClause.getPredicate(); + final var predicate = whereClause.getPredicate(); return new SqmWhereClause( predicate == null ? null : predicate.copy( context ), nodeBuilder() ); } } @@ -70,8 +70,8 @@ public SqmRoot from(Class entityClass) { } public SqmRoot from(EntityType entity) { - final EntityDomainType entityDomainType = (EntityDomainType) entity; - final SqmRoot root = getTarget(); + final var entityDomainType = (EntityDomainType) entity; + final var root = getTarget(); if ( root.getModel() != entity ) { throw new IllegalArgumentException( String.format( @@ -124,7 +124,7 @@ protected void setWhere(Predicate @Nullable ... restrictions) { // Clear the current predicate if one is present whereClause.setPredicate( null ); if ( restrictions != null ) { - for ( Predicate restriction : restrictions ) { + for ( var restriction : restrictions ) { whereClause.applyPredicate( (SqmPredicate) restriction ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmStatement.java index be3d59fae475..83217d39c292 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmStatement.java @@ -49,7 +49,7 @@ protected AbstractSqmStatement( } else { final Set> parameters = new LinkedHashSet<>( this.parameters.size() ); - for ( SqmParameter parameter : this.parameters ) { + for ( var parameter : this.parameters ) { parameters.add( parameter.copy( context ) ); } return parameters; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteStatement.java index 4ab2ad036baa..dc476e8d841d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteStatement.java @@ -147,7 +147,7 @@ private SqmCteStatement( @Override public SqmCteStatement copy(SqmCopyContext context) { - final SqmCteStatement existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/delete/SqmDeleteStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/delete/SqmDeleteStatement.java index d2ddd024a301..136783583b11 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/delete/SqmDeleteStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/delete/SqmDeleteStatement.java @@ -61,7 +61,7 @@ public SqmDeleteStatement( @Override public SqmDeleteStatement copy(SqmCopyContext context) { - final SqmDeleteStatement existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java index dac9754aa13d..243382d20741 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java @@ -33,7 +33,6 @@ import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmPathSource; -import org.hibernate.query.sqm.spi.SqmCreationHelper; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.SqmRenderContext; @@ -66,6 +65,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableList; import static org.hibernate.query.sqm.internal.SqmUtil.findCompatibleFetchJoin; +import static org.hibernate.query.sqm.spi.SqmCreationHelper.buildRootNavigablePath; /** * Convenience base class for SqmFrom implementations @@ -85,7 +85,6 @@ protected AbstractSqmFrom( @Nullable String alias, NodeBuilder nodeBuilder) { super( navigablePath, referencedNavigable, lhs, nodeBuilder ); - if ( lhs == null ) { throw new IllegalArgumentException( "LHS cannot be null" ); } @@ -100,7 +99,7 @@ protected AbstractSqmFrom( @Nullable String alias, NodeBuilder nodeBuilder) { super( - SqmCreationHelper.buildRootNavigablePath( entityType.getHibernateEntityName(), alias ), + buildRootNavigablePath( entityType.getHibernateEntityName(), alias ), (SqmEntityDomainType) entityType, null, nodeBuilder @@ -118,7 +117,6 @@ protected AbstractSqmFrom( @Nullable String alias, NodeBuilder nodeBuilder) { super( navigablePath, entityType, null, nodeBuilder ); - this.alias = alias; } @@ -136,20 +134,32 @@ protected void copyTo(AbstractSqmFrom target, SqmCopyContext context) { super.copyTo( target, context ); final var joins = this.joins; if ( joins != null ) { - final ArrayList> newJoins = new ArrayList<>( joins.size() ); - for ( SqmJoin join : joins ) { - newJoins.add( join.copy( context ) ); - } - target.joins = newJoins; + target.joins = copyJoins( context, joins ); } final var treats = this.treats; if ( treats != null ) { - final ArrayList> newTreats = new ArrayList<>( treats.size() ); - for ( SqmTreatedFrom treat : treats ) { - newTreats.add( treat.copy( context ) ); - } - target.treats = newTreats; + target.treats = copyTreats( context, treats ); + } + } + + private static ArrayList> copyTreats( + SqmCopyContext context, List> treats) { + final ArrayList> newTreats = + new ArrayList<>( treats.size() ); + for ( SqmTreatedFrom treat : treats ) { + newTreats.add( treat.copy( context ) ); + } + return newTreats; + } + + private static ArrayList> copyJoins( + SqmCopyContext context, List> joins) { + final ArrayList> newJoins = + new ArrayList<>( joins.size() ); + for ( var join : joins ) { + newJoins.add( join.copy( context ) ); } + return newJoins; } @Override @@ -169,7 +179,7 @@ public SqmPath resolvePathPart( SqmCreationState creationState) { // Try to resolve an existing attribute join without ON clause SqmPath resolvedPath = null; - for ( SqmJoin sqmJoin : getSqmJoins() ) { + for ( var sqmJoin : getSqmJoins() ) { // We can only match singular joins here, as plural path parts are interpreted like sub-queries if ( sqmJoin instanceof SqmSingularJoin attributeJoin && name.equals( sqmJoin.getReferencedPathSource().getPathName() ) ) { @@ -202,7 +212,7 @@ public SqmPath resolvePathPart( if ( resolvedPath != null ) { return resolvedPath; } - final SqmPath sqmPath = get( name, true ); + final var sqmPath = get( name, true ); creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); return sqmPath; } @@ -233,22 +243,21 @@ public void addSqmJoin(SqmJoin join) { @Internal public void removeLeftFetchJoins() { - final List> joins = this.joins; + final var joins = this.joins; if ( joins != null ) { for ( var join : new ArrayList<>( joins ) ) { - if ( join instanceof SqmAttributeJoin attributeJoin ) { - if ( attributeJoin.isFetched() ) { - if ( join.getSqmJoinType() == SqmJoinType.LEFT ) { - joins.remove( join ); - final var orderedJoins = findRoot().getOrderedJoins(); - if ( orderedJoins != null ) { - orderedJoins.remove( join ); - } - } - else { - attributeJoin.clearFetched(); + if ( join instanceof SqmAttributeJoin attributeJoin + && attributeJoin.isFetched() ) { + if ( join.getSqmJoinType() == SqmJoinType.LEFT ) { + joins.remove( join ); + final var orderedJoins = findRoot().getOrderedJoins(); + if ( orderedJoins != null ) { + orderedJoins.remove( join ); } } + else { + attributeJoin.clearFetched(); + } } } } @@ -266,9 +275,10 @@ public void visitSqmJoins(Consumer> consumer) { return treats == null ? emptyList() : treats; } - protected > @Nullable X findTreat(ManagedDomainType targetType, @Nullable String alias) { + protected > @Nullable X findTreat( + ManagedDomainType targetType, @Nullable String alias) { if ( treats != null ) { - for ( SqmTreatedFrom treat : treats ) { + for ( var treat : treats ) { if ( treat.getModel() == targetType ) { if ( Objects.equals( treat.getExplicitAlias(), alias ) ) { //noinspection unchecked @@ -332,7 +342,7 @@ public SqmSingularJoin join(SingularAttribute attribute) @Override public SqmSingularJoin join(SingularAttribute attribute, JoinType jt) { - final SqmSingularJoin join = + final var join = buildSingularJoin( (SqmSingularPersistentAttribute) attribute, SqmJoinType.from( jt ), false ); addSqmJoin( join ); @@ -352,8 +362,8 @@ public SqmEntityJoin join(EntityDomainType targetEntityDescriptor) @Override public SqmEntityJoin join(EntityDomainType targetEntityDescriptor, SqmJoinType joinType) { //noinspection unchecked - final SqmRoot root = (SqmRoot) findRoot(); - final SqmEntityJoin sqmEntityJoin = new SqmEntityJoin<>( + final var root = (SqmRoot) findRoot(); + final var sqmEntityJoin = new SqmEntityJoin<>( targetEntityDescriptor, generateAlias(), joinType, @@ -370,7 +380,7 @@ public SqmBagJoin join(CollectionAttribute attribute) { @Override public SqmBagJoin join(CollectionAttribute attribute, JoinType jt) { - final SqmBagJoin join = buildBagJoin( + final var join = buildBagJoin( (BagPersistentAttribute) attribute, SqmJoinType.from( jt ), false @@ -386,7 +396,7 @@ public SqmSetJoin join(SetAttribute attribute) { @Override public SqmSetJoin join(SetAttribute attribute, JoinType jt) { - final SqmSetJoin join = buildSetJoin( + final var join = buildSetJoin( (SetPersistentAttribute) attribute, SqmJoinType.from( jt ), false @@ -402,7 +412,7 @@ public SqmListJoin join(ListAttribute attribute) { @Override public SqmListJoin join(ListAttribute attribute, JoinType jt) { - final SqmListJoin join = buildListJoin( + final var join = buildListJoin( (ListPersistentAttribute) attribute, SqmJoinType.from( jt ), false @@ -418,7 +428,7 @@ public SqmMapJoin join(MapAttribute attribute) @Override public SqmMapJoin join(MapAttribute attribute, JoinType jt) { - final SqmMapJoin join = buildMapJoin( + final var join = buildMapJoin( (MapPersistentAttribute) attribute, SqmJoinType.from( jt ), false @@ -435,7 +445,7 @@ public SqmAttributeJoin join(String attributeName) { @Override @SuppressWarnings("unchecked") public SqmAttributeJoin join(String attributeName, JoinType jt) { - final SqmPathSource subPathSource = (SqmPathSource) + final var subPathSource = (SqmPathSource) getReferencedPathSource().getSubPathSource( attributeName ); return (SqmAttributeJoin) buildJoin( subPathSource, SqmJoinType.from( jt ), false ); } @@ -448,10 +458,9 @@ public SqmBagJoin joinCollection(String attributeName) { @Override @SuppressWarnings("unchecked") public SqmBagJoin joinCollection(String attributeName, JoinType jt) { - final SqmPathSource joinedPathSource = getReferencedPathSource().getSubPathSource( attributeName ); - + final var joinedPathSource = getReferencedPathSource().getSubPathSource( attributeName ); if ( joinedPathSource instanceof BagPersistentAttribute ) { - final SqmBagJoin join = buildBagJoin( + final var join = buildBagJoin( (BagPersistentAttribute) joinedPathSource, SqmJoinType.from( jt ), false @@ -479,10 +488,9 @@ public SqmSetJoin joinSet(String attributeName) { @Override @SuppressWarnings("unchecked") public SqmSetJoin joinSet(String attributeName, JoinType jt) { - final SqmPathSource joinedPathSource = getReferencedPathSource().getSubPathSource( attributeName ); - + final var joinedPathSource = getReferencedPathSource().getSubPathSource( attributeName ); if ( joinedPathSource instanceof SetPersistentAttribute ) { - final SqmSetJoin join = buildSetJoin( + final var join = buildSetJoin( (SetPersistentAttribute) joinedPathSource, SqmJoinType.from( jt ), false @@ -510,10 +518,10 @@ public SqmListJoin joinList(String attributeName) { @Override @SuppressWarnings("unchecked") public SqmListJoin joinList(String attributeName, JoinType jt) { - final SqmPathSource joinedPathSource = getReferencedPathSource().getSubPathSource( attributeName ); + final var joinedPathSource = getReferencedPathSource().getSubPathSource( attributeName ); if ( joinedPathSource instanceof ListPersistentAttribute ) { - final SqmListJoin join = buildListJoin( + final var join = buildListJoin( (ListPersistentAttribute) joinedPathSource, SqmJoinType.from( jt ), false @@ -541,10 +549,10 @@ public SqmMapJoin joinMap(String attributeName) { @Override @SuppressWarnings("unchecked") public SqmMapJoin joinMap(String attributeName, JoinType jt) { - final SqmPathSource joinedPathSource = getReferencedPathSource().getSubPathSource( attributeName ); + final var joinedPathSource = getReferencedPathSource().getSubPathSource( attributeName ); if ( joinedPathSource instanceof MapPersistentAttribute ) { - final SqmMapJoin join = buildMapJoin( + final var join = buildMapJoin( (MapPersistentAttribute) joinedPathSource, SqmJoinType.from( jt ), false @@ -582,8 +590,8 @@ public SqmEntityJoin join(Class entityJavaType, JoinType joinType) @Override public SqmEntityJoin join(EntityType entity, JoinType joinType) { //noinspection unchecked - final SqmEntityJoin join = - new SqmEntityJoin<>( entity, generateAlias(), joinType, (SqmRoot) findRoot() ); + final var root = (SqmRoot) findRoot(); + final var join = new SqmEntityJoin<>( entity, generateAlias(), joinType, root ); addSqmJoin( join ); return join; } @@ -616,11 +624,11 @@ public JpaDerivedJoin join(Subquery subquery, SqmJoinType joinType, bo public JpaDerivedJoin join(Subquery subquery, SqmJoinType joinType, boolean lateral, String alias) { validateComplianceFromSubQuery(); //noinspection unchecked - final JpaDerivedJoin join = + final var derivedJoin = new SqmDerivedJoin<>( (SqmSubQuery) subquery, alias, joinType, lateral, (SqmRoot) findRoot() ); //noinspection unchecked - addSqmJoin( (SqmJoin) join ); - return join; + addSqmJoin( (SqmJoin) derivedJoin ); + return derivedJoin; } @Override @@ -636,11 +644,11 @@ public SqmJoin join(JpaCteCriteria cte, SqmJoinType joinType) { public SqmJoin join(JpaCteCriteria cte, SqmJoinType joinType, String alias) { validateComplianceFromSubQuery(); //noinspection unchecked - final SqmJoin join = + final var cteJoin = new SqmCteJoin<>( ( SqmCteStatement ) cte, alias, joinType, (SqmRoot) findRoot() ); //noinspection unchecked - addSqmJoin( (SqmJoin) join ); - return join; + addSqmJoin( (SqmJoin) cteJoin ); + return cteJoin; } @Override @@ -667,7 +675,7 @@ public JpaFunctionJoin join(JpaSetReturningFunction function) { public JpaFunctionJoin join(JpaSetReturningFunction function, SqmJoinType joinType, boolean lateral) { validateComplianceFromFunction(); //noinspection unchecked - final SqmFunctionJoin join = + final var functionJoin = new SqmFunctionJoin<>( (SqmSetReturningFunction) function, generateAlias(), @@ -675,8 +683,8 @@ public JpaFunctionJoin join(JpaSetReturningFunction function, SqmJoinT (SqmRoot) findRoot() ); //noinspection unchecked - addSqmJoin( (SqmJoin) join ); - return join; + addSqmJoin( (SqmJoin) functionJoin ); + return functionJoin; } @Override @@ -744,7 +752,7 @@ public JpaCrossJoin crossJoin(Class entityJavaType) { @Override public JpaCrossJoin crossJoin(EntityDomainType entity) { - final SqmCrossJoin crossJoin = + final var crossJoin = new SqmCrossJoin<>( (SqmEntityDomainType) entity, generateAlias(), findRoot() ); // noinspection unchecked addSqmJoin( (SqmJoin) crossJoin ); @@ -768,14 +776,13 @@ public SqmSingularJoin fetch(SingularAttribute attribute) @Override public SqmSingularJoin fetch(SingularAttribute attribute, JoinType jt) { final var persistentAttribute = (SqmSingularPersistentAttribute) attribute; - final SqmAttributeJoin compatibleFetchJoin = + final var compatibleFetchJoin = findCompatibleFetchJoin( this, persistentAttribute, SqmJoinType.from( jt ) ); if ( compatibleFetchJoin != null ) { return (SqmSingularJoin) compatibleFetchJoin; } - final SqmSingularJoin join = - buildSingularJoin( persistentAttribute, SqmJoinType.from( jt ), true ); + final var join = buildSingularJoin( persistentAttribute, SqmJoinType.from( jt ), true ); addSqmJoin( join ); return join; } @@ -816,15 +823,14 @@ private SqmAttributeJoin buildJoin( SqmJoinType joinType, boolean fetched) { if ( fetched ) { - final SqmAttributeJoin compatibleFetchJoin = + final var compatibleFetchJoin = findCompatibleFetchJoin( this, joinedPathSource, joinType ); if ( compatibleFetchJoin != null ) { return compatibleFetchJoin; } } - final SqmAttributeJoin sqmJoin = - buildAttributeJoin( joinedPathSource, joinType, fetched ); + final var sqmJoin = buildAttributeJoin( joinedPathSource, joinType, fetched ); addSqmJoin( sqmJoin ); return sqmJoin; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmJoin.java index f0bc2d5ceb4d..dbee0745575e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmJoin.java @@ -57,20 +57,18 @@ public void setJoinPredicate(@Nullable SqmPredicate predicate) { LOG.tracef( "Setting join predicate [%s] (was [%s])", predicate, - this.onClausePredicate == null ? "" : this.onClausePredicate + onClausePredicate == null ? "" : this.onClausePredicate ); } - this.onClausePredicate = predicate; + onClausePredicate = predicate; } public void applyRestriction(SqmPredicate restriction) { - if ( this.onClausePredicate == null ) { - this.onClausePredicate = restriction; - } - else { - this.onClausePredicate = combinePredicates( onClausePredicate, restriction ); - } + onClausePredicate = + onClausePredicate == null + ? restriction + : combinePredicates( onClausePredicate, restriction ); } protected void copyTo(AbstractSqmJoin target, SqmCopyContext context) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmPath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmPath.java index 41963d633b34..d8dab6a5801a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmPath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmPath.java @@ -16,7 +16,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.AssertionFailure; import org.hibernate.metamodel.mapping.CollectionPart; -import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.ManagedDomainType; import org.hibernate.metamodel.model.domain.PersistentAttribute; @@ -38,6 +37,7 @@ import static java.util.Collections.emptyList; import static org.hibernate.internal.util.NullnessUtil.castNonNull; +import static org.hibernate.metamodel.mapping.EntityDiscriminatorMapping.DISCRIMINATOR_ROLE_NAME; /** * @author Steve Ebersole @@ -73,22 +73,27 @@ protected void copyTo(AbstractSqmPath target, SqmCopyContext context) { // meant for assertions only private boolean navigablePathsMatch(AbstractSqmPath target) { - final SqmPath lhs = getLhsOrRoot(); - final SqmPath targetLhs = target.getLhsOrRoot(); + final var lhs = getLhsOrRoot(); + final var targetLhs = target.getLhsOrRoot(); return lhs == null && targetLhs == null - || lhs != null && targetLhs != null - && (lhs.getNavigablePath() == targetLhs.getNavigablePath() - || getRoot( lhs ).getNodeType() instanceof SqmPolymorphicRootDescriptor - ); + || lhs != null && targetLhs != null && ( samePath( lhs, targetLhs ) || isPolymorphicRoot( lhs ) ); + } + + private static boolean samePath(SqmPath lhs, SqmPath targetLhs) { + return lhs.getNavigablePath() == targetLhs.getNavigablePath(); + } + + private boolean isPolymorphicRoot(SqmPath lhs) { + return getRoot( lhs ).getNodeType() instanceof SqmPolymorphicRootDescriptor; } private @Nullable SqmPath getLhsOrRoot() { - final SqmPath lhs = getLhs(); + final var lhs = getLhs(); return lhs != null ? lhs : findRoot(); } private SqmPath getRoot(SqmPath lhs) { - final SqmPath parent = lhs.getLhs(); + final var parent = lhs.getLhs(); return parent == null ? lhs : getRoot( parent ); } @@ -131,9 +136,9 @@ public void registerReusablePath(SqmPath path) { reusablePaths = new HashMap<>(); } final String relativeName = path.getNavigablePath().getLocalName(); - final SqmPath previous = reusablePaths.put( relativeName, path ); + final var previous = reusablePaths.put( relativeName, path ); if ( previous != null && previous != path ) { - throw new IllegalStateException( "Implicit-join path registration unexpectedly overrode previous registration - " + relativeName ); + throw new IllegalStateException( "Implicit join path registration unexpectedly overrode previous registration - " + relativeName ); } } @@ -159,11 +164,12 @@ public SqmPathSource getModel() { @Override public SqmPathSource getResolvedModel() { - final SqmPathSource pathSource = getReferencedPathSource(); - final SqmPath lhs = getLhs(); + final var pathSource = getReferencedPathSource(); + final var lhs = getLhs(); if ( pathSource.isGeneric() && lhs != null && lhs.getResolvedModel().getPathType() instanceof SqmManagedDomainType lhsType ) { - final var concreteAttribute = lhsType.findConcreteGenericAttribute( pathSource.getPathName() ); + final var concreteAttribute = + lhsType.findConcreteGenericAttribute( pathSource.getPathName() ); if ( concreteAttribute != null ) { //noinspection unchecked return (SqmPathSource) concreteAttribute; @@ -189,34 +195,31 @@ public SqmPathSource getResolvedModel() { @Override public SqmExpression> type() { - final SqmPathSource referencedPathSource = getReferencedPathSource(); - final SqmPathSource subPathSource = - referencedPathSource.findSubPathSource( EntityDiscriminatorMapping.DISCRIMINATOR_ROLE_NAME ); + final var referencedPathSource = getReferencedPathSource(); + final var subPathSource = + referencedPathSource.findSubPathSource( DISCRIMINATOR_ROLE_NAME ); if ( subPathSource == null ) { - return new SqmLiteral<>( - referencedPathSource.getBindableJavaType(), + return new SqmLiteral<>( referencedPathSource.getBindableJavaType(), nodeBuilder().getTypeConfiguration().getBasicTypeForJavaType( Class.class ), - nodeBuilder() - ); + nodeBuilder() ); } else { @SuppressWarnings("unchecked") final var discriminatorSource = (SqmPathSource>) subPathSource; - return resolvePath( EntityDiscriminatorMapping.DISCRIMINATOR_ROLE_NAME, discriminatorSource ); + return resolvePath( DISCRIMINATOR_ROLE_NAME, discriminatorSource ); } } @Override public SqmPath get(String attributeName) { @SuppressWarnings("unchecked") - final SqmPathSource subNavigable = (SqmPathSource) getResolvedModel().getSubPathSource( attributeName ); + final var subNavigable = (SqmPathSource) getResolvedModel().getSubPathSource( attributeName ); return resolvePath( attributeName, subNavigable ); } @Override - public SqmPath get(String attributeName, boolean includeSubtypes) { - @SuppressWarnings("unchecked") - final SqmPathSource subPathSource = (SqmPathSource) + public SqmPath get(String attributeName, boolean includeSubtypes) { + final var subPathSource = (SqmPathSource) getResolvedModel().getSubPathSource( attributeName, includeSubtypes ); return resolvePath( attributeName, subPathSource ); } @@ -227,11 +230,12 @@ protected SqmPath resolvePath(PersistentAttribute attribute) { } protected SqmPath resolvePath(String attributeName, SqmPathSource pathSource) { - final SqmPathSource intermediatePathSource = - getResolvedModel().getIntermediatePathSource( pathSource ); + final var intermediatePathSource = + getResolvedModel() + .getIntermediatePathSource( pathSource ); if ( reusablePaths == null ) { reusablePaths = new HashMap<>(); - final SqmPath path = pathSource.createSqmPath( this, intermediatePathSource ); + final var path = pathSource.createSqmPath( this, intermediatePathSource ); reusablePaths.put( attributeName, path ); return path; } @@ -244,28 +248,29 @@ protected SqmPath resolvePath(String attributeName, SqmPathSource path } protected SqmTreatedPath getTreatedPath(ManagedDomainType treatTarget) { - final NavigablePath treat = getNavigablePath().treatAs( treatTarget.getTypeName() ); - final SqmPath lhs = castNonNull( getLhs() ); - final SqmPath reusablePath = lhs.getReusablePath( treat.getLocalName() ); - //TODO: check this cast - @SuppressWarnings("unchecked") - final SqmTreatedPath path = (SqmTreatedPath) reusablePath; + final var treat = getNavigablePath().treatAs( treatTarget.getTypeName() ); + final var lhs = castNonNull( getLhs() ); + final var path = lhs.getReusablePath( treat.getLocalName() ); if ( path == null ) { - final SqmTreatedPath treatedPath; - if ( treatTarget instanceof SqmEntityDomainType entityDomainType ) { - treatedPath = new SqmTreatedEntityValuedSimplePath<>( this, entityDomainType, nodeBuilder() ); - } - else if ( treatTarget instanceof SqmEmbeddableDomainType embeddableDomainType ) { - treatedPath = new SqmTreatedEmbeddedValuedSimplePath<>( this, embeddableDomainType ); - } - else { - throw new AssertionFailure( "Unrecognized treat target type: " + treatTarget.getTypeName() ); - } + final var treatedPath = treat( treatTarget ); lhs.registerReusablePath( treatedPath ); return treatedPath; } else { - return path; + //TODO: check this cast + return (SqmTreatedPath) path; + } + } + + private SqmTreatedPath treat(ManagedDomainType treatTarget) { + if ( treatTarget instanceof SqmEntityDomainType entityDomainType ) { + return new SqmTreatedEntityValuedSimplePath<>( this, entityDomainType, nodeBuilder() ); + } + else if ( treatTarget instanceof SqmEmbeddableDomainType embeddableDomainType ) { + return new SqmTreatedEmbeddedValuedSimplePath<>( this, embeddableDomainType ); + } + else { + throw new AssertionFailure( "Unrecognized treat target type: " + treatTarget.getTypeName() ); } } @@ -304,10 +309,9 @@ public SqmTreatedPath treatAs(EntityDomainType treatTarge * and if not creates a copy of the navigable path with the correct parent. */ protected NavigablePath getNavigablePathCopy(SqmPath parent) { - final NavigablePath realParentPath = getRealParentPath( - castNonNull( navigablePath.getRealParent() ), - parent.getNavigablePath() - ); + final var realParentPath = + getRealParentPath( castNonNull( navigablePath.getRealParent() ), + parent.getNavigablePath() ); if ( realParentPath != null ) { return realParentPath.append( navigablePath.getLocalName(), navigablePath.getAlias() ); } @@ -315,38 +319,34 @@ protected NavigablePath getNavigablePathCopy(SqmPath parent) { } private @Nullable NavigablePath getRealParentPath(NavigablePath realParent, NavigablePath parent) { - @Nullable NavigablePath realParentPath; if ( parent == realParent ) { - realParentPath = null; + return null; } - else if ( realParent instanceof EntityIdentifierNavigablePath entityIdentifierNavigablePath ) { - realParentPath = getRealParentPath( castNonNull( realParent.getRealParent() ), parent ); - if ( realParentPath != null ) { - realParentPath = new EntityIdentifierNavigablePath( - realParentPath, - entityIdentifierNavigablePath.getIdentifierAttributeName() - ); + else { + final var realParentParent = realParent.getRealParent(); + if ( realParent instanceof EntityIdentifierNavigablePath entityIdentifierNavigablePath ) { + final var realParentPath = getRealParentPath( castNonNull( realParentParent ), parent ); + return realParentPath != null + ? new EntityIdentifierNavigablePath( realParentPath, + entityIdentifierNavigablePath.getIdentifierAttributeName() ) + : null; } - } - else if ( realParent.getAlias() == null && realParent instanceof TreatedNavigablePath ) { - // This might be an implicitly treated parent path, check with the non-treated parent - realParentPath = getRealParentPath( castNonNull( realParent.getRealParent() ), parent ); - if ( realParentPath != null ) { - realParentPath = realParentPath.treatAs( realParent.getLocalName().substring( 1 ) ); + else if ( realParent instanceof TreatedNavigablePath && realParent.getAlias() == null ) { + // This might be an implicitly treated parent path, check with the non-treated parent + final var realParentPath = getRealParentPath( castNonNull( realParentParent ), parent ); + return realParentPath != null + ? realParentPath.treatAs( realParent.getLocalName().substring( 1 ) ) + : null; } - } - else if ( CollectionPart.Nature.fromNameExact( realParent.getLocalName() ) != null ) { - if ( parent == realParent.getRealParent() ) { - realParentPath = null; + else if ( CollectionPart.Nature.fromNameExact( realParent.getLocalName() ) != null ) { + return parent == realParentParent + ? null + : parent.append( realParent.getLocalName() ); } else { - realParentPath = parent.append( realParent.getLocalName() ); + return parent; } } - else { - realParentPath = parent; - } - return realParentPath; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmSimplePath.java index 5a2508e074d2..14acc0b67729 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmSimplePath.java @@ -63,7 +63,7 @@ public void appendHqlString(StringBuilder hql, SqmRenderContext context) { @Override public SqmPathSource getReferencedPathSource() { - final SqmPathSource pathSource = super.getReferencedPathSource(); + final var pathSource = super.getReferencedPathSource(); return pathSource instanceof SqmSingularPersistentAttribute attribute ? attribute.getSqmPathSource() : pathSource; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/NonAggregatedCompositeSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/NonAggregatedCompositeSimplePath.java index 0336e139d421..38a4755dec23 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/NonAggregatedCompositeSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/NonAggregatedCompositeSimplePath.java @@ -32,13 +32,13 @@ public NonAggregatedCompositeSimplePath( @Override public NonAggregatedCompositeSimplePath copy(SqmCopyContext context) { - final NonAggregatedCompositeSimplePath existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmPath lhsCopy = getLhs().copy( context ); - final NonAggregatedCompositeSimplePath path = context.registerCopy( + final var lhsCopy = getLhs().copy( context ); + final var path = context.registerCopy( this, new NonAggregatedCompositeSimplePath<>( getNavigablePathCopy( lhsCopy ), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmAnyValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmAnyValuedSimplePath.java index a9cb529f5843..a9ecb6eb5602 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmAnyValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmAnyValuedSimplePath.java @@ -42,13 +42,13 @@ public SqmAnyValuedSimplePath( @Override public SqmAnyValuedSimplePath copy(SqmCopyContext context) { - final SqmAnyValuedSimplePath existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmPath lhsCopy = getLhs().copy( context ); - final SqmAnyValuedSimplePath path = context.registerCopy( + final var lhsCopy = getLhs().copy( context ); + final var path = context.registerCopy( this, new SqmAnyValuedSimplePath<>( getNavigablePathCopy( lhsCopy ), @@ -97,7 +97,7 @@ public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - final SqmPath sqmPath = get( name, true ); + final var sqmPath = get( name, true ); creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); return sqmPath; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java index 56f0734df782..0261947e3b76 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java @@ -51,12 +51,12 @@ protected SqmBagJoin( @Override public SqmBagJoin copy(SqmCopyContext context) { - final SqmBagJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmFrom lhsCopy = getLhs().copy( context ); - final SqmBagJoin path = context.registerCopy( + final var lhsCopy = getLhs().copy( context ); + final var path = context.registerCopy( this, new SqmBagJoin<>( lhsCopy, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java index fd9da0ce5153..63a5e462e3a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java @@ -8,7 +8,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.model.domain.EntityDomainType; -import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.sqm.SqmBindableType; import org.hibernate.query.sqm.UnknownPathException; @@ -54,13 +53,13 @@ public SqmBasicValuedSimplePath( @Override public SqmBasicValuedSimplePath copy(SqmCopyContext context) { - final SqmBasicValuedSimplePath existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmPath lhsCopy = getLhs().copy( context ); - final SqmBasicValuedSimplePath path = context.registerCopy( + final var lhsCopy = getLhs().copy( context ); + final var path = context.registerCopy( this, new SqmBasicValuedSimplePath<>( getNavigablePathCopy( lhsCopy ), @@ -105,7 +104,7 @@ public SqmPath resolveIndexedAccess( SqmExpression selector, boolean isTerminal, SqmCreationState creationState) { - final SqmPathRegistry pathRegistry = + final var pathRegistry = creationState.getCurrentProcessingState().getPathRegistry(); final String alias = selector.toHqlString(); final NavigablePath navigablePath = diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java index db109cab8805..70b1f411f211 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java @@ -50,11 +50,11 @@ private SqmCorrelatedBagJoin( @Override public SqmCorrelatedBagJoin copy(SqmCopyContext context) { - final SqmCorrelatedBagJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCorrelatedBagJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCorrelatedBagJoin<>( getLhs().copy( context ), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java index f0b3250a89fc..e3865dab2c08 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java @@ -6,7 +6,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.query.hql.spi.SqmCreationProcessingState; -import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.from.SqmCrossJoin; @@ -45,11 +44,11 @@ private SqmCorrelatedCrossJoin( @Override public SqmCorrelatedCrossJoin copy(SqmCopyContext context) { - final SqmCorrelatedCrossJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCorrelatedCrossJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCorrelatedCrossJoin<>( getReferencedPathSource(), @@ -85,7 +84,7 @@ public SqmRoot getCorrelatedRoot() { @Override public SqmCorrelatedCrossJoin makeCopy(SqmCreationProcessingState creationProcessingState) { - final SqmPathRegistry pathRegistry = creationProcessingState.getPathRegistry(); + final var pathRegistry = creationProcessingState.getPathRegistry(); return new SqmCorrelatedCrossJoin<>( getReferencedPathSource(), getExplicitAlias(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCteJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCteJoin.java index fdec8d254534..736874882396 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCteJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCteJoin.java @@ -51,11 +51,11 @@ private SqmCorrelatedCteJoin( @Override public SqmCorrelatedCteJoin copy(SqmCopyContext context) { - final SqmCorrelatedCteJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCorrelatedCteJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCorrelatedCteJoin<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedDerivedJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedDerivedJoin.java index 31f4f204d265..730d2ba7331f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedDerivedJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedDerivedJoin.java @@ -55,11 +55,11 @@ private SqmCorrelatedDerivedJoin( @Override public SqmCorrelatedDerivedJoin copy(SqmCopyContext context) { - final SqmCorrelatedDerivedJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCorrelatedDerivedJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCorrelatedDerivedJoin<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedDerivedRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedDerivedRoot.java index 9a1e35a0b96f..d31a92765f97 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedDerivedRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedDerivedRoot.java @@ -32,11 +32,11 @@ private SqmCorrelatedDerivedRoot(SqmRoot correlationParent) { @Override public SqmCorrelatedDerivedRoot copy(SqmCopyContext context) { - final SqmCorrelatedDerivedRoot existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCorrelatedDerivedRoot path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCorrelatedDerivedRoot<>( getCorrelationParent().copy( context ) ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedDerivedRootJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedDerivedRootJoin.java index 3e26123b3b11..a9507348376e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedDerivedRootJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedDerivedRootJoin.java @@ -26,11 +26,11 @@ public SqmCorrelatedDerivedRootJoin( @Override public SqmCorrelatedDerivedRootJoin copy(SqmCopyContext context) { - final SqmCorrelatedDerivedRootJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCorrelatedDerivedRootJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCorrelatedDerivedRootJoin<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java index 3cc0ae64d453..10741af724a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java @@ -7,7 +7,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.hql.spi.SqmCreationProcessingState; -import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; @@ -49,11 +48,11 @@ public SqmCorrelatedEntityJoin( @Override public SqmCorrelatedEntityJoin copy(SqmCopyContext context) { - final SqmCorrelatedEntityJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCorrelatedEntityJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCorrelatedEntityJoin<>( getReferencedPathSource(), @@ -105,7 +104,7 @@ public SqmCorrelatedEntityJoin createCorrelation() { @Override public SqmCorrelatedEntityJoin makeCopy(SqmCreationProcessingState creationProcessingState) { - final SqmPathRegistry pathRegistry = creationProcessingState.getPathRegistry(); + final var pathRegistry = creationProcessingState.getPathRegistry(); return new SqmCorrelatedEntityJoin<>( getReferencedPathSource(), getExplicitAlias(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java index fc9b039bcca7..6b5804e61690 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java @@ -50,11 +50,11 @@ private SqmCorrelatedListJoin( @Override public SqmCorrelatedListJoin copy(SqmCopyContext context) { - final SqmCorrelatedListJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCorrelatedListJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCorrelatedListJoin<>( getLhs().copy( context ), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java index 0bb8c3b42bb2..9d07a56777b4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java @@ -50,11 +50,11 @@ private SqmCorrelatedMapJoin( @Override public SqmCorrelatedMapJoin copy(SqmCopyContext context) { - final SqmCorrelatedMapJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCorrelatedMapJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCorrelatedMapJoin<>( getLhs().copy( context ), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java index 496c55a9c44d..1d5b2ebeed43 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java @@ -33,11 +33,11 @@ public SqmCorrelatedPluralPartJoin(SqmPluralPartJoin correlationParent) { @Override public SqmCorrelatedPluralPartJoin copy(SqmCopyContext context) { - final SqmCorrelatedPluralPartJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCorrelatedPluralPartJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCorrelatedPluralPartJoin<>( correlationParent.copy( context ) ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java index 0d0824267277..40969d7fd864 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java @@ -38,11 +38,11 @@ protected SqmCorrelatedRoot(NavigablePath navigablePath, SqmPathSource refere @Override public SqmCorrelatedRoot copy(SqmCopyContext context) { - final SqmCorrelatedRoot existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCorrelatedRoot path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCorrelatedRoot<>( correlationParent.copy( context ) ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java index 0a77643f4ee5..314d58fdbf2c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java @@ -28,11 +28,11 @@ public SqmCorrelatedRootJoin( @Override public SqmCorrelatedRootJoin copy(SqmCopyContext context) { - final SqmCorrelatedRootJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCorrelatedRootJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCorrelatedRootJoin<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java index 21abbcec1ecc..999ca4e304b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java @@ -50,11 +50,11 @@ private SqmCorrelatedSetJoin( @Override public SqmCorrelatedSetJoin copy(SqmCopyContext context) { - final SqmCorrelatedSetJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCorrelatedSetJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCorrelatedSetJoin<>( getLhs().copy( context ), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java index 3db5f80c14eb..a83133c851d1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java @@ -50,11 +50,11 @@ private SqmCorrelatedSingularJoin( @Override public SqmCorrelatedSingularJoin copy(SqmCopyContext context) { - final SqmCorrelatedSingularJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCorrelatedSingularJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCorrelatedSingularJoin<>( getLhs().copy( context ), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCteRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCteRoot.java index a1d5973902d9..850a56e27aed 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCteRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCteRoot.java @@ -54,11 +54,11 @@ protected SqmCteRoot( @Override public SqmCteRoot copy(SqmCopyContext context) { - final SqmCteRoot existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCteRoot path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCteRoot<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmDerivedRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmDerivedRoot.java index 05ea4470d4be..ba28e6da5df5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmDerivedRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmDerivedRoot.java @@ -55,11 +55,11 @@ protected SqmDerivedRoot( @Override public SqmDerivedRoot copy(SqmCopyContext context) { - final SqmDerivedRoot existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmDerivedRoot path = context.registerCopy( + final var path = context.registerCopy( this, new SqmDerivedRoot<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmElementAggregateFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmElementAggregateFunction.java index cb46ed519897..c099cd2079c6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmElementAggregateFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmElementAggregateFunction.java @@ -13,7 +13,6 @@ import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmBindableType; -import org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmRenderContext; @@ -38,32 +37,28 @@ public SqmElementAggregateFunction(SqmPluralValuedSimplePath pluralDomainPath .getElementPathSource() ); this.functionName = functionName; - final SqmCriteriaNodeBuilder nodeBuilder = pluralDomainPath.nodeBuilder(); - switch ( functionName ) { - case "sum": - //noinspection unchecked - this.returnableType = (ReturnableType) nodeBuilder.getSumReturnTypeResolver() - .resolveFunctionReturnType( - null, - (SqmToSqlAstConverter) null, - List.of( pluralDomainPath ), - nodeBuilder.getTypeConfiguration() - ); - break; - case "avg": - //noinspection unchecked - this.returnableType = (ReturnableType) nodeBuilder.getAvgReturnTypeResolver() - .resolveFunctionReturnType( - null, - (SqmToSqlAstConverter) null, - List.of( pluralDomainPath ), - nodeBuilder.getTypeConfiguration() - ); - break; - default: - this.returnableType = null; - break; - } + final var nodeBuilder = pluralDomainPath.nodeBuilder(); + final var type = switch ( functionName ) { + case "sum" -> + nodeBuilder.getSumReturnTypeResolver() + .resolveFunctionReturnType( + null, + (SqmToSqlAstConverter) null, + List.of( pluralDomainPath ), + nodeBuilder.getTypeConfiguration() + ); + case "avg" -> + nodeBuilder.getAvgReturnTypeResolver() + .resolveFunctionReturnType( + null, + (SqmToSqlAstConverter) null, + List.of( pluralDomainPath ), + nodeBuilder.getTypeConfiguration() + ); + default -> null; + }; + //noinspection unchecked + returnableType = (ReturnableType) type; } @Override @@ -87,14 +82,14 @@ public SqmElementAggregateFunction(SqmPluralValuedSimplePath pluralDomainPath @Override public SqmElementAggregateFunction copy(SqmCopyContext context) { - final SqmElementAggregateFunction existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmElementAggregateFunction path = context.registerCopy( + final var path = context.registerCopy( this, - new SqmElementAggregateFunction<>( + new SqmElementAggregateFunction( getPluralDomainPath().copy( context ), functionName ) @@ -112,7 +107,7 @@ public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - final SqmPath sqmPath = get( name, true ); + final var sqmPath = get( name, true ); creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); return sqmPath; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java index c84032b4457f..8e6ca7368536 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java @@ -49,13 +49,13 @@ public SqmEmbeddedValuedSimplePath( @Override public SqmEmbeddedValuedSimplePath copy(SqmCopyContext context) { - final SqmEmbeddedValuedSimplePath existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } final SqmPath lhsCopy = getLhs().copy( context ); - final SqmEmbeddedValuedSimplePath path = context.registerCopy( + final var path = context.registerCopy( this, new SqmEmbeddedValuedSimplePath<>( getNavigablePathCopy( lhsCopy ), @@ -86,7 +86,7 @@ public PersistenceType getPersistenceType() { @Override public SqmPath resolvePathPart(String name, boolean isTerminal, SqmCreationState creationState) { - final SqmPath sqmPath = get( name, true ); + final var sqmPath = get( name, true ); creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); return sqmPath; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java index a3ba68ecf625..f2adc59d68a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java @@ -28,13 +28,13 @@ public SqmEntityValuedSimplePath( @Override public SqmEntityValuedSimplePath copy(SqmCopyContext context) { - final SqmEntityValuedSimplePath existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } final SqmPath lhsCopy = getLhs().copy( context ); - final SqmEntityValuedSimplePath path = context.registerCopy( + final var path = context.registerCopy( this, new SqmEntityValuedSimplePath<>( getNavigablePathCopy( lhsCopy ), @@ -52,7 +52,7 @@ public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - final SqmPath sqmPath = get( name, true ); + final var sqmPath = get( name, true ); creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); return sqmPath; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFkExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFkExpression.java index 73f18ff4d2bd..3d3085f48431 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFkExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFkExpression.java @@ -6,7 +6,6 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; -import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.IdentifiableDomainType; import org.hibernate.query.hql.spi.SqmCreationState; @@ -37,19 +36,22 @@ private SqmFkExpression( SqmPath toOnePath) { super( navigablePath, - (SqmPathSource) castNonNull( pathDomainType( toOnePath ).getIdentifierDescriptor() ), + (SqmPathSource) + castNonNull( pathDomainType( toOnePath ) + .getIdentifierDescriptor() ), toOnePath, toOnePath.nodeBuilder() ); } private static IdentifiableDomainType pathDomainType(SqmPath toOnePath) { - final DomainType domainType = toOnePath.getReferencedPathSource().getPathType(); - if ( domainType instanceof IdentifiableDomainType identifiableDomainType ) { + if ( toOnePath.getReferencedPathSource().getPathType() + instanceof IdentifiableDomainType identifiableDomainType ) { return identifiableDomainType; } else { - throw new IllegalArgumentException( "Invalid path provided to 'fk()' function: " + toOnePath.getNavigablePath() ); + throw new IllegalArgumentException( "Invalid path provided to 'fk()' function: " + + toOnePath.getNavigablePath() ); } } @@ -72,11 +74,11 @@ public void appendHqlString(StringBuilder hql, SqmRenderContext context) { @Override public SqmFkExpression copy(SqmCopyContext context) { - final SqmFkExpression existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmEntityValuedSimplePath lhsCopy = (SqmEntityValuedSimplePath) getLhs().copy( context ); + final var lhsCopy = (SqmEntityValuedSimplePath) getLhs().copy( context ); return context.registerCopy( this, new SqmFkExpression<>( getNavigablePathCopy( lhsCopy ), lhsCopy ) @@ -95,7 +97,7 @@ public SqmTreatedPath treatAs(EntityDomainType treatTarget @Override public SqmPath resolvePathPart(String name, boolean isTerminal, SqmCreationState creationState) { - final SqmPath sqmPath = get( name, true ); + final var sqmPath = get( name, true ); creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); return sqmPath; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFunctionPath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFunctionPath.java index 3ed186cdae57..5f5035dc36b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFunctionPath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFunctionPath.java @@ -10,8 +10,6 @@ import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource; import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource; import org.hibernate.query.hql.spi.SqmCreationState; -import org.hibernate.query.hql.spi.SqmPathRegistry; -import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmBindableType; import org.hibernate.query.sqm.SqmPathSource; @@ -21,13 +19,10 @@ import org.hibernate.query.sqm.tree.SqmRenderContext; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmFunction; -import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.spi.NavigablePath; import org.hibernate.type.BasicPluralType; -import org.hibernate.type.BasicType; import jakarta.persistence.metamodel.Bindable; -import jakarta.persistence.metamodel.ManagedType; import jakarta.persistence.metamodel.Type; @@ -52,17 +47,18 @@ private SqmFunctionPath(NavigablePath navigablePath, SqmFunction function) { private static SqmPathSource determinePathSource(NavigablePath navigablePath, SqmFunction function) { //noinspection unchecked - final SqmBindableType nodeType = (SqmBindableType) function.getNodeType(); + final var nodeType = (SqmBindableType) function.getNodeType(); if ( nodeType == null ) { throw new IllegalArgumentException( "Null return type for function: " + function.getFunctionName() ); } - final Class bindableJavaType = nodeType.getJavaType(); - final ManagedType managedType = function.nodeBuilder() - .getJpaMetamodel() - .findManagedType( bindableJavaType ); + final var bindableJavaType = nodeType.getJavaType(); + final var managedType = + function.nodeBuilder().getJpaMetamodel() + .findManagedType( bindableJavaType ); if ( managedType == null ) { - final BasicType basicType = function.nodeBuilder().getTypeConfiguration() - .getBasicTypeForJavaType( bindableJavaType ); + final var basicType = + function.nodeBuilder().getTypeConfiguration() + .getBasicTypeForJavaType( bindableJavaType ); if ( basicType == null ) { throw new IllegalArgumentException( "Couldn't determine basic type for java type: " + bindableJavaType.getName() ); } @@ -95,14 +91,15 @@ public SqmFunction getFunction() { @Override public SqmFunctionPath copy(SqmCopyContext context) { - final SqmFunctionPath existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmFunctionPath path = context.registerCopy( + final var path = context.registerCopy( this, - new SqmFunctionPath<>( getNavigablePath(), (SqmFunction) function.copy( context ) ) + new SqmFunctionPath( getNavigablePath(), + (SqmFunction) function.copy( context ) ) ); copyTo( path, context ); return path; @@ -113,7 +110,7 @@ public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - final SqmPath sqmPath = get( name, true ); + final var sqmPath = get( name, true ); creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); return sqmPath; } @@ -123,20 +120,18 @@ public SqmPath resolveIndexedAccess( SqmExpression selector, boolean isTerminal, SqmCreationState creationState) { - final SqmPathRegistry pathRegistry = creationState.getCurrentProcessingState().getPathRegistry(); - final String alias = selector.toHqlString(); - final NavigablePath navigablePath = getNavigablePath().append( - CollectionPart.Nature.ELEMENT.getName(), - alias - ); - final SqmFrom indexedPath = pathRegistry.findFromByPath( navigablePath ); + final var pathRegistry = creationState.getCurrentProcessingState().getPathRegistry(); + final var navigablePath = + getNavigablePath().append( CollectionPart.Nature.ELEMENT.getName(), + selector.toHqlString() ); + final var indexedPath = pathRegistry.findFromByPath( navigablePath ); if ( indexedPath != null ) { return indexedPath; } if ( !( getReferencedPathSource().getPathType() instanceof BasicPluralType ) ) { throw new UnsupportedOperationException( "Index access is only supported for basic plural types." ); } - final QueryEngine queryEngine = creationState.getCreationContext().getQueryEngine(); + final var queryEngine = creationState.getCreationContext().getQueryEngine(); final SelfRenderingSqmFunction result = queryEngine.getSqmFunctionRegistry() .getFunctionDescriptor( "array_get" ) .generateSqmExpression( @@ -144,7 +139,7 @@ public SqmPath resolveIndexedAccess( null, queryEngine ); - final SqmFunctionPath path = new SqmFunctionPath<>( result ); + final SqmFunctionPath path = new SqmFunctionPath<>( result ); pathRegistry.register( path ); return path; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFunctionRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFunctionRoot.java index a0464f45fdde..32535c433619 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFunctionRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFunctionRoot.java @@ -53,11 +53,11 @@ protected SqmFunctionRoot( @Override public SqmFunctionRoot copy(SqmCopyContext context) { - final SqmFunctionRoot existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmFunctionRoot path = context.registerCopy( + final var path = context.registerCopy( this, new SqmFunctionRoot<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexAggregateFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexAggregateFunction.java index 268f7aa2e580..6baa4c16208b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexAggregateFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexAggregateFunction.java @@ -14,7 +14,6 @@ import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmBindableType; import org.hibernate.query.sqm.SqmPathSource; -import org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmRenderContext; @@ -42,32 +41,28 @@ public SqmIndexAggregateFunction(SqmPluralValuedSimplePath pluralDomainPath, .getIndexPathSource() ); this.functionName = functionName; - final SqmCriteriaNodeBuilder nodeBuilder = pluralDomainPath.nodeBuilder(); - switch ( functionName ) { - case "sum": - //noinspection unchecked - this.returnableType = (ReturnableType) nodeBuilder.getSumReturnTypeResolver() - .resolveFunctionReturnType( - null, - (SqmToSqlAstConverter) null, - List.of( pluralDomainPath.get( CollectionPart.Nature.INDEX.getName() ) ), - nodeBuilder.getTypeConfiguration() - ); - break; - case "avg": - //noinspection unchecked - this.returnableType = (ReturnableType) nodeBuilder.getAvgReturnTypeResolver() - .resolveFunctionReturnType( - null, - (SqmToSqlAstConverter) null, - List.of( pluralDomainPath.get( CollectionPart.Nature.INDEX.getName() ) ), - nodeBuilder.getTypeConfiguration() - ); - break; - default: - this.returnableType = null; - break; - } + final var nodeBuilder = pluralDomainPath.nodeBuilder(); + final var type = switch ( functionName ) { + case "sum" -> + nodeBuilder.getSumReturnTypeResolver() + .resolveFunctionReturnType( + null, + (SqmToSqlAstConverter) null, + List.of( pluralDomainPath.get( CollectionPart.Nature.INDEX.getName() ) ), + nodeBuilder.getTypeConfiguration() + ); + case "avg" -> + nodeBuilder.getAvgReturnTypeResolver() + .resolveFunctionReturnType( + null, + (SqmToSqlAstConverter) null, + List.of( pluralDomainPath.get( CollectionPart.Nature.INDEX.getName() ) ), + nodeBuilder.getTypeConfiguration() + ); + default -> null; + }; + //noinspection unchecked + returnableType = (ReturnableType) type; } @Override @@ -91,14 +86,14 @@ public SqmIndexAggregateFunction(SqmPluralValuedSimplePath pluralDomainPath, @Override public SqmIndexAggregateFunction copy(SqmCopyContext context) { - final SqmIndexAggregateFunction existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmIndexAggregateFunction path = context.registerCopy( + final var path = context.registerCopy( this, - new SqmIndexAggregateFunction<>( + new SqmIndexAggregateFunction( getPluralDomainPath().copy( context ), functionName ) @@ -116,7 +111,7 @@ public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - final SqmPath sqmPath = get( name, true ); + final var sqmPath = get( name, true ); creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); return sqmPath; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexedCollectionAccessPath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexedCollectionAccessPath.java index b0191e880af8..8519d243c25d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexedCollectionAccessPath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexedCollectionAccessPath.java @@ -46,13 +46,13 @@ public SqmIndexedCollectionAccessPath( @Override public SqmIndexedCollectionAccessPath copy(SqmCopyContext context) { - final SqmIndexedCollectionAccessPath existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } final SqmAttributeJoin lhsCopy = getLhs().copy( context ); - final SqmIndexedCollectionAccessPath path = context.registerCopy( + final var path = context.registerCopy( this, new SqmIndexedCollectionAccessPath( getNavigablePathCopy( lhsCopy ), @@ -78,7 +78,7 @@ public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - final SqmPath sqmPath = get( name, true ); + final var sqmPath = get( name, true ); creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); return sqmPath; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java index 8ec14102919a..0268f27bc865 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java @@ -55,12 +55,12 @@ protected SqmListJoin( @Override public SqmListJoin copy(SqmCopyContext context) { - final SqmListJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } final SqmFrom lhsCopy = getLhs().copy( context ); - final SqmListJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmListJoin<>( lhsCopy, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapEntryReference.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapEntryReference.java index b246031821ea..99ec573cc3fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapEntryReference.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapEntryReference.java @@ -56,7 +56,7 @@ public SqmMapEntryReference( @Override public SqmMapEntryReference copy(SqmCopyContext context) { - final SqmMapEntryReference existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java index 5491d2b8ba70..434f752961f1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java @@ -55,12 +55,12 @@ protected SqmMapJoin( @Override public SqmMapJoin copy(SqmCopyContext context) { - final SqmMapJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } final SqmFrom lhsCopy = getLhs().copy( context ); - final SqmMapJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmMapJoin<>( lhsCopy, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPath.java index 40563779e159..7ea720558d49 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPath.java @@ -164,7 +164,7 @@ default SqmPath resolveIndexedAccess( * * @see SqmPathSource#findSubPathSource(String, boolean) */ - default SqmPath get(String attributeName, boolean includeSubtypes) { + default SqmPath get(String attributeName, boolean includeSubtypes) { return get( attributeName ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralPartJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralPartJoin.java index 2e6d88eae773..029d9ae48a5a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralPartJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralPartJoin.java @@ -67,12 +67,12 @@ public boolean isImplicitlySelectable() { @Override public SqmPluralPartJoin copy(SqmCopyContext context) { - final SqmPluralPartJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } final SqmFrom lhsCopy = getLhs().copy( context ); - final SqmPluralPartJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmPluralPartJoin<>( lhsCopy, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralValuedSimplePath.java index 50bdcdffd36b..4aec559e33dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralValuedSimplePath.java @@ -14,7 +14,6 @@ import org.hibernate.query.NotIndexedCollectionException; import org.hibernate.query.PathException; import org.hibernate.query.hql.spi.SqmCreationState; -import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmPathSource; @@ -58,13 +57,13 @@ public SqmPluralValuedSimplePath( @Override public SqmPluralValuedSimplePath copy(SqmCopyContext context) { - final SqmPluralValuedSimplePath existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } final SqmPath lhsCopy = getLhs().copy( context ); - final SqmPluralValuedSimplePath path = context.registerCopy( + final var path = context.registerCopy( this, new SqmPluralValuedSimplePath<>( getNavigablePathCopy( lhsCopy ), @@ -104,7 +103,7 @@ public SqmPath resolvePathPart( + "' refers to a collection and so element attribute '" + name + "' may not be referenced directly (use element() function)" ); } - final SqmPath sqmPath = get( name, true ); + final var sqmPath = get( name, true ); creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); return sqmPath; } @@ -114,7 +113,7 @@ public SqmPath resolveIndexedAccess( SqmExpression selector, boolean isTerminal, SqmCreationState creationState) { - final SqmPathRegistry pathRegistry = creationState.getCurrentProcessingState().getPathRegistry(); + final var pathRegistry = creationState.getCurrentProcessingState().getPathRegistry(); final String alias = selector.toHqlString(); final NavigablePath navigablePath = getParentNavigablePath() diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java index df28f49f1af1..81007629b943 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java @@ -53,12 +53,12 @@ protected SqmSetJoin( @Override public SqmSetJoin copy(SqmCopyContext context) { - final SqmSetJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } final SqmFrom lhsCopy = getLhs().copy( context ); - final SqmSetJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmSetJoin<>( lhsCopy, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java index a5300366ad8e..909a55c307a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java @@ -65,12 +65,12 @@ protected SqmSingularJoin( @Override public SqmSingularJoin copy(SqmCopyContext context) { - final SqmSingularJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } final SqmFrom lhsCopy = getLhs().copy( context ); - final SqmSingularJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmSingularJoin<>( lhsCopy, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedBagJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedBagJoin.java index e91085a45730..82219a2a04b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedBagJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedBagJoin.java @@ -78,11 +78,11 @@ private SqmTreatedBagJoin( @Override public SqmTreatedBagJoin copy(SqmCopyContext context) { - final SqmTreatedBagJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmTreatedBagJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmTreatedBagJoin<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEmbeddedValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEmbeddedValuedSimplePath.java index a69680b47fae..224fce35d086 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEmbeddedValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEmbeddedValuedSimplePath.java @@ -55,11 +55,11 @@ private SqmTreatedEmbeddedValuedSimplePath( @Override public SqmTreatedEmbeddedValuedSimplePath copy(SqmCopyContext context) { - final SqmTreatedEmbeddedValuedSimplePath existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmTreatedEmbeddedValuedSimplePath path = context.registerCopy( + final var path = context.registerCopy( this, new SqmTreatedEmbeddedValuedSimplePath<>( getNavigablePath(), @@ -104,7 +104,7 @@ public X accept(SemanticQueryWalker walker) { @Override public SqmPath resolvePathPart(String name, boolean isTerminal, SqmCreationState creationState) { - final SqmPath sqmPath = get( name, true ); + final var sqmPath = get( name, true ); creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); return sqmPath; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEntityJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEntityJoin.java index 202786f6d1ba..d2b5413df139 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEntityJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEntityJoin.java @@ -60,11 +60,11 @@ private SqmTreatedEntityJoin( @Override public SqmTreatedEntityJoin copy(SqmCopyContext context) { - final SqmTreatedEntityJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmTreatedEntityJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmTreatedEntityJoin<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEntityValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEntityValuedSimplePath.java index 7081f2d5cef7..398ae2c8cf3c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEntityValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEntityValuedSimplePath.java @@ -77,12 +77,12 @@ private SqmTreatedEntityValuedSimplePath( @Override public SqmTreatedEntityValuedSimplePath copy(SqmCopyContext context) { - final SqmTreatedEntityValuedSimplePath existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmTreatedEntityValuedSimplePath path = context.registerCopy( + final var path = context.registerCopy( this, new SqmTreatedEntityValuedSimplePath<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedListJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedListJoin.java index eea6d196161f..9f72b682d51b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedListJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedListJoin.java @@ -78,11 +78,11 @@ private SqmTreatedListJoin( @Override public SqmTreatedListJoin copy(SqmCopyContext context) { - final SqmTreatedListJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmTreatedListJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmTreatedListJoin<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedMapJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedMapJoin.java index 631a27f08278..930bb707e04d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedMapJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedMapJoin.java @@ -77,11 +77,11 @@ private SqmTreatedMapJoin( @Override public SqmTreatedMapJoin copy(SqmCopyContext context) { - final SqmTreatedMapJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmTreatedMapJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmTreatedMapJoin<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedPluralPartJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedPluralPartJoin.java index 3d53621d86a3..29b3a19b7e90 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedPluralPartJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedPluralPartJoin.java @@ -18,12 +18,12 @@ */ @SuppressWarnings("rawtypes") public class SqmTreatedPluralPartJoin extends SqmPluralPartJoin implements SqmTreatedJoin { - private final SqmPluralPartJoin wrappedPath; - private final SqmEntityDomainType treatTarget; + private final SqmPluralPartJoin wrappedPath; + private final SqmEntityDomainType treatTarget; public SqmTreatedPluralPartJoin( - SqmPluralPartJoin wrappedPath, - SqmEntityDomainType treatTarget, + SqmPluralPartJoin wrappedPath, + SqmEntityDomainType treatTarget, @Nullable String alias) { //noinspection unchecked super( @@ -41,8 +41,8 @@ public SqmTreatedPluralPartJoin( private SqmTreatedPluralPartJoin( NavigablePath navigablePath, - SqmPluralPartJoin wrappedPath, - SqmEntityDomainType treatTarget, + SqmPluralPartJoin wrappedPath, + SqmEntityDomainType treatTarget, @Nullable String alias) { //noinspection unchecked super( @@ -59,11 +59,11 @@ private SqmTreatedPluralPartJoin( @Override public SqmTreatedPluralPartJoin copy(SqmCopyContext context) { - final SqmTreatedPluralPartJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmTreatedPluralPartJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmTreatedPluralPartJoin( getNavigablePath(), @@ -77,22 +77,22 @@ public SqmTreatedPluralPartJoin copy(SqmCopyContext context) { } @Override - public SqmPluralPartJoin getWrappedPath() { + public SqmPluralPartJoin getWrappedPath() { return wrappedPath; } @Override - public EntityDomainType getTreatTarget() { + public EntityDomainType getTreatTarget() { return treatTarget; } @Override - public @NonNull SqmBindableType getNodeType() { + public @NonNull SqmBindableType getNodeType() { return treatTarget; } @Override - public SqmPathSource getReferencedPathSource() { + public SqmPathSource getReferencedPathSource() { return treatTarget; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedRoot.java index 1279851a7657..bee6babd11b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedRoot.java @@ -116,7 +116,7 @@ public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - final SqmPath sqmPath = get( name, true ); + final var sqmPath = get( name, true ); creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); return sqmPath; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSetJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSetJoin.java index 05c249fed839..d90c44e0e8eb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSetJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSetJoin.java @@ -78,11 +78,11 @@ private SqmTreatedSetJoin( @Override public SqmTreatedSetJoin copy(SqmCopyContext context) { - final SqmTreatedSetJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmTreatedSetJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmTreatedSetJoin<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSimplePath.java index 63193e925e8d..505373053ddd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSimplePath.java @@ -78,12 +78,12 @@ private SqmTreatedSimplePath( @Override public SqmTreatedSimplePath copy(SqmCopyContext context) { - final SqmTreatedSimplePath existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmTreatedSimplePath path = context.registerCopy( + final var path = context.registerCopy( this, new SqmTreatedSimplePath<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSingularJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSingularJoin.java index df52fec73f8c..53fb588cdc98 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSingularJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSingularJoin.java @@ -80,11 +80,11 @@ private SqmTreatedSingularJoin( @Override public SqmTreatedSingularJoin copy(SqmCopyContext context) { - final SqmTreatedSingularJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmTreatedSingularJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmTreatedSingularJoin<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAny.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAny.java index e1270c827e78..8a0b7ac7d523 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAny.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAny.java @@ -38,7 +38,7 @@ public SqmAny(SqmSubQuery subquery, NodeBuilder criteriaBuilder) { @Override public SqmAny copy(SqmCopyContext context) { - final SqmAny existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAnyDiscriminatorValue.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAnyDiscriminatorValue.java index b9675b66f913..ef8b45ffb9cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAnyDiscriminatorValue.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAnyDiscriminatorValue.java @@ -43,7 +43,7 @@ public BasicType getDomainType(){ @Override public SqmAnyDiscriminatorValue copy(SqmCopyContext context) { - final SqmAnyDiscriminatorValue existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmBinaryArithmetic.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmBinaryArithmetic.java index 5e77fd159523..c98fca2883fc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmBinaryArithmetic.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmBinaryArithmetic.java @@ -73,7 +73,7 @@ public SqmBinaryArithmetic( @Override public SqmBinaryArithmetic copy(SqmCopyContext context) { - final SqmBinaryArithmetic existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSearched.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSearched.java index 20461b1fb63e..1044d8447914 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSearched.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSearched.java @@ -49,7 +49,7 @@ private SqmCaseSearched(@Nullable SqmBindableType inherentType, int estimated @Override public SqmCaseSearched copy(SqmCopyContext context) { - final SqmCaseSearched existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSimple.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSimple.java index 593bb0f69d58..a8aceefad745 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSimple.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSimple.java @@ -55,7 +55,7 @@ private SqmCaseSimple( @Override public SqmCaseSimple copy(SqmCopyContext context) { - final SqmCaseSimple existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCoalesce.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCoalesce.java index 979ba811c726..431676cee9b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCoalesce.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCoalesce.java @@ -49,7 +49,7 @@ public SqmCoalesce(@Nullable SqmBindableType type, int numberOfArguments, Nod @Override public SqmCoalesce copy(SqmCopyContext context) { - final SqmCoalesce existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmDistinct.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmDistinct.java index ebd0ccc5f160..89c3af5cab83 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmDistinct.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmDistinct.java @@ -28,7 +28,7 @@ public SqmDistinct(SqmExpression expression, NodeBuilder builder) { @Override public SqmDistinct copy(SqmCopyContext context) { - final SqmDistinct existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEnumLiteral.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEnumLiteral.java index 453b5a92b6e2..52fe2112efbc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEnumLiteral.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEnumLiteral.java @@ -47,7 +47,7 @@ public SqmEnumLiteral( @Override public SqmEnumLiteral copy(SqmCopyContext context) { - final SqmEnumLiteral existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEvery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEvery.java index ca9096abb773..df6449272146 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEvery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEvery.java @@ -37,7 +37,7 @@ public SqmEvery(SqmSubQuery subquery, NodeBuilder criteriaBuilder) { @Override public SqmEvery copy(SqmCopyContext context) { - final SqmEvery existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFieldLiteral.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFieldLiteral.java index f871671f8878..e197e1046a81 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFieldLiteral.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFieldLiteral.java @@ -78,7 +78,7 @@ public PersistenceType getPersistenceType() { @Override public SqmFieldLiteral copy(SqmCopyContext context) { - final SqmFieldLiteral existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJpaCriteriaParameterWrapper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJpaCriteriaParameterWrapper.java index 2e9a35ef9801..35851e900791 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJpaCriteriaParameterWrapper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJpaCriteriaParameterWrapper.java @@ -46,7 +46,7 @@ public SqmJpaCriteriaParameterWrapper( @Override public SqmJpaCriteriaParameterWrapper copy(SqmCopyContext context) { - final SqmJpaCriteriaParameterWrapper existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJsonNullBehavior.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJsonNullBehavior.java index 7cab17b40b01..8bc7759db8a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJsonNullBehavior.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJsonNullBehavior.java @@ -52,12 +52,7 @@ public X accept(SemanticQueryWalker walker) { @Override public void appendHqlString(StringBuilder hql, SqmRenderContext context) { - if ( this == NULL ) { - hql.append( " null on null" ); - } - else { - hql.append( " absent on null" ); - } + hql.append( this == NULL ? " null on null" : " absent on null" ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJsonTableFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJsonTableFunction.java index 5df34f9b30aa..db519eaf445f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJsonTableFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJsonTableFunction.java @@ -138,7 +138,7 @@ public JpaJsonTableFunction passing(String parameterName, Expression expressi @Override public SqmJsonTableFunction copy(SqmCopyContext context) { - final SqmJsonTableFunction existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJsonValueExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJsonValueExpression.java index 5123d55348e1..48e778703c13 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJsonValueExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJsonValueExpression.java @@ -99,7 +99,7 @@ private SqmJsonValueExpression( } public SqmJsonValueExpression copy(SqmCopyContext context) { - final SqmJsonValueExpression existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteral.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteral.java index ede9c60a6245..a271286ce9b2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteral.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteral.java @@ -53,7 +53,7 @@ protected SqmLiteral(@Nullable SqmBindableType inherentType, NodeBuilder node @Override public SqmLiteral copy(SqmCopyContext context) { - final SqmLiteral existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralEmbeddableType.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralEmbeddableType.java index 0fc44ed3fed3..02f5dbe2be02 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralEmbeddableType.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralEmbeddableType.java @@ -45,7 +45,7 @@ public EmbeddableDomainType getEmbeddableDomainType() { @Override public SqmLiteralEmbeddableType copy(SqmCopyContext context) { - final SqmLiteralEmbeddableType existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralEntityType.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralEntityType.java index 4792516c1c95..75be501c5a3d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralEntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralEntityType.java @@ -42,7 +42,7 @@ public SqmLiteralEntityType(SqmEntityDomainType entityType, NodeBuilder nodeB @Override public SqmLiteralEntityType copy(SqmCopyContext context) { - final SqmLiteralEntityType existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralNull.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralNull.java index 8353603cfc4e..d4f05b1b9f3b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralNull.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralNull.java @@ -26,7 +26,7 @@ public SqmLiteralNull(@Nullable SqmBindableType expressibleType, NodeBuilder @Override public SqmLiteralNull copy(SqmCopyContext context) { - final SqmLiteralNull existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmModifiedSubQueryExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmModifiedSubQueryExpression.java index 5cefb33c02e9..b385d1299a22 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmModifiedSubQueryExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmModifiedSubQueryExpression.java @@ -53,7 +53,7 @@ public SqmModifiedSubQueryExpression( @Override public SqmModifiedSubQueryExpression copy(SqmCopyContext context) { - final SqmModifiedSubQueryExpression existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmNamedExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmNamedExpression.java index 578e108979e9..d2cf343ace33 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmNamedExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmNamedExpression.java @@ -32,7 +32,7 @@ public SqmNamedExpression(SqmExpression expression, String name) { @Override public SqmNamedExpression copy(SqmCopyContext context) { - final SqmNamedExpression existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmNamedParameter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmNamedParameter.java index 3b847e1df9d3..44f8605e4c16 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmNamedParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmNamedParameter.java @@ -36,7 +36,7 @@ public SqmNamedParameter( @Override public SqmNamedParameter copy(SqmCopyContext context) { - final SqmNamedParameter existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } @@ -75,7 +75,7 @@ public String getName() { @Override public SqmParameter copy() { - return new SqmNamedParameter<>( getName(), allowMultiValuedBinding(), this.getNodeType(), nodeBuilder() ); + return new SqmNamedParameter<>( getName(), allowMultiValuedBinding(), getNodeType(), nodeBuilder() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmOver.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmOver.java index d46895a9bd0c..b2e746a32acd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmOver.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmOver.java @@ -63,7 +63,7 @@ public SqmOver( @Override public SqmOver copy(SqmCopyContext context) { - final SqmOver existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmOverflow.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmOverflow.java index 94a29334b9b9..8b50156714a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmOverflow.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmOverflow.java @@ -30,7 +30,7 @@ public SqmOverflow(SqmExpression separatorExpression, @Nullable SqmExpression @Override public SqmOverflow copy(SqmCopyContext context) { - final SqmOverflow existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameterizedEntityType.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameterizedEntityType.java index 915a506b3cb5..2fe415aeaedf 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameterizedEntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameterizedEntityType.java @@ -32,7 +32,7 @@ public SqmParameterizedEntityType(SqmParameter parameterExpression, NodeBuild @Override public SqmParameterizedEntityType copy(SqmCopyContext context) { - final SqmParameterizedEntityType existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmPositionalParameter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmPositionalParameter.java index cea1a470b3b3..24a9096ecea9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmPositionalParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmPositionalParameter.java @@ -38,7 +38,7 @@ public SqmPositionalParameter( @Override public SqmPositionalParameter copy(SqmCopyContext context) { - final SqmPositionalParameter existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSelfRenderingExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSelfRenderingExpression.java index 3ba9155f13fa..387063aeb4d4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSelfRenderingExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSelfRenderingExpression.java @@ -30,7 +30,7 @@ public SqmSelfRenderingExpression( @Override public SqmSelfRenderingExpression copy(SqmCopyContext context) { - final SqmSelfRenderingExpression existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSummarization.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSummarization.java index ce744035a9a7..eedb11bfe222 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSummarization.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSummarization.java @@ -31,7 +31,7 @@ public SqmSummarization(Kind kind, List> groupings, NodeBuilder @Override public SqmSummarization copy(SqmCopyContext context) { - final SqmSummarization existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmToDuration.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmToDuration.java index f0917b6126e0..ea6ad1aef28b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmToDuration.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmToDuration.java @@ -35,7 +35,7 @@ public SqmToDuration( @Override public SqmToDuration copy(SqmCopyContext context) { - final SqmToDuration existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTuple.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTuple.java index dc3d93543df0..4a71852f3151 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTuple.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTuple.java @@ -61,7 +61,7 @@ public SqmTuple(List> groupedExpressions, @Nullable SqmBindable @Override public SqmTuple copy(SqmCopyContext context) { - final SqmTuple existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmUnaryOperation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmUnaryOperation.java index 211fd039c889..7a73098017e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmUnaryOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmUnaryOperation.java @@ -49,7 +49,7 @@ public SqmUnaryOperation( @Override public SqmUnaryOperation copy(SqmCopyContext context) { - final SqmUnaryOperation existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmXmlTableFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmXmlTableFunction.java index ba8de20e513f..6e17d367b172 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmXmlTableFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmXmlTableFunction.java @@ -97,7 +97,7 @@ private static List> createArgumentsList(SqmExpression x @Override public SqmXmlTableFunction copy(SqmCopyContext context) { - final SqmXmlTableFunction existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/ValueBindJpaCriteriaParameter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/ValueBindJpaCriteriaParameter.java index 6562bde5dd45..89736c09f9c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/ValueBindJpaCriteriaParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/ValueBindJpaCriteriaParameter.java @@ -40,7 +40,7 @@ private ValueBindJpaCriteriaParameter(ValueBindJpaCriteriaParameter original) @Override public ValueBindJpaCriteriaParameter copy(SqmCopyContext context) { - final ValueBindJpaCriteriaParameter existing = context.getCopy( this ); + final var existing = context.getCopy( this ); return existing != null ? existing : context.registerCopy( this, new ValueBindJpaCriteriaParameter<>( this ) ); @@ -59,26 +59,31 @@ public void appendHqlString(StringBuilder hql, SqmRenderContext context) { // but for equals/hashCode, use equals/hashCode of the underlying value, if available, from the nodes JavaType @Override - public final boolean equals(@Nullable Object o) { - if ( this == o ) { + public final boolean equals(@Nullable Object object) { + if ( this == object ) { return true; } - if ( o instanceof ValueBindJpaCriteriaParameter that ) { - if ( value == null ) { - return that.value == null && Objects.equals( getNodeType(), that.getNodeType() ); + else if ( object instanceof ValueBindJpaCriteriaParameter that ) { + if ( this.value == null || that.value == null ) { + return this.value == that.value + && Objects.equals( this.getNodeType(), that.getNodeType() ); } - final var javaType = getJavaTypeDescriptor(); - if ( that.value != null ) { - if ( javaType != null ) { - //noinspection unchecked - return javaType.equals( that.getJavaTypeDescriptor() ) && javaType.areEqual( value, (T) that.value ); + else { + final var thisJavaType = getJavaTypeDescriptor(); + final var thatJavaType = that.getJavaTypeDescriptor(); + if ( thisJavaType == null || thatJavaType == null ) { + return thisJavaType == thatJavaType + && this.value.equals( that.value ); } else { - return that.getJavaTypeDescriptor() == null && value.equals( that.value ); + return thisJavaType.equals( thatJavaType ) + && thisJavaType.areEqual( value, thisJavaType.cast( that.value ) ); } } } - return false; + else { + return false; + } } @Override @@ -86,7 +91,11 @@ public int hashCode() { if ( value == null ) { return 0; } - final var javaType = getJavaTypeDescriptor(); - return javaType == null ? value.hashCode() : javaType.extractHashCode( value ); + else { + final var javaType = getJavaTypeDescriptor(); + return javaType == null + ? value.hashCode() + : javaType.extractHashCode( value ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCrossJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCrossJoin.java index 97a8ef98d811..3b79ff44bf03 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCrossJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCrossJoin.java @@ -9,7 +9,6 @@ import org.hibernate.metamodel.model.domain.PersistentAttribute; import org.hibernate.query.criteria.JpaCrossJoin; import org.hibernate.query.hql.spi.SqmCreationProcessingState; -import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; @@ -93,11 +92,11 @@ public SqmJoinType getSqmJoinType() { @Override public SqmCrossJoin copy(SqmCopyContext context) { - final SqmCrossJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmCrossJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCrossJoin<>( getNavigablePath(), @@ -145,7 +144,7 @@ public SqmCorrelatedCrossJoin createCorrelation() { } public SqmCrossJoin makeCopy(SqmCreationProcessingState creationProcessingState) { - final SqmPathRegistry pathRegistry = creationProcessingState.getPathRegistry(); + final var pathRegistry = creationProcessingState.getPathRegistry(); return new SqmCrossJoin<>( getReferencedPathSource(), getExplicitAlias(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCteJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCteJoin.java index ffa77988f79e..8e8d50a1b62d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCteJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCteJoin.java @@ -74,12 +74,12 @@ public boolean isImplicitlySelectable() { @Override public SqmCteJoin copy(SqmCopyContext context) { - final SqmCteJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } //noinspection unchecked - final SqmCteJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmCteJoin<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmDerivedJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmDerivedJoin.java index 35d4efe519cd..7f81dcce5508 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmDerivedJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmDerivedJoin.java @@ -97,12 +97,12 @@ public boolean isImplicitlySelectable() { @Override public SqmDerivedJoin copy(SqmCopyContext context) { - final SqmDerivedJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } //noinspection unchecked - final SqmDerivedJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmDerivedJoin<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java index e677689700bf..71fa83c1a766 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java @@ -11,7 +11,6 @@ import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaPredicate; import org.hibernate.query.hql.spi.SqmCreationProcessingState; -import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.spi.SqmCreationHelper; import org.hibernate.query.sqm.tree.SqmCopyContext; @@ -76,11 +75,11 @@ public boolean isImplicitlySelectable() { @Override public SqmEntityJoin copy(SqmCopyContext context) { - final SqmEntityJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmEntityJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmEntityJoin<>( getNavigablePath(), @@ -210,7 +209,7 @@ public SqmCorrelatedEntityJoin createCorrelation() { } public SqmEntityJoin makeCopy(SqmCreationProcessingState creationProcessingState) { - final SqmPathRegistry pathRegistry = creationProcessingState.getPathRegistry(); + final var pathRegistry = creationProcessingState.getPathRegistry(); return new SqmEntityJoin<>( getReferencedPathSource(), getExplicitAlias(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFunctionJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFunctionJoin.java index 660d80a6184e..e4f05e647b3f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFunctionJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFunctionJoin.java @@ -97,12 +97,12 @@ public boolean isImplicitlySelectable() { @Override public SqmFunctionJoin copy(SqmCopyContext context) { - final SqmFunctionJoin existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } //noinspection unchecked - final SqmFunctionJoin path = context.registerCopy( + final var path = context.registerCopy( this, new SqmFunctionJoin<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java index 3b396a94e1a9..7050dd7cbfd4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java @@ -73,11 +73,11 @@ protected SqmRoot( @Override public SqmRoot copy(SqmCopyContext context) { - final SqmRoot existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } - final SqmRoot path = context.registerCopy( + final var path = context.registerCopy( this, new SqmRoot<>( getNavigablePath(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmConflictClause.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmConflictClause.java index be4e281908e8..fc68a466ecd6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmConflictClause.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmConflictClause.java @@ -148,7 +148,7 @@ public NodeBuilder nodeBuilder() { @Override public SqmConflictClause copy(SqmCopyContext context) { - final SqmConflictClause existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmConflictUpdateAction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmConflictUpdateAction.java index 060eef3d369d..da5ecf4b0076 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmConflictUpdateAction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmConflictUpdateAction.java @@ -136,7 +136,7 @@ public NodeBuilder nodeBuilder() { @Override public SqmConflictUpdateAction copy(SqmCopyContext context) { - final SqmConflictUpdateAction existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertSelectStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertSelectStatement.java index 97177adfae92..e2c8cbbb7ed1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertSelectStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertSelectStatement.java @@ -75,7 +75,7 @@ private SqmInsertSelectStatement( @Override public SqmInsertSelectStatement copy(SqmCopyContext context) { - final SqmInsertSelectStatement existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertValuesStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertValuesStatement.java index 8f9198e689d0..4acd969d8c88 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertValuesStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertValuesStatement.java @@ -74,7 +74,7 @@ private SqmInsertValuesStatement( @Override public SqmInsertValuesStatement copy(SqmCopyContext context) { - final SqmInsertValuesStatement existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInListPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInListPredicate.java index be145463d679..65123e6ee5e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInListPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInListPredicate.java @@ -67,7 +67,7 @@ public SqmInListPredicate( @Override public SqmInListPredicate copy(SqmCopyContext context) { - final SqmInListPredicate existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInSubQueryPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInSubQueryPredicate.java index 3cb9064939f4..ca73b4928d5a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInSubQueryPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInSubQueryPredicate.java @@ -57,7 +57,7 @@ public SqmInSubQueryPredicate( @Override public SqmInSubQueryPredicate copy(SqmCopyContext context) { - final SqmInSubQueryPredicate existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java index 996ba715ca3e..eb8006d81821 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java @@ -90,7 +90,7 @@ public AbstractSqmSelectQuery( protected Map> copyCteStatements(SqmCopyContext context) { final Map> copies = new LinkedHashMap<>( cteStatements.size() ); - for ( Map.Entry> entry : cteStatements.entrySet() ) { + for ( var entry : cteStatements.entrySet() ) { copies.put( entry.getKey(), entry.getValue().copy( context ) ); } return copies; @@ -181,7 +181,7 @@ private String validateCteName(String name) { } protected JpaCteCriteria withInternal(String name, AbstractQuery criteria) { - final SqmCteStatement cteStatement = new SqmCteStatement<>( + final var cteStatement = new SqmCteStatement<>( name, (SqmSelectQuery) criteria, this, @@ -198,7 +198,7 @@ protected JpaCteCriteria withInternal( AbstractQuery baseCriteria, boolean unionDistinct, Function, AbstractQuery> recursiveCriteriaProducer) { - final SqmCteStatement cteStatement = new SqmCteStatement<>( + final var cteStatement = new SqmCteStatement<>( name, (SqmSelectQuery) baseCriteria, unionDistinct, @@ -247,7 +247,7 @@ public List> getRootList() { * @see org.hibernate.query.criteria.JpaCriteriaQuery#getRoot(int, Class) */ public JpaRoot getRoot(int position, Class type) { - final List> rootList = getQuerySpec().getRootList(); + final var rootList = getQuerySpec().getRootList(); if ( rootList.size() <= position ) { throw new IllegalArgumentException( "Not enough root entities" ); } @@ -258,7 +258,7 @@ public JpaRoot getRoot(int position, Class type) { * @see org.hibernate.query.criteria.JpaCriteriaQuery#getRoot(String, Class) */ public JpaRoot getRoot(String alias, Class type) { - for ( SqmRoot root : getQuerySpec().getRootList() ) { + for ( var root : getQuerySpec().getRootList() ) { final String rootAlias = root.getAlias(); if ( rootAlias != null && rootAlias.equals( alias ) ) { return castRoot( root, type ); @@ -268,7 +268,7 @@ public JpaRoot getRoot(String alias, Class type) { } private static JpaRoot castRoot(JpaRoot root, Class type) { - final Class rootEntityType = root.getJavaType(); + final var rootEntityType = root.getJavaType(); if ( rootEntityType == null ) { throw new AssertionFailure( "Java type of root entity was null" ); } @@ -277,7 +277,7 @@ private static JpaRoot castRoot(JpaRoot root, Class type) + "' did not have the given type '" + type.getTypeName() + "'"); } @SuppressWarnings("unchecked") // safe, we just checked - final JpaRoot result = (JpaRoot) root; + final var result = (JpaRoot) root; return result; } @@ -296,20 +296,20 @@ public SqmRoot from(Class entityClass) { @Override public SqmDerivedRoot from(Subquery subquery) { validateComplianceFromSubQuery(); - final SqmDerivedRoot root = new SqmDerivedRoot<>( (SqmSubQuery) subquery, null ); + final var root = new SqmDerivedRoot<>( (SqmSubQuery) subquery, null ); addRoot( root ); return root; } public JpaRoot from(JpaCteCriteria cte) { - final SqmCteRoot root = new SqmCteRoot<>( ( SqmCteStatement ) cte, null ); + final var root = new SqmCteRoot<>( ( SqmCteStatement ) cte, null ); addRoot( root ); return root; } @Override public JpaFunctionRoot from(JpaSetReturningFunction function) { - final SqmFunctionRoot root = new SqmFunctionRoot<>( (SqmSetReturningFunction) function, null ); + final var root = new SqmFunctionRoot<>( (SqmSetReturningFunction) function, null ); addRoot( root ); return root; } @@ -355,8 +355,8 @@ public SqmSelectQuery distinct(boolean distinct) { @Override public @Nullable JpaSelection getSelection() { - final SqmSelectClause selectClause = getQuerySpec().getSelectClause(); - final List> selections = selectClause.getSelections(); + final var selectClause = getQuerySpec().getSelectClause(); + final var selections = selectClause.getSelections(); return (JpaSelection) switch ( selections.size() ) { case 0 -> null; case 1 -> selections.get( 0 ).getSelectableNode(); @@ -438,7 +438,7 @@ public AbstractQuery having(List restrictions) { public void appendHqlString(StringBuilder hql, SqmRenderContext context) { if ( !cteStatements.isEmpty() ) { hql.append( "with " ); - for ( SqmCteStatement value : cteStatements.values() ) { + for ( var value : cteStatements.values() ) { value.appendHqlString( hql, context ); hql.append( ", " ); } @@ -479,7 +479,7 @@ public int cacheHashCode() { @SuppressWarnings("unchecked") protected Selection getResultSelection(Selection[] selections) { - final Class resultType = getResultType(); + final var resultType = getResultType(); if ( resultType == Object.class ) { return switch ( selections.length ) { case 0 -> throw new IllegalArgumentException( "Empty selections passed to criteria query typed as Object" ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java index 12cf9b69e5ce..c8e79168c930 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java @@ -5,7 +5,6 @@ package org.hibernate.query.sqm.tree.select; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -23,11 +22,15 @@ import org.hibernate.query.sqm.tree.domain.SqmDomainType; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.jpa.AbstractJpaSelection; +import org.hibernate.type.descriptor.java.DateJavaType; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.TemporalJavaType; import org.hibernate.type.spi.TypeConfiguration; import org.jboss.logging.Logger; +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; import static java.util.stream.Collectors.toList; import static org.hibernate.internal.util.NullnessUtil.castNonNull; import static org.hibernate.query.sqm.DynamicInstantiationNature.CLASS; @@ -157,7 +160,7 @@ public boolean checkInstantiation(TypeConfiguration typeConfiguration) { if ( isConstructorCompatible( javaType, argTypes, typeConfiguration ) ) { return true; } - final List> arguments = getArguments(); + final var arguments = getArguments(); final List aliases = new ArrayList<>( arguments.size() ); for ( var argument : arguments ) { final String alias = argument.getAlias(); @@ -182,9 +185,18 @@ private List> argumentTypes() { return getArguments().stream() .map( arg -> { final var expressible = arg.getExpressible(); - return expressible != null && expressible.getExpressibleJavaType() != null ? - expressible.getExpressibleJavaType().getJavaTypeClass() : - Void.class; + if ( expressible != null ) { + final var expressibleJavaType = expressible.getExpressibleJavaType(); + if ( expressibleJavaType != null ) { + return expressibleJavaType instanceof DateJavaType temporalJavaType + // Hack to accommodate a constructor with java.sql parameter + // types when the entity has java.util.Date as its field types. + // (This was requested in HHH-4179 and we fixed it by accident.) + ? TemporalJavaType.resolveJavaTypeClass( temporalJavaType.getPrecision() ) + : expressibleJavaType.getJavaTypeClass(); + } + } + return Void.class; } ).collect( toList() ); } @@ -194,7 +206,7 @@ public boolean isFullyAliased() { @Override public SqmDynamicInstantiation copy(SqmCopyContext context) { - final SqmDynamicInstantiation existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } @@ -227,7 +239,7 @@ public SqmDynamicInstantiationTarget getInstantiationTarget() { } public List> getArguments() { - return arguments == null ? Collections.emptyList() : Collections.unmodifiableList( arguments ); + return arguments == null ? emptyList() : unmodifiableList( arguments ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmJpaCompoundSelection.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmJpaCompoundSelection.java index e4d6fedc05f7..548a55431c5c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmJpaCompoundSelection.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmJpaCompoundSelection.java @@ -60,7 +60,7 @@ public SqmJpaCompoundSelection( @Override public SqmJpaCompoundSelection copy(SqmCopyContext context) { - final SqmJpaCompoundSelection existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryGroup.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryGroup.java index bd61a0088b3b..f5017e192dbd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryGroup.java @@ -62,7 +62,7 @@ public SqmQueryGroup( @Override public SqmQueryPart copy(SqmCopyContext context) { - final SqmQueryGroup existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java index 325432fe867c..3533d8e13524 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java @@ -98,7 +98,7 @@ public SqmQuerySpec(SqmQuerySpec original, SqmCopyContext context) { @Override public SqmQuerySpec copy(SqmCopyContext context) { - final SqmQuerySpec existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java index 4de3b90080da..18782b6f52cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java @@ -122,7 +122,7 @@ private SqmSelectStatement( @Override public SqmSelectStatement copy(SqmCopyContext context) { - final SqmSelectStatement existing = context.getCopy( this ); + final var existing = context.getCopy( this ); return existing != null ? existing : createCopy( context, getResultType() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectableNode.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectableNode.java index 211d22610866..d291aa6f5ac9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectableNode.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectableNode.java @@ -35,7 +35,7 @@ public interface SqmSelectableNode extends JpaSelection, SqmTypedNode { SqmSelectableNode copy(SqmCopyContext context); default @Nullable Integer getTupleLength() { - final SqmBindableType nodeType = getNodeType(); + final SqmBindableType nodeType = getExpressible(); final SqmDomainType sqmType = nodeType == null ? null : nodeType.getSqmType(); return sqmType == null ? 1 : sqmType.getTupleLength(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java index 1d086f3bce81..b0bc52227bea 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java @@ -150,7 +150,7 @@ private SqmSubQuery( @Override public SqmSubQuery copy(SqmCopyContext context) { - final SqmSubQuery existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java index 5e9b8e7905a0..981226432c4a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java @@ -107,7 +107,7 @@ private SqmUpdateStatement( @Override public SqmUpdateStatement copy(SqmCopyContext context) { - final SqmUpdateStatement existing = context.getCopy( this ); + final var existing = context.getCopy( this ); if ( existing != null ) { return existing; } @@ -179,7 +179,7 @@ public void setSetClause(SqmSetClause setClause) { @Override public SqmUpdateStatement set(SingularAttribute attribute, @Nullable X value) { - final SqmCriteriaNodeBuilder nodeBuilder = (SqmCriteriaNodeBuilder) nodeBuilder(); + final var nodeBuilder = (SqmCriteriaNodeBuilder) nodeBuilder(); SqmPath sqmAttribute = getTarget().get( attribute ); applyAssignment( sqmAttribute, nodeBuilder.value( value, sqmAttribute) ); return this; @@ -193,7 +193,7 @@ public SqmUpdateStatement set(SingularAttribute attribute, @Override public SqmUpdateStatement set(Path attribute, @Nullable X value) { - final SqmCriteriaNodeBuilder nodeBuilder = (SqmCriteriaNodeBuilder) nodeBuilder(); + final var nodeBuilder = (SqmCriteriaNodeBuilder) nodeBuilder(); final SqmPath sqmAttribute = (SqmPath) attribute; applyAssignment( sqmAttribute, nodeBuilder.value( value, sqmAttribute ) ); return this; @@ -213,7 +213,7 @@ public SqmUpdateStatement set(String attributeName, @Nullable Object value) { expression = (SqmExpression) value; } else { - final SqmCriteriaNodeBuilder nodeBuilder = (SqmCriteriaNodeBuilder) nodeBuilder(); + final var nodeBuilder = (SqmCriteriaNodeBuilder) nodeBuilder(); expression = nodeBuilder.value( value, sqmPath ); } applyAssignment( sqmPath, expression ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tuple/internal/AnonymousTupleBasicValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tuple/internal/AnonymousTupleBasicValuedModelPart.java index 40266a57e163..598022ff7f5a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tuple/internal/AnonymousTupleBasicValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tuple/internal/AnonymousTupleBasicValuedModelPart.java @@ -65,7 +65,7 @@ public AnonymousTupleBasicValuedModelPart( new SelectableMappingImpl( "", selectionExpression, - new SelectablePath( partName ), + null, null, null, null, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tuple/internal/AnonymousTupleEmbeddableValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tuple/internal/AnonymousTupleEmbeddableValuedModelPart.java index 4e3e700380dd..c05647eb1f49 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tuple/internal/AnonymousTupleEmbeddableValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tuple/internal/AnonymousTupleEmbeddableValuedModelPart.java @@ -128,7 +128,7 @@ private Map createModelParts( sqmExpressible, attributeType, sqlTypedMappings, - selectionIndex, + selectionIndex + index, selectionExpression + "_" + attribute.getName(), attribute.getName(), modelPartContainer.findSubPart( attribute.getName(), null ), diff --git a/hibernate-core/src/main/java/org/hibernate/relational/SchemaManager.java b/hibernate-core/src/main/java/org/hibernate/relational/SchemaManager.java index 395b82a12bb0..8df8b1326eac 100644 --- a/hibernate-core/src/main/java/org/hibernate/relational/SchemaManager.java +++ b/hibernate-core/src/main/java/org/hibernate/relational/SchemaManager.java @@ -10,7 +10,8 @@ /** * Allows programmatic {@linkplain #exportMappedObjects schema export}, * {@linkplain #validateMappedObjects schema validation}, - * {@linkplain #truncateMappedObjects data cleanup}, and + * {@linkplain #truncateMappedObjects data cleanup}, + * {@linkplain #populate data population}, and * {@linkplain #dropMappedObjects schema cleanup} as a convenience for * writing tests. * diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslatorFactory.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslatorFactory.java index 8727488b9187..12604f6f74ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslatorFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslatorFactory.java @@ -8,7 +8,7 @@ import org.hibernate.sql.ast.tree.MutationStatement; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.model.ast.TableMutation; import org.hibernate.sql.model.jdbc.JdbcMutationOperation; @@ -21,7 +21,7 @@ public interface SqlAstTranslatorFactory { /** * Builds a single-use select translator */ - SqlAstTranslator buildSelectTranslator(SessionFactoryImplementor sessionFactory, SelectStatement statement); + SqlAstTranslator buildSelectTranslator(SessionFactoryImplementor sessionFactory, SelectStatement statement); /** * Builds a single-use mutation translator diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index 2fe0d6e35eef..1e89cb8c2072 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -188,6 +188,7 @@ import org.hibernate.sql.model.ast.ColumnWriteFragment; import org.hibernate.sql.model.ast.RestrictedTableMutation; import org.hibernate.sql.model.ast.TableMutation; +import org.hibernate.sql.model.internal.OptionalTableInsert; import org.hibernate.sql.model.internal.OptionalTableUpdate; import org.hibernate.sql.model.internal.TableDeleteCustomSql; import org.hibernate.sql.model.internal.TableDeleteStandard; @@ -8508,6 +8509,9 @@ private T translateTableMutation(TableMutation mutation) { @Override public void visitStandardTableInsert(TableInsertStandard tableInsert) { + if ( tableInsert instanceof OptionalTableInsert ) { + throw new IllegalQueryOperationException( "Optional table insert is not supported" ); + } getCurrentClauseStack().push( Clause.INSERT ); try { renderInsertInto( tableInsert ); @@ -8521,7 +8525,7 @@ public void visitStandardTableInsert(TableInsertStandard tableInsert) { } } - private void renderInsertInto(TableInsertStandard tableInsert) { + protected void renderInsertInto(TableInsertStandard tableInsert) { applySqlComment( tableInsert.getMutationComment() ); if ( tableInsert.getNumberOfValueBindings() == 0 ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstTranslatorWithMerge.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstTranslatorWithMerge.java index ee1cee398c5c..2368ac99e63b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstTranslatorWithMerge.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstTranslatorWithMerge.java @@ -8,6 +8,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.util.StringHelper; +import org.hibernate.jdbc.Expectation; import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.sql.model.ast.ColumnValueBinding; @@ -44,6 +45,10 @@ public MergeOperation createMergeOperation(OptionalTableUpdate optionalTableUpda optionalTableUpdate.getMutatingTable().getTableMapping(), optionalTableUpdate.getMutationTarget(), getSql(), + // Without value bindings, the upsert may have an update count of 0 + optionalTableUpdate.getValueBindings().isEmpty() + ? new Expectation.OptionalRowCount() + : new Expectation.RowCount(), getParameterBinders() ); } @@ -232,16 +237,18 @@ protected void renderMergeUpdate(OptionalTableUpdate optionalTableUpdate) { final List valueBindings = optionalTableUpdate.getValueBindings(); final List optimisticLockBindings = optionalTableUpdate.getOptimisticLockBindings(); - renderWhenMatched( optimisticLockBindings ); - appendSql( " then update set " ); - for ( int i = 0; i < valueBindings.size(); i++ ) { - final ColumnValueBinding binding = valueBindings.get( i ); - if ( i > 0 ) { - appendSql( ", " ); + if ( !valueBindings.isEmpty() ) { + renderWhenMatched( optimisticLockBindings ); + appendSql( " then update set " ); + for ( int i = 0; i < valueBindings.size(); i++ ) { + final ColumnValueBinding binding = valueBindings.get( i ); + if ( i > 0 ) { + appendSql( ", " ); + } + binding.getColumnReference().appendColumnForWrite( this, null ); + appendSql( "=" ); + binding.getColumnReference().appendColumnForWrite( this, "s" ); } - binding.getColumnReference().appendColumnForWrite( this, null ); - appendSql( "=" ); - binding.getColumnReference().appendColumnForWrite( this, "s" ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstTranslatorWithUpsert.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstTranslatorWithUpsert.java index b914a4270dab..e594d9a5f513 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstTranslatorWithUpsert.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstTranslatorWithUpsert.java @@ -7,6 +7,7 @@ import java.util.List; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.jdbc.Expectation; import org.hibernate.persister.entity.mutation.EntityTableMapping; import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.exec.spi.JdbcOperation; @@ -37,6 +38,10 @@ public MutationOperation createMergeOperation(OptionalTableUpdate optionalTableU optionalTableUpdate.getMutatingTable().getTableMapping(), optionalTableUpdate.getMutationTarget(), getSql(), + // Without value bindings, the upsert may have an update count of 0 + optionalTableUpdate.getValueBindings().isEmpty() + ? new Expectation.OptionalRowCount() + : new Expectation.RowCount(), getParameterBinders() ); @@ -203,17 +208,19 @@ protected void renderMergeUpdate(OptionalTableUpdate optionalTableUpdate) { final List valueBindings = optionalTableUpdate.getValueBindings(); final List optimisticLockBindings = optionalTableUpdate.getOptimisticLockBindings(); - appendSql( "when matched then update set " ); - for ( int i = 0; i < valueBindings.size(); i++ ) { - final ColumnValueBinding binding = valueBindings.get( i ); - if ( i > 0 ) { - appendSql( ", " ); + if ( !valueBindings.isEmpty() ) { + appendSql( "when matched then update set " ); + for ( int i = 0; i < valueBindings.size(); i++ ) { + final ColumnValueBinding binding = valueBindings.get( i ); + if ( i > 0 ) { + appendSql( ", " ); + } + binding.getColumnReference().appendColumnForWrite( this, "t" ); + appendSql( "=" ); + binding.getColumnReference().appendColumnForWrite( this, "s" ); } - binding.getColumnReference().appendColumnForWrite( this, "t" ); - appendSql( "=" ); - binding.getColumnReference().appendColumnForWrite( this, "s" ); + renderMatchedWhere( optimisticLockBindings ); } - renderMatchedWhere( optimisticLockBindings ); } private void renderMatchedWhere(List optimisticLockBindings) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstTranslatorFactory.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstTranslatorFactory.java index 59cf7cc5c62d..a72adb7b93a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstTranslatorFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstTranslatorFactory.java @@ -12,7 +12,7 @@ import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.model.ast.TableMutation; import org.hibernate.sql.model.jdbc.JdbcMutationOperation; @@ -24,7 +24,7 @@ public class StandardSqlAstTranslatorFactory implements SqlAstTranslatorFactory { @Override - public SqlAstTranslator buildSelectTranslator(SessionFactoryImplementor sessionFactory, SelectStatement statement) { + public SqlAstTranslator buildSelectTranslator(SessionFactoryImplementor sessionFactory, SelectStatement statement) { return buildTranslator( sessionFactory, statement ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java index 055ecf5ccbba..274b126f1afd 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java @@ -130,7 +130,6 @@ public static CteTable createIdTable(String cteName, PersistentClass persistentC idName = "id"; } final List columns = new ArrayList<>( persistentClass.getIdentifier().getColumnSpan() ); - final Metadata metadata = persistentClass.getIdentifier().getBuildingContext().getMetadataCollector(); forEachCteColumn( idName, persistentClass.getIdentifier(), columns::add ); return new CteTable( cteName, columns ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcOperationQuery.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcOperationQuery.java index e111c2a17e7c..1c192f43dc3b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcOperationQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcOperationQuery.java @@ -90,9 +90,8 @@ public boolean isCompatibleWith(JdbcParameterBindings jdbcParameterBindings, Que return true; } - @SuppressWarnings("unchecked") static boolean equal(JdbcParameterBinding appliedBinding, JdbcParameterBinding binding, JavaType type) { return type.isInstance( appliedBinding.getBindValue() ) - && type.areEqual( (T) binding.getBindValue(), (T) appliedBinding.getBindValue() ); + && type.areEqual( type.cast( binding.getBindValue() ), type.cast( appliedBinding.getBindValue() ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/TableMergeBuilder.java b/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/TableMergeBuilder.java index 75054ef7e78d..754676cf0f34 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/TableMergeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/TableMergeBuilder.java @@ -12,7 +12,6 @@ import org.hibernate.sql.model.ast.MutatingTableReference; import org.hibernate.sql.model.ast.RestrictedTableMutation; import org.hibernate.sql.model.internal.OptionalTableUpdate; -import org.hibernate.sql.model.internal.TableUpdateNoSet; import java.util.List; @@ -39,9 +38,6 @@ public TableMergeBuilder( @Override public RestrictedTableMutation buildMutation() { final List valueBindings = combine( getValueBindings(), getKeyBindings(), getLobValueBindings() ); - if ( valueBindings.isEmpty() ) { - return (RestrictedTableMutation) new TableUpdateNoSet( getMutatingTable(), getMutationTarget() ); - } // TODO: add getMergeDetails() // if ( getMutatingTable().getTableMapping().getUpdateDetails().getCustomSql() != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/internal/OptionalTableInsert.java b/hibernate-core/src/main/java/org/hibernate/sql/model/internal/OptionalTableInsert.java new file mode 100644 index 000000000000..99d1915ea41f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/internal/OptionalTableInsert.java @@ -0,0 +1,41 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.sql.model.internal; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.model.MutationTarget; +import org.hibernate.sql.model.ast.ColumnValueBinding; +import org.hibernate.sql.model.ast.ColumnValueParameter; +import org.hibernate.sql.model.ast.MutatingTableReference; + +import java.util.List; + +public class OptionalTableInsert extends TableInsertStandard { + + private final @Nullable String constraintName; + private final List constraintColumnNames; + + public OptionalTableInsert( + MutatingTableReference mutatingTable, + MutationTarget mutationTarget, + List valueBindings, + List returningColumns, + List parameters, + @Nullable String constraintName, + List constraintColumnNames) { + super( mutatingTable, mutationTarget, valueBindings, returningColumns, parameters ); + this.constraintName = constraintName; + this.constraintColumnNames = constraintColumnNames; + } + + public @Nullable String getConstraintName() { + return constraintName; + } + + public List getConstraintColumnNames() { + return constraintColumnNames; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java index 5042cc87be35..2a759c77b928 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java @@ -41,8 +41,6 @@ public class DeleteOrUpsertOperation implements SelfExecutingUpdateOperation { private final OptionalTableUpdate optionalTableUpdate; - private final Expectation expectation = getExpectation(); - public DeleteOrUpsertOperation( EntityMutationTarget mutationTarget, EntityTableMapping tableMapping, @@ -233,6 +231,6 @@ public OptionalTableUpdate getOptionalTableUpdate() { } protected Expectation getExpectation() { - return new Expectation.RowCount(); + return upsertOperation.getExpectation(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/MergeOperation.java b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/MergeOperation.java index 0e1bf7d0dd94..ad7dabcd7ccd 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/MergeOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/MergeOperation.java @@ -23,7 +23,16 @@ public MergeOperation( MutationTarget mutationTarget, String sql, List parameterBinders) { - super( tableDetails, mutationTarget, sql, false, new Expectation.RowCount(), parameterBinders ); + this( tableDetails, mutationTarget, sql, new Expectation.RowCount(), parameterBinders ); + } + + public MergeOperation( + TableMapping tableDetails, + MutationTarget mutationTarget, + String sql, + Expectation expectation, + List parameterBinders) { + super( tableDetails, mutationTarget, sql, false, expectation, parameterBinders ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java index 70362dc28fb4..01614f4f1ca8 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java @@ -105,6 +105,22 @@ public TableMapping getTableDetails() { return tableMapping; } + public List getValueBindings() { + return valueBindings; + } + + public List getKeyBindings() { + return keyBindings; + } + + public List getOptimisticLockBindings() { + return optimisticLockBindings; + } + + public List getParameters() { + return parameters; + } + @Override public JdbcValueDescriptor findValueDescriptor(String columnName, ParameterUsage usage) { for ( int i = 0; i < jdbcValueDescriptors.size(); i++ ) { @@ -153,11 +169,17 @@ public void performMutation( performInsert( jdbcValueBindings, session ); } catch (ConstraintViolationException cve) { - throw cve.getKind() == UNIQUE + if ( cve.getKind() == UNIQUE ) { + // Ignore primary key violation if the insert is composed of just the primary key + if ( !valueBindings.isEmpty() ) { // assume it was the primary key constraint which was violated, // due to a new version of the row existing in the database - ? new StaleStateException( mutationTarget.getRolePath(), cve ) - : cve; + throw new StaleStateException( mutationTarget.getRolePath(), cve ); + } + } + else { + throw cve; + } } } } @@ -369,7 +391,7 @@ protected JdbcMutationOperation createJdbcUpdate(SharedSessionContractImplemento } private void performInsert(JdbcValueBindings jdbcValueBindings, SharedSessionContractImplementor session) { - final JdbcInsertMutation jdbcInsert = createJdbcInsert( session ); + final JdbcMutationOperation jdbcInsert = createJdbcOptionalInsert( session ); final JdbcServices jdbcServices = session.getJdbcServices(); final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); final PreparedStatement insertStatement = createStatementDetails( jdbcInsert, jdbcCoordinator ); @@ -407,6 +429,13 @@ private void performInsert(JdbcValueBindings jdbcValueBindings, SharedSessionCon } } + /* + * Used by Hibernate Reactive + */ + protected JdbcMutationOperation createJdbcOptionalInsert(SharedSessionContractImplementor session) { + return createJdbcInsert( session ); + } + /* * Used by Hibernate Reactive */ diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateWithUpsertOperation.java b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateWithUpsertOperation.java new file mode 100644 index 000000000000..af5c148f4191 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateWithUpsertOperation.java @@ -0,0 +1,64 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.sql.model.jdbc; + +import org.hibernate.engine.jdbc.mutation.internal.MutationQueryOptions; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.mutation.EntityMutationTarget; +import org.hibernate.sql.model.ast.MutatingTableReference; +import org.hibernate.sql.model.ast.TableMutation; +import org.hibernate.sql.model.internal.OptionalTableInsert; +import org.hibernate.sql.model.internal.OptionalTableUpdate; + +import java.util.Arrays; +import java.util.Collections; + +/** + * Uses {@link org.hibernate.sql.model.internal.OptionalTableInsert} for the insert operation, + * to avoid primary key constraint violations when inserting only primary key columns. + */ +public class OptionalTableUpdateWithUpsertOperation extends OptionalTableUpdateOperation { + + public OptionalTableUpdateWithUpsertOperation( + EntityMutationTarget mutationTarget, + OptionalTableUpdate upsert, + @SuppressWarnings("unused") SessionFactoryImplementor factory) { + super( mutationTarget, upsert, factory ); + } + + @Override + protected JdbcMutationOperation createJdbcOptionalInsert(SharedSessionContractImplementor session) { + if ( getTableDetails().getInsertDetails() != null + && getTableDetails().getInsertDetails().getCustomSql() != null + || !getValueBindings().isEmpty() ) { + return super.createJdbcOptionalInsert( session ); + } + else { + // Ignore a primary key violation on insert when inserting just the primary key columns + final TableMutation tableInsert = new OptionalTableInsert( + new MutatingTableReference( getTableDetails() ), + getMutationTarget(), + CollectionHelper.combine( getValueBindings(), getKeyBindings() ), + Collections.emptyList(), + getParameters(), + null, + Arrays.asList( ((EntityPersister) getMutationTarget()).getIdentifierColumnNames() ) + ); + + final SessionFactoryImplementor factory = session.getSessionFactory(); + return factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory() + .buildModelMutationTranslator( tableInsert, factory ) + .translate( null, MutationQueryOptions.INSTANCE ); + } + } + + @Override + public String toString() { + return "OptionalTableUpdateWithUpsertOperation(" + getTableDetails() + ")"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/UpsertOperation.java b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/UpsertOperation.java index 6ba371d381c8..23b671724d4c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/UpsertOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/UpsertOperation.java @@ -23,7 +23,16 @@ public UpsertOperation( MutationTarget mutationTarget, String sql, List parameterBinders) { - super( tableDetails, mutationTarget, sql, false, new Expectation.RowCount(), parameterBinders ); + this( tableDetails, mutationTarget, sql, new Expectation.RowCount(), parameterBinders ); + } + + public UpsertOperation( + TableMapping tableDetails, + MutationTarget mutationTarget, + String sql, + Expectation expectation, + List parameterBinders) { + super( tableDetails, mutationTarget, sql, false, expectation, parameterBinders ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResultAssembler.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResultAssembler.java index c25dee2ac2bf..6a6937281095 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResultAssembler.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResultAssembler.java @@ -60,7 +60,7 @@ public J assemble( if ( valueConverter != null ) { if ( jdbcValue != null ) { // the raw value type should be the converter's relational-JTD - if ( ! valueConverter.getRelationalJavaType().getJavaTypeClass().isInstance( jdbcValue ) ) { + if ( ! valueConverter.getRelationalJavaType().isInstance( jdbcValue ) ) { throw new HibernateException( String.format( Locale.ROOT, diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/CoercingResultAssembler.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/CoercingResultAssembler.java index 91e9f003a081..6676eaad3fdd 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/CoercingResultAssembler.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/CoercingResultAssembler.java @@ -29,9 +29,6 @@ public CoercingResultAssembler( */ @Override public Object extractRawValue(RowProcessingState rowProcessingState) { - return assembledJavaType.coerce( - super.extractRawValue( rowProcessingState ), - rowProcessingState.getSession() - ); + return assembledJavaType.coerce( super.extractRawValue( rowProcessingState ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/AbstractImmediateCollectionInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/AbstractImmediateCollectionInitializer.java index c9b700fcd633..dd91a6c5420f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/AbstractImmediateCollectionInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/AbstractImmediateCollectionInitializer.java @@ -26,6 +26,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; +import static org.hibernate.internal.util.NullnessUtil.castNonNull; + /** * Base support for CollectionInitializer implementations that represent * an immediate initialization of some sort (join, select, batch, sub-select) @@ -141,7 +143,39 @@ public void resolveState(Data data) { public void resolveFromPreviousRow(Data data) { super.resolveFromPreviousRow( data ); if ( data.getState() == State.RESOLVED ) { - resolveKeySubInitializers( data ); + // The state is resolved, so we know a collection instance exists + final PersistentCollection collection = castNonNull( data.getCollectionInstance() ); + if ( collection.wasInitialized() ) { + // The collection was an already initialized instance, so we can set this to initialized + // and just resolve sub-initializers + resolveFromPreviouslyInitializedInstance( data ); + } + else { + resolveKeySubInitializers( data ); + } + } + } + + protected void resolveFromPreviouslyInitializedInstance(Data data) { + data.setState( State.INITIALIZED ); + if ( data.shallowCached ) { + initializeShallowCached( data ); + } + else { + resolveInstanceSubInitializers( data ); + } + final var rowProcessingState = data.getRowProcessingState(); + if ( rowProcessingState.needsResolveState() ) { + // Resolve the state of the identifier if result caching is enabled and this is not a query cache hit + if ( collectionKeyResultAssembler != null ) { + collectionKeyResultAssembler.resolveState( rowProcessingState ); + } + if ( !getInitializingCollectionDescriptor().useShallowQueryCacheLayout() ) { + if ( collectionValueKeyResultAssembler != null ) { + collectionValueKeyResultAssembler.resolveState( rowProcessingState ); + } + resolveCollectionContentState( rowProcessingState ); + } } } @@ -337,25 +371,7 @@ public void resolveInstance(Object instance, Data data) { } data.collectionValueKey = null; if ( collection.wasInitialized() ) { - data.setState( State.INITIALIZED ); - if ( data.shallowCached ) { - initializeShallowCached( data ); - } - else { - resolveInstanceSubInitializers( data ); - } - if ( rowProcessingState.needsResolveState() ) { - // Resolve the state of the identifier if result caching is enabled and this is not a query cache hit - if ( collectionKeyResultAssembler != null ) { - collectionKeyResultAssembler.resolveState( rowProcessingState ); - } - if ( !getInitializingCollectionDescriptor().useShallowQueryCacheLayout() ) { - if ( collectionValueKeyResultAssembler != null ) { - collectionValueKeyResultAssembler.resolveState( rowProcessingState ); - } - resolveCollectionContentState( rowProcessingState ); - } - } + resolveFromPreviouslyInitializedInstance( data ); } else { if ( data.shallowCached ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchImpl.java index 23445bb15838..7328d783ee8e 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchImpl.java @@ -195,8 +195,10 @@ public EmbeddableInitializer createInitializer(InitializerParent parent, A @Override public boolean appliesTo(GraphImplementor graphImplementor, JpaMetamodel metamodel) { - // We use managedType here since this fetch could correspond to an entity type if the embeddable is an id-class - return GraphHelper.appliesTo( graphImplementor, metamodel.managedType( getResultJavaType().getTypeName() ) ); + // We use managedType here since this fetch could correspond + // to an entity type if the embeddable is an @IdClass + return GraphHelper.appliesTo( graphImplementor, + metamodel.managedType( getResultJavaType().getTypeName() ) ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java index 60a1f9c825f1..352262529287 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java @@ -1154,6 +1154,8 @@ private void resolveEntity(EntityInitializerData data, Object proxy) { public void resolveInstance(EntityInitializerData data) { if ( data.getState() == State.KEY_RESOLVED ) { final var rowProcessingState = data.getRowProcessingState(); + final var session = rowProcessingState.getSession(); + final var persistenceContext = session.getPersistenceContextInternal(); data.setState( State.RESOLVED ); if ( data.entityKey == null ) { assert identifierAssembler != null; @@ -1165,7 +1167,7 @@ public void resolveInstance(EntityInitializerData data) { resolveEntityKey( data, id ); } data.entityHolder = - rowProcessingState.getSession().getPersistenceContextInternal() + persistenceContext .claimEntityHolderIfPossible( data.entityKey, null, @@ -1179,7 +1181,6 @@ public void resolveInstance(EntityInitializerData data) { else { resolveEntityInstance1( data ); if ( data.uniqueKeyAttributePath != null ) { - final var session = rowProcessingState.getSession(); final var concreteDescriptor = getConcreteDescriptor( data ); final var entityUniqueKey = new EntityUniqueKey( concreteDescriptor.getEntityName(), @@ -1188,8 +1189,7 @@ public void resolveInstance(EntityInitializerData data) { data.uniqueKeyPropertyTypes[concreteDescriptor.getSubclassId()], session.getFactory() ); - session.getPersistenceContextInternal() - .addEntity( entityUniqueKey, data.getInstance() ); + persistenceContext.addEntity( entityUniqueKey, data.getInstance() ); } } @@ -1346,7 +1346,10 @@ protected void upgradeLockMode(EntityInitializerData data) { protected boolean isProxyInstance(Object proxy) { return proxy != null && ( proxy instanceof MapProxy - || entityDescriptor.getJavaType().getJavaTypeClass().isInstance( proxy ) ); + // do NOT use JavaType.isInstance() here; we're testing if the + // proxy itself is an instance of the given entity type, not if + // the underlying entity implementation is an instance + || entityDescriptor.getJavaType().getJavaTypeClass().isInstance( proxy ) ); } private boolean isExistingEntityInitialized(Object existingEntity) { @@ -2112,7 +2115,7 @@ public boolean isHasLazyInitializingSubAssemblers() { return assemblers; } - protected @Nullable BasicResultAssembler discriminatorAssembler() { + protected @Nullable BasicResultAssembler getDiscriminatorAssembler() { return discriminatorAssembler; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiation.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiation.java index 6ebfd95a82f0..6ca2cb334c34 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiation.java @@ -7,7 +7,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import org.hibernate.query.sqm.DynamicInstantiationNature; import org.hibernate.query.sqm.sql.ConversionException; @@ -18,17 +17,19 @@ import org.jboss.logging.Logger; +import static java.util.stream.Collectors.toList; + /** * Represents a dynamic-instantiation (from an SQM query) as a DomainResultProducer * * @author Steve Ebersole */ -public class DynamicInstantiation implements DomainResultProducer { +public class DynamicInstantiation implements DomainResultProducer { private static final Logger LOG = Logger.getLogger( DynamicInstantiation.class ); private final DynamicInstantiationNature nature; private final JavaType targetJavaType; - private List arguments; + private List> arguments; private boolean argumentAdditionsComplete = false; @@ -47,7 +48,7 @@ public JavaType getTargetJavaType() { return targetJavaType; } - public void addArgument(String alias, DomainResultProducer argumentResultProducer, DomainResultCreationState creationState) { + public void addArgument(String alias, DomainResultProducer argumentResultProducer) { if ( argumentAdditionsComplete ) { throw new ConversionException( "Unexpected call to DynamicInstantiation#addAgument after previously complete" ); } @@ -82,7 +83,7 @@ public void complete() { argumentAdditionsComplete = true; } - public List getArguments() { + public List> getArguments() { return arguments; } @@ -92,17 +93,16 @@ public String toString() { } @Override - public DomainResult createDomainResult( + public DomainResult createDomainResult( String resultVariable, DomainResultCreationState creationState) { - //noinspection unchecked - return new DynamicInstantiationResultImpl( + return new DynamicInstantiationResultImpl<>( resultVariable, getNature(), getTargetJavaType(), getArguments().stream() .map( argument -> argument.buildArgumentDomainResult( creationState ) ) - .collect( Collectors.toList() ) + .collect( toList() ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationArgument.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationArgument.java index 2d1f5d5438ec..cb5677668f48 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationArgument.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationArgument.java @@ -6,7 +6,6 @@ import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter; import org.hibernate.query.sqm.sql.internal.DomainResultProducer; -import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.results.graph.DomainResultCreationState; /** @@ -26,9 +25,9 @@ public String getAlias() { } public ArgumentDomainResult buildArgumentDomainResult(DomainResultCreationState creationState) { - final SqlExpressionResolver sqlExpressionResolver = creationState.getSqlAstCreationState() - .getCurrentProcessingState() - .getSqlExpressionResolver(); + final var sqlExpressionResolver = + creationState.getSqlAstCreationState().getCurrentProcessingState() + .getSqlExpressionResolver(); if ( sqlExpressionResolver instanceof BaseSqmToSqlAstConverter.SqmAliasedNodeCollector ) { if ( !( argumentResultProducer instanceof DynamicInstantiation ) ) { ( (BaseSqmToSqlAstConverter.SqmAliasedNodeCollector) sqlExpressionResolver ).next(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerConstructorImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerConstructorImpl.java index 99a90e91f6a1..7e411f7429e7 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerConstructorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerConstructorImpl.java @@ -40,7 +40,7 @@ public JavaType getAssembledJavaType() { @Override public R assemble(RowProcessingState rowProcessingState) { final int numberOfArgs = argumentReaders.size(); - Object[] args = new Object[ numberOfArgs ]; + final var args = new Object[ numberOfArgs ]; for ( int i = 0; i < numberOfArgs; i++ ) { args[i] = argumentReaders.get( i ).assemble( rowProcessingState ); } @@ -60,14 +60,14 @@ public R assemble(RowProcessingState rowProcessingState) { @Override public void resolveState(RowProcessingState rowProcessingState) { - for ( ArgumentReader argumentReader : argumentReaders ) { + for ( var argumentReader : argumentReaders ) { argumentReader.resolveState( rowProcessingState ); } } @Override public void forEachResultAssembler(BiConsumer, X> consumer, X arg) { - for ( ArgumentReader argumentReader : argumentReaders ) { + for ( var argumentReader : argumentReaders ) { argumentReader.forEachResultAssembler( consumer, arg ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerInjectionImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerInjectionImpl.java index cd842c71c7b3..d3b298e0b9b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerInjectionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerInjectionImpl.java @@ -5,11 +5,7 @@ package org.hibernate.sql.results.graph.instantiation.internal; import java.beans.BeanInfo; -import java.beans.PropertyDescriptor; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; @@ -35,7 +31,7 @@ public DynamicInstantiationAssemblerInjectionImpl( JavaType target, List> argumentReaders) { this.target = target; - final Class targetJavaType = target.getJavaTypeClass(); + final var targetJavaType = target.getJavaTypeClass(); final List beanInjections = new ArrayList<>( argumentReaders.size() ); BeanInfoHelper.visitBeanInfo( targetJavaType, @@ -58,20 +54,20 @@ private DynamicInstantiationAssemblerInjectionImpl(List beanInjec } private static BeanInjection injection(BeanInfo beanInfo, ArgumentReader argument, Class targetJavaType) { - final Class argType = argument.getAssembledJavaType().getJavaTypeClass(); + final var argType = argument.getAssembledJavaType().getJavaTypeClass(); final String alias = argument.getAlias(); // see if we can find a property with the given name... - for ( PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors() ) { + for ( var propertyDescriptor : beanInfo.getPropertyDescriptors() ) { if ( propertyMatches( alias, argType, propertyDescriptor ) ) { - final Method setter = propertyDescriptor.getWriteMethod(); + final var setter = propertyDescriptor.getWriteMethod(); setter.setAccessible(true); return new BeanInjection( new BeanInjectorSetter<>( setter ), argument ); } } // see if we can find a Field with the given name... - final Field field = findField( targetJavaType, alias, argType ); + final var field = findField( targetJavaType, alias, argType ); if ( field != null ) { return new BeanInjection( new BeanInjectorField<>( field ), argument ); } @@ -92,7 +88,7 @@ public JavaType getAssembledJavaType() { public T assemble(RowProcessingState rowProcessingState) { final T result; try { - final Constructor constructor = target.getJavaTypeClass().getDeclaredConstructor(); + final var constructor = target.getJavaTypeClass().getDeclaredConstructor(); constructor.setAccessible( true ); result = constructor.newInstance(); } @@ -101,7 +97,7 @@ public T assemble(RowProcessingState rowProcessingState) { throw new InstantiationException( "Error instantiating class '" + target.getTypeName() + "' using default constructor: " + e.getMessage(), e ); } - for ( BeanInjection beanInjection : beanInjections ) { + for ( var beanInjection : beanInjections ) { final Object assembled = beanInjection.getValueAssembler().assemble( rowProcessingState ); beanInjection.getBeanInjector().inject( result, assembled ); } @@ -110,14 +106,14 @@ public T assemble(RowProcessingState rowProcessingState) { @Override public void resolveState(RowProcessingState rowProcessingState) { - for ( BeanInjection beanInjection : beanInjections ) { + for ( var beanInjection : beanInjections ) { beanInjection.getValueAssembler().resolveState( rowProcessingState ); } } @Override public void forEachResultAssembler(BiConsumer, X> consumer, X arg) { - for ( BeanInjection beanInjection : beanInjections ) { + for ( var beanInjection : beanInjections ) { beanInjection.getValueAssembler().forEachResultAssembler( consumer, arg ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerListImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerListImpl.java index ff44cbf5fb12..da4ad30545a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerListImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerListImpl.java @@ -41,7 +41,7 @@ public JavaType> getAssembledJavaType() { public List assemble( RowProcessingState rowProcessingState) { final ArrayList result = new ArrayList<>(); - for ( ArgumentReader argumentReader : argumentReaders ) { + for ( var argumentReader : argumentReaders ) { result.add( argumentReader.assemble( rowProcessingState ) ); } return result; @@ -49,14 +49,14 @@ public List assemble( @Override public void resolveState(RowProcessingState rowProcessingState) { - for ( ArgumentReader argumentReader : argumentReaders ) { + for ( var argumentReader : argumentReaders ) { argumentReader.resolveState( rowProcessingState ); } } @Override public void forEachResultAssembler(BiConsumer, X> consumer, X arg) { - for ( ArgumentReader argumentReader : argumentReaders ) { + for ( var argumentReader : argumentReaders ) { argumentReader.forEachResultAssembler( consumer, arg ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerMapImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerMapImpl.java index e6c5b1a34149..7252811a6764 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerMapImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationAssemblerMapImpl.java @@ -33,7 +33,7 @@ public DynamicInstantiationAssemblerMapImpl( this.argumentReaders = argumentReaders; final Set aliases = new HashSet<>(); - for ( ArgumentReader argumentReader : argumentReaders ) { + for ( var argumentReader : argumentReaders ) { if ( argumentReader.getAlias() == null ) { throw new IllegalStateException( "alias for Map dynamic instantiation argument cannot be null" ); } @@ -60,20 +60,16 @@ public JavaType> getAssembledJavaType() { public Map assemble( RowProcessingState rowProcessingState) { final HashMap result = new HashMap<>(); - - for ( ArgumentReader argumentReader : argumentReaders ) { - result.put( - argumentReader.getAlias(), - argumentReader.assemble( rowProcessingState ) - ); + for ( var argumentReader : argumentReaders ) { + result.put( argumentReader.getAlias(), + argumentReader.assemble( rowProcessingState ) ); } - return result; } @Override public void resolveState(RowProcessingState rowProcessingState) { - for ( ArgumentReader argumentReader : argumentReaders ) { + for ( var argumentReader : argumentReaders ) { argumentReader.resolveState( rowProcessingState ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationResultImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationResultImpl.java index 3bee263d66d4..58294dad65ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationResultImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiationResultImpl.java @@ -4,7 +4,6 @@ */ package org.hibernate.sql.results.graph.instantiation.internal; -import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.BitSet; import java.util.HashSet; @@ -18,9 +17,10 @@ import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.instantiation.DynamicInstantiationResult; +import org.hibernate.type.descriptor.java.DateJavaType; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.spi.TypeConfiguration; +import org.hibernate.type.descriptor.java.TemporalJavaType; import org.jboss.logging.Logger; import static java.util.stream.Collectors.toList; @@ -63,7 +63,7 @@ public String getResultVariable() { public boolean containsAnyNonScalarResults() { //noinspection ForLoopReplaceableByForEach for ( int i = 0; i < argumentResults.size(); i++ ) { - final ArgumentDomainResult argumentResult = argumentResults.get( i ); + final var argumentResult = argumentResults.get( i ); if ( argumentResult.containsAnyNonScalarResults() ) { return true; } @@ -74,7 +74,7 @@ public boolean containsAnyNonScalarResults() { @Override public void collectValueIndexesToCache(BitSet valueIndexes) { - for ( ArgumentDomainResult argumentResult : argumentResults ) { + for ( var argumentResult : argumentResults ) { argumentResult.collectValueIndexesToCache( valueIndexes ); } } @@ -88,7 +88,7 @@ public DomainResultAssembler createResultAssembler(InitializerParent paren final List> argumentReaders = new ArrayList<>(); if ( argumentResults != null ) { - for ( ArgumentDomainResult argumentResult : argumentResults ) { + for ( var argumentResult : argumentResults ) { final String argumentAlias = argumentResult.getResultVariable(); if ( argumentAlias == null ) { areAllArgumentsAliased = false; @@ -155,19 +155,15 @@ private DomainResultAssembler assembler( List duplicatedAliases, List> argumentReaders, AssemblerCreationState creationState) { - final List> argumentTypes = + // find a constructor matching argument types + final var constructor = findMatchingConstructor( + javaType.getJavaTypeClass(), argumentReaders.stream() - .map(reader -> reader.getAssembledJavaType().getJavaTypeClass()) - .collect(toList()); - final TypeConfiguration typeConfiguration = + .map( reader -> argumentClass( reader ) ) + .collect( toList() ), creationState.getSqlAstCreationContext() .getMappingMetamodel() - .getTypeConfiguration(); - // find a constructor matching argument types - final Constructor constructor = findMatchingConstructor( - javaType.getJavaTypeClass(), - argumentTypes, - typeConfiguration + .getTypeConfiguration() ); if ( constructor != null ) { constructor.setAccessible( true ); @@ -200,6 +196,16 @@ private DomainResultAssembler assembler( return new DynamicInstantiationAssemblerInjectionImpl<>( javaType, argumentReaders ); } + private static Class argumentClass(ArgumentReader reader) { + final var assembledJavaType = reader.getAssembledJavaType(); + return assembledJavaType instanceof DateJavaType temporalJavaType + // Hack to accommodate a constructor with java.sql parameter + // types when the entity has java.util.Date as its field types. + // (This was requested in HHH-4179 and we fixed it by accident.) + ? TemporalJavaType.resolveJavaTypeClass( temporalJavaType.getPrecision() ) + : assembledJavaType.getJavaTypeClass(); + } + private List signature() { return argumentResults.stream() .map( adt -> adt.getResultJavaType().getTypeName() ) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/InstantiationHelper.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/InstantiationHelper.java index faa0bf5813c2..48c33a6ef997 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/InstantiationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/InstantiationHelper.java @@ -13,7 +13,6 @@ import java.beans.PropertyDescriptor; import java.lang.reflect.Constructor; import java.lang.reflect.Field; -import java.lang.reflect.Type; import java.util.List; import static org.hibernate.query.sqm.tree.expression.Compatibility.areAssignmentCompatible; @@ -47,7 +46,7 @@ public static boolean isInjectionCompatible(Class targetJavaType, List targetJavaType, BeanInfo beanInfo, String alias, Class argType) { - for ( PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors() ) { + for ( var propertyDescriptor : beanInfo.getPropertyDescriptors() ) { if ( propertyMatches( alias, argType, propertyDescriptor ) ) { return true; } @@ -63,7 +62,7 @@ public static boolean isConstructorCompatible(Class javaClass, List> Class type, List> argumentTypes, TypeConfiguration typeConfiguration) { - for ( final Constructor constructor : type.getDeclaredConstructors() ) { + for ( var constructor : type.getDeclaredConstructors() ) { if ( isConstructorCompatible( constructor, argumentTypes, typeConfiguration ) ) { //noinspection unchecked return (Constructor) constructor; @@ -79,12 +78,12 @@ public static boolean isConstructorCompatible( final var genericParameterTypes = constructor.getGenericParameterTypes(); if ( genericParameterTypes.length == argumentTypes.size() ) { for (int i = 0; i < argumentTypes.size(); i++ ) { - final Type parameterType = genericParameterTypes[i]; + final var parameterType = genericParameterTypes[i]; final var argumentType = argumentTypes.get( i ); final var type = parameterType instanceof Class classParameter ? classParameter - : typeConfiguration.getJavaTypeRegistry().getDescriptor( parameterType ) + : typeConfiguration.getJavaTypeRegistry().resolveDescriptor( parameterType ) .getJavaTypeClass(); if ( !areAssignmentCompatible( type, argumentType ) ) { if ( LOG.isDebugEnabled() ) { @@ -107,7 +106,7 @@ public static boolean isConstructorCompatible( static Field findField(Class declaringClass, String name, Class javaType) { try { - final Field field = declaringClass.getDeclaredField( name ); + final var field = declaringClass.getDeclaredField( name ); // field should never be null if ( areAssignmentCompatible( field.getType(), javaType ) ) { field.setAccessible( true ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardRowReader.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardRowReader.java index 5b9901f60ca2..4499d498a896 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardRowReader.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardRowReader.java @@ -13,7 +13,6 @@ import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.InitializerData; -import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingResolution; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.sql.results.spi.RowReader; @@ -25,7 +24,6 @@ /** * @author Steve Ebersole */ -@SuppressWarnings("rawtypes") public class StandardRowReader implements RowReader { private final DomainResultAssembler[] resultAssemblers; private final Initializer[] resultInitializers; @@ -36,7 +34,7 @@ public class StandardRowReader implements RowReader { private final InitializerData[] sortedForResolveInstanceData; private final boolean hasCollectionInitializers; private final @Nullable RowTransformer rowTransformer; - private final Class domainResultJavaType; + private final @Nullable Class domainResultJavaType; private final ComponentType componentType; private final Class resultElementClass; @@ -44,7 +42,7 @@ public class StandardRowReader implements RowReader { public StandardRowReader( JdbcValuesMappingResolution jdbcValuesMappingResolution, RowTransformer rowTransformer, - Class domainResultJavaType) { + @Nullable Class domainResultJavaType) { this( jdbcValuesMappingResolution.getDomainResultAssemblers(), jdbcValuesMappingResolution.getResultInitializers(), @@ -63,7 +61,7 @@ public StandardRowReader( Initializer[] sortedForResolveInitializers, boolean hasCollectionInitializers, RowTransformer rowTransformer, - Class domainResultJavaType) { + @Nullable Class domainResultJavaType) { this.resultAssemblers = resultAssemblers; this.resultInitializers = (Initializer[]) resultInitializers; this.resultInitializersData = new InitializerData[resultInitializers.length]; @@ -72,11 +70,12 @@ public StandardRowReader( this.sortedForResolveInstance = (Initializer[]) sortedForResolveInitializers; this.sortedForResolveInstanceData = new InitializerData[sortedForResolveInstance.length]; this.hasCollectionInitializers = hasCollectionInitializers; - this.rowTransformer = rowTransformer == RowTransformerArrayImpl.instance() && resultAssemblers.length != 1 + this.rowTransformer = + rowTransformer == RowTransformerArrayImpl.instance() && resultAssemblers.length != 1 || rowTransformer == RowTransformerStandardImpl.instance() || rowTransformer == RowTransformerSingularReturnImpl.instance() && resultAssemblers.length == 1 - ? null - : rowTransformer; + ? null + : rowTransformer; this.domainResultJavaType = domainResultJavaType; if ( domainResultJavaType == null || domainResultJavaType == Object[].class @@ -100,8 +99,8 @@ public Class getDomainResultResultJavaType() { @Override public List<@Nullable JavaType> getResultJavaTypes() { - List> javaTypes = new ArrayList<>( resultAssemblers.length ); - for ( DomainResultAssembler resultAssembler : resultAssemblers ) { + final List> javaTypes = new ArrayList<>( resultAssemblers.length ); + for ( var resultAssembler : resultAssemblers ) { javaTypes.add( resultAssembler.getAssembledJavaType() ); } return javaTypes; @@ -114,15 +113,18 @@ public int getInitializerCount() { @Override public @Nullable EntityKey resolveSingleResultEntityKey(RowProcessingState rowProcessingState) { - final EntityInitializer entityInitializer = resultInitializers.length == 0 - ? null - : resultInitializers[0].asEntityInitializer(); + final var entityInitializer = + resultInitializers.length == 0 + ? null + : resultInitializers[0].asEntityInitializer(); if ( entityInitializer == null ) { return null; } - final EntityKey entityKey = entityInitializer.resolveEntityKeyOnly( rowProcessingState ); - finishUpRow(); - return entityKey; + else { + final var entityKey = entityInitializer.resolveEntityKeyOnly( rowProcessingState ); + finishUpRow(); + return entityKey; + } } @Override @@ -131,36 +133,34 @@ public boolean hasCollectionInitializers() { } @Override - @AllowReflection public T readRow(RowProcessingState rowProcessingState) { - coordinateInitializers( rowProcessingState ); + coordinateInitializers(); + final T result = getResult( rowProcessingState ); + finishUpRow(); + return result; + } - final T result; + @AllowReflection + @SuppressWarnings("unchecked") + private T getResult(RowProcessingState rowProcessingState) { if ( componentType != ComponentType.OBJECT ) { - result = readPrimitiveRow( rowProcessingState ); + return (T) readPrimitiveRow( rowProcessingState ); + } + else if ( resultAssemblers.length == 1 && rowTransformer == null ) { + return (T) resultAssemblers[0].assemble( rowProcessingState ); } else { - if ( resultAssemblers.length == 1 && rowTransformer == null ) { - //noinspection unchecked - result = (T) resultAssemblers[0].assemble( rowProcessingState ); - } - else { - final Object[] resultRow = (Object[]) Array.newInstance( resultElementClass, resultAssemblers.length ); - for ( int i = 0; i < resultAssemblers.length; i++ ) { - resultRow[i] = resultAssemblers[i].assemble( rowProcessingState ); - } - //noinspection unchecked - result = rowTransformer == null - ? (T) resultRow - : rowTransformer.transformRow( resultRow ); + final var resultRow = (Object[]) Array.newInstance( resultElementClass, resultAssemblers.length ); + for ( int i = 0; i < resultAssemblers.length; i++ ) { + resultRow[i] = resultAssemblers[i].assemble( rowProcessingState ); } + return rowTransformer == null + ? (T) resultRow + : rowTransformer.transformRow( resultRow ); } - - finishUpRow(); - return result; } - private T readPrimitiveRow(RowProcessingState rowProcessingState) { + private Object readPrimitiveRow(RowProcessingState rowProcessingState) { // The following is ugly, but unfortunately necessary to not hurt performance. // This implementation was micro-benchmarked and discussed with Francesco Nigro, // who hinted that using this style instead of the reflective Array.getLength(), Array.set() @@ -171,61 +171,61 @@ private T readPrimitiveRow(RowProcessingState rowProcessingState) { for ( int i = 0; i < resultAssemblers.length; i++ ) { resultBooleanRow[i] = (boolean) resultAssemblers[i].assemble( rowProcessingState ); } - return (T) resultBooleanRow; + return resultBooleanRow; case BYTE: final byte[] resultByteRow = new byte[resultAssemblers.length]; for ( int i = 0; i < resultAssemblers.length; i++ ) { resultByteRow[i] = (byte) resultAssemblers[i].assemble( rowProcessingState ); } - return (T) resultByteRow; + return resultByteRow; case CHAR: final char[] resultCharRow = new char[resultAssemblers.length]; for ( int i = 0; i < resultAssemblers.length; i++ ) { resultCharRow[i] = (char) resultAssemblers[i].assemble( rowProcessingState ); } - return (T) resultCharRow; + return resultCharRow; case SHORT: final short[] resultShortRow = new short[resultAssemblers.length]; for ( int i = 0; i < resultAssemblers.length; i++ ) { resultShortRow[i] = (short) resultAssemblers[i].assemble( rowProcessingState ); } - return (T) resultShortRow; + return resultShortRow; case INT: final int[] resultIntRow = new int[resultAssemblers.length]; for ( int i = 0; i < resultAssemblers.length; i++ ) { resultIntRow[i] = (int) resultAssemblers[i].assemble( rowProcessingState ); } - return (T) resultIntRow; + return resultIntRow; case LONG: final long[] resultLongRow = new long[resultAssemblers.length]; for ( int i = 0; i < resultAssemblers.length; i++ ) { resultLongRow[i] = (long) resultAssemblers[i].assemble( rowProcessingState ); } - return (T) resultLongRow; + return resultLongRow; case FLOAT: final float[] resultFloatRow = new float[resultAssemblers.length]; for ( int i = 0; i < resultAssemblers.length; i++ ) { resultFloatRow[i] = (float) resultAssemblers[i].assemble( rowProcessingState ); } - return (T) resultFloatRow; + return resultFloatRow; case DOUBLE: final double[] resultDoubleRow = new double[resultAssemblers.length]; for ( int i = 0; i < resultAssemblers.length; i++ ) { resultDoubleRow[i] = (double) resultAssemblers[i].assemble( rowProcessingState ); } - return (T) resultDoubleRow; + return resultDoubleRow; default: throw new AssertionError( "Object should be handled specially" ); } } private void finishUpRow() { - for ( InitializerData data : initializersData ) { + for ( var data : initializersData ) { data.setState( Initializer.State.UNINITIALIZED ); } } - private void coordinateInitializers(RowProcessingState rowProcessingState) { + private void coordinateInitializers() { for ( int i = 0; i < resultInitializers.length; i++ ) { resultInitializers[i].resolveKey( resultInitializersData[i] ); } @@ -244,7 +244,7 @@ private void coordinateInitializers(RowProcessingState rowProcessingState) { @Override public void startLoading(RowProcessingState processingState) { for ( int i = 0; i < resultInitializers.length; i++ ) { - final Initializer initializer = resultInitializers[i]; + final var initializer = resultInitializers[i]; initializer.startLoading( processingState ); resultInitializersData[i] = initializer.getData( processingState ); } @@ -305,7 +305,9 @@ else if ( resultType == float[].class) { else if ( resultType == double[].class) { return DOUBLE; } - return OBJECT; + else { + return OBJECT; + } } public Class getComponentType() { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/TupleImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/TupleImpl.java index 2409c6c0d353..d53ed305f766 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/TupleImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/TupleImpl.java @@ -10,7 +10,8 @@ import jakarta.persistence.Tuple; import jakarta.persistence.TupleElement; -import static org.hibernate.internal.util.type.PrimitiveWrapperHelper.getDescriptorByPrimitiveType; +import static org.hibernate.internal.util.type.PrimitiveWrappers.cast; +import static org.hibernate.internal.util.type.PrimitiveWrappers.isInstance; /** * Implementation of the JPA Tuple contract @@ -27,7 +28,6 @@ public TupleImpl(TupleMetadata tupleMetadata, Object[] row) { } @Override - @SuppressWarnings("unchecked") public X get(TupleElement tupleElement) { final Integer index = tupleMetadata.get( tupleElement ); if ( index == null ) { @@ -36,15 +36,14 @@ public X get(TupleElement tupleElement) { ); } // index should be "in range" by nature of size check in ctor - return (X) row[index]; + return cast( tupleElement.getJavaType(), row[index] ); } @Override - @SuppressWarnings("unchecked") public X get(String alias, Class type) { final Object untyped = get( alias ); if ( untyped != null ) { - if ( !elementTypeMatches( type, untyped ) ) { + if ( !isInstance( type, untyped ) ) { throw new IllegalArgumentException( String.format( "Requested tuple value [alias=%s, value=%s] cannot be assigned to requested type [%s]", @@ -55,12 +54,12 @@ public X get(String alias, Class type) { ); } } - return (X) untyped; + return cast( type, untyped ); } @Override public Object get(String alias) { - Integer index = tupleMetadata.get( alias ); + final Integer index = tupleMetadata.get( alias ); if ( index == null ) { throw new IllegalArgumentException( "Given alias [" + alias + "] did not correspond to an element in the result tuple" @@ -71,10 +70,9 @@ public Object get(String alias) { } @Override - @SuppressWarnings("unchecked") public X get(int i, Class type) { final Object result = get( i ); - if ( result != null && !elementTypeMatches( type, result ) ) { + if ( result != null && !isInstance( type, result ) ) { throw new IllegalArgumentException( String.format( "Requested tuple value [index=%s, realType=%s] cannot be assigned to requested type [%s]", @@ -84,7 +82,7 @@ public X get(int i, Class type) { ) ); } - return (X) result; + return cast( type, result ); } @Override @@ -97,11 +95,6 @@ public Object get(int i) { return row[i]; } - private boolean elementTypeMatches(Class type, Object untyped) { - return type.isInstance( untyped ) - || type.isPrimitive() && getDescriptorByPrimitiveType( type ).getWrapperClass().isInstance( untyped ); - } - @Override public Object[] toArray() { return row; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/AbstractJdbcValues.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/AbstractJdbcValues.java index 26f6f835cdcf..1433fa7383e7 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/AbstractJdbcValues.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/AbstractJdbcValues.java @@ -21,9 +21,9 @@ public final boolean next(RowProcessingState rowProcessingState) { @Override public boolean previous(RowProcessingState rowProcessingState) { - // NOTE : we do not even bother interacting with the query-cache put manager because - // this method is implicitly related to scrolling and caching of scrolled results - // is not supported + // NOTE: we do not even bother interacting with the query-cache put manager because + // this method is implicitly related to scrolling and caching of scrolled results + // is not supported return processPrevious( rowProcessingState ); } @@ -31,9 +31,9 @@ public boolean previous(RowProcessingState rowProcessingState) { @Override public boolean scroll(int numberOfRows, RowProcessingState rowProcessingState) { - // NOTE : we do not even bother interacting with the query-cache put manager because - // this method is implicitly related to scrolling and caching of scrolled results - // is not supported + // NOTE: we do not even bother interacting with the query-cache put manager because + // this method is implicitly related to scrolling and caching of scrolled results + // is not supported return processScroll( numberOfRows, rowProcessingState ); } @@ -41,9 +41,9 @@ public boolean scroll(int numberOfRows, RowProcessingState rowProcessingState) { @Override public boolean position(int position, RowProcessingState rowProcessingState) { - // NOTE : we do not even bother interacting with the query-cache put manager because - // this method is implicitly related to scrolling and caching of scrolled results - // is not supported + // NOTE: we do not even bother interacting with the query-cache put manager because + // this method is implicitly related to scrolling and caching of scrolled results + // is not supported return processPosition( position, rowProcessingState ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/AbstractResultSetAccess.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/AbstractResultSetAccess.java index 99184c081997..2154f395915c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/AbstractResultSetAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/AbstractResultSetAccess.java @@ -17,7 +17,6 @@ import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; -import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.spi.TypeConfiguration; /** @@ -108,27 +107,28 @@ public int getResultCountEstimate() { @Override public BasicType resolveType(int position, JavaType explicitJavaType, TypeConfiguration typeConfiguration) { try { - final ResultSetMetaData metaData = getResultSetMetaData(); - final JdbcTypeRegistry registry = typeConfiguration.getJdbcTypeRegistry(); + final var metaData = getResultSetMetaData(); + final var registry = typeConfiguration.getJdbcTypeRegistry(); final String columnTypeName = metaData.getColumnTypeName( position ); final int columnType = metaData.getColumnType( position ); final int scale = metaData.getScale( position ); final int precision = metaData.getPrecision( position ); final int displaySize = metaData.getColumnDisplaySize( position ); - final Dialect dialect = getDialect(); + final var dialect = getDialect(); final int length = dialect.resolveSqlTypeLength( columnTypeName, columnType, precision, scale, displaySize ); - final JdbcType resolvedJdbcType = + final var resolvedJdbcType = dialect.resolveSqlTypeDescriptor( columnTypeName, columnType, length, scale, registry ); - final JdbcType jdbcType = + final var jdbcType = explicitJavaType == null ? resolvedJdbcType : jdbcType( explicitJavaType, resolvedJdbcType, length, precision, scale, typeConfiguration ); // If there is an explicit JavaType, then prefer its recommended JDBC type - final JavaType javaType = + final var javaType = explicitJavaType == null - ? jdbcType.getJdbcRecommendedJavaTypeMapping( length, scale, typeConfiguration ) + ? jdbcType.getRecommendedJavaType( length, scale, typeConfiguration ) : explicitJavaType; - return typeConfiguration.getBasicTypeRegistry().resolve( javaType, jdbcType ); + return typeConfiguration.getBasicTypeRegistry() + .resolve( (JavaType) javaType, jdbcType ); } catch (SQLException e) { throw getSqlExceptionHelper() diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/CachedJdbcValuesMetadata.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/CachedJdbcValuesMetadata.java index ba45c9873eb1..8d07a3a374a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/CachedJdbcValuesMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/CachedJdbcValuesMetadata.java @@ -6,12 +6,13 @@ import java.io.Serializable; -import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.spi.TypeConfiguration; +import static org.hibernate.internal.util.collections.ArrayHelper.indexOf; + public final class CachedJdbcValuesMetadata implements JdbcValuesMetadata, Serializable { private final String[] columnNames; private final BasicType[] types; @@ -28,7 +29,7 @@ public int getColumnCount() { @Override public int resolveColumnPosition(String columnName) { - final int position = ArrayHelper.indexOf( columnNames, columnName ) + 1; + final int position = indexOf( columnNames, columnName ) + 1; if ( position == 0 ) { throw new IllegalStateException( "Unexpected resolving of unavailable column: " + columnName ); } @@ -49,7 +50,7 @@ public BasicType resolveType( int position, JavaType explicitJavaType, TypeConfiguration typeConfiguration) { - final BasicType type = types[position - 1]; + final var type = types[position - 1]; if ( type == null ) { throw new IllegalStateException( "Unexpected resolving of unavailable column at position: " + position ); } @@ -58,10 +59,8 @@ public BasicType resolveType( return (BasicType) type; } else { - return typeConfiguration.getBasicTypeRegistry().resolve( - explicitJavaType, - type.getJdbcType() - ); + return typeConfiguration.getBasicTypeRegistry() + .resolve( explicitJavaType, type.getJdbcType() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java index fde5c29cf3ce..4a2060226842 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java @@ -9,14 +9,10 @@ import org.hibernate.dialect.Dialect; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.NoopLimitHandler; -import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.engine.spi.SessionEventListenerManager; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.event.monitor.spi.DiagnosticEvent; -import org.hibernate.event.monitor.spi.EventMonitor; import org.hibernate.query.spi.Limit; import org.hibernate.query.spi.QueryOptions; import org.hibernate.resource.jdbc.spi.JdbcSessionContext; @@ -62,7 +58,7 @@ public DeferredResultSetAccess( JdbcSelectExecutor.StatementCreator statementCreator, int resultCountEstimate) { super( executionContext.getSession() ); - final JdbcServices jdbcServices = executionContext.getSession().getJdbcServices(); + final var jdbcServices = executionContext.getSession().getJdbcServices(); this.jdbcParameterBindings = jdbcParameterBindings; this.executionContext = executionContext; @@ -71,7 +67,7 @@ public DeferredResultSetAccess( this.sqlStatementLogger = jdbcServices.getSqlStatementLogger(); this.resultCountEstimate = resultCountEstimate; - final QueryOptions queryOptions = executionContext.getQueryOptions(); + final var queryOptions = executionContext.getQueryOptions(); if ( queryOptions == null ) { finalSql = jdbcSelect.getSqlString(); limit = null; @@ -80,41 +76,54 @@ public DeferredResultSetAccess( else { // Note that limit and lock aren't set for SQM as that is applied during SQL rendering // But for native queries, we have to adapt the SQL string - final Dialect dialect = jdbcServices.getDialect(); + final var dialect = jdbcServices.getDialect(); final String sql = jdbcSelect.getSqlString(); limit = queryOptions.getLimit(); final boolean needsLimitHandler = needsLimitHandler( jdbcSelect ); limitHandler = needsLimitHandler ? dialect.getLimitHandler() : NoopLimitHandler.NO_LIMIT; - final String sqlWithLimit = !needsLimitHandler ? sql : limitHandler.processSql( - sql, - jdbcParameterBindings.getBindings().size(), - jdbcServices.getParameterMarkerStrategy(), - queryOptions - ); + final String sqlWithLimit = + needsLimitHandler + ? sqlWithLimit( jdbcParameterBindings, sql, jdbcServices, queryOptions ) + : sql; + + final String sqlWithLocking = + sqlWithLocking( jdbcSelect.getLockStrategy(), sqlWithLimit, queryOptions, dialect ); + + finalSql = + dialect.addSqlHintOrComment( sqlWithLocking, queryOptions, + executionContext.getSession().getFactory() + .getSessionFactoryOptions().isCommentsEnabled() ); + } + } - final var lockOptions = queryOptions.getLockOptions(); - final var jdbcLockStrategy = jdbcSelect.getLockStrategy(); - final String sqlWithLocking; - if ( hasLocking( jdbcLockStrategy, lockOptions ) ) { - final boolean usesFollowOnLocking = useFollowOnLocking( jdbcLockStrategy, sqlWithLimit, queryOptions, lockOptions, dialect ); - if ( usesFollowOnLocking ) { - sqlWithLocking = sqlWithLimit; - } - else { - sqlWithLocking = dialect.applyLocksToSql( sqlWithLimit, lockOptions, emptyMap() ); - } - } - else { - sqlWithLocking = sqlWithLimit; - } + private String sqlWithLimit( + JdbcParameterBindings jdbcParameterBindings, + String sql, + JdbcServices jdbcServices, + QueryOptions queryOptions) { + return limitHandler.processSql( + sql, + jdbcParameterBindings.getBindings().size(), + jdbcServices.getParameterMarkerStrategy(), + queryOptions + ); + } - final boolean commentsEnabled = executionContext.getSession() - .getFactory() - .getSessionFactoryOptions() - .isCommentsEnabled(); - finalSql = dialect.addSqlHintOrComment( sqlWithLocking, queryOptions, commentsEnabled ); + private static String sqlWithLocking( + JdbcLockStrategy jdbcLockStrategy, + String sqlWithLimit, + QueryOptions queryOptions, + Dialect dialect) { + final var lockOptions = queryOptions.getLockOptions(); + if ( hasLocking( jdbcLockStrategy, lockOptions ) ) { + return useFollowOnLocking( jdbcLockStrategy, sqlWithLimit, queryOptions, lockOptions, dialect ) + ? sqlWithLimit + : dialect.applyLocksToSql( sqlWithLimit, lockOptions, emptyMap() ); + } + else { + return sqlWithLimit; } } @@ -221,7 +230,7 @@ protected void bindParameters(PreparedStatement preparedStatement) throws SQLExc } private void setQueryOptions(PreparedStatement preparedStatement) throws SQLException { - final QueryOptions queryOptions = executionContext.getQueryOptions(); + final var queryOptions = executionContext.getQueryOptions(); // set options if ( queryOptions != null ) { final Integer fetchSize = queryOptions.getFetchSize(); @@ -238,10 +247,10 @@ private void setQueryOptions(PreparedStatement preparedStatement) throws SQLExce } private void executeQuery() { - final LogicalConnectionImplementor logicalConnection = + final var logicalConnection = getPersistenceContext().getJdbcCoordinator().getLogicalConnection(); - final SharedSessionContractImplementor session = executionContext.getSession(); + final var session = executionContext.getSession(); try { CORE_LOGGER.tracef( "Executing query to retrieve ResultSet: %s", finalSql ); // prepare the query @@ -254,8 +263,8 @@ private void executeQuery() { if ( sqlStatementLogger.getLogSlowQuery() > 0 ) { executeStartNanos = System.nanoTime(); } - final EventMonitor eventMonitor = session.getEventMonitor(); - final DiagnosticEvent jdbcPreparedStatementExecutionEvent = + final var eventMonitor = session.getEventMonitor(); + final var jdbcPreparedStatementExecutionEvent = eventMonitor.beginJdbcPreparedStatementExecutionEvent(); try { eventListenerManager.jdbcExecuteStatementStart(); @@ -326,8 +335,7 @@ protected LockMode determineFollowOnLockMode(LockOptions lockOptions) { @Override public void release() { - final JdbcCoordinator jdbcCoordinator = - getPersistenceContext().getJdbcCoordinator(); + final var jdbcCoordinator = getPersistenceContext().getJdbcCoordinator(); final LogicalConnectionImplementor logicalConnection = jdbcCoordinator.getLogicalConnection(); if ( resultSet != null ) { logicalConnection.getResourceRegistry().release( resultSet, preparedStatement ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DirectResultSetAccess.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DirectResultSetAccess.java index bc2a40a3f88a..8a589ff38565 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DirectResultSetAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DirectResultSetAccess.java @@ -25,7 +25,9 @@ public DirectResultSetAccess( this.resultSetSource = resultSetSource; this.resultSet = resultSet; - persistenceContext.getJdbcCoordinator().getLogicalConnection().getResourceRegistry() + persistenceContext.getJdbcCoordinator() + .getLogicalConnection() + .getResourceRegistry() .register( resultSet, resultSetSource ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesCacheHit.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesCacheHit.java index 45c7f0a037a8..200b050df28e 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesCacheHit.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesCacheHit.java @@ -36,47 +36,44 @@ public JdbcValuesCacheHit(List cachedResults, JdbcValuesMapping resolvedMappi @Override protected boolean processNext(RowProcessingState rowProcessingState) { - // NOTE : explicitly skipping limit handling because the cached state ought - // already be the limited size since the cache key includes limits - + // NOTE: explicitly skipping limit handling because the cached state ought + // already be the limited size since the cache key includes limits position++; - if ( position >= numberOfRows ) { position = numberOfRows; return false; } - - return true; + else { + return true; + } } @Override protected boolean processPrevious(RowProcessingState rowProcessingState) { - // NOTE : explicitly skipping limit handling because the cached state ought - // already be the limited size since the cache key includes limits - + // NOTE: explicitly skipping limit handling because the cached state ought + // already be the limited size since the cache key includes limits position--; - if ( position >= numberOfRows ) { position = numberOfRows; return false; } - - return true; + else { + return true; + } } @Override protected boolean processScroll(int numberOfRows, RowProcessingState rowProcessingState) { - // NOTE : explicitly skipping limit handling because the cached state should - // already be the limited size since the cache key includes limits - + // NOTE: explicitly skipping limit handling because the cached state should + // already be the limited size since the cache key includes limits position += numberOfRows; - if ( position >= this.numberOfRows ) { position = this.numberOfRows; return false; } - - return true; + else { + return true; + } } @Override @@ -86,8 +83,8 @@ public int getPosition() { @Override protected boolean processPosition(int position, RowProcessingState rowProcessingState) { - // NOTE : explicitly skipping limit handling because the cached state should - // already be the limited size since the cache key includes limits + // NOTE: explicitly skipping limit handling because the cached state should + // already be the limited size since the cache key includes limits if ( position < 0 ) { // we need to subtract it from `numberOfRows` @@ -102,9 +99,10 @@ protected boolean processPosition(int position, RowProcessingState rowProcessing this.position = numberOfRows; return false; } - - this.position = position; - return true; + else { + this.position = position; + return true; + } } @Override @@ -140,12 +138,9 @@ public void afterLast(RowProcessingState rowProcessingState) { @Override public boolean isLast(RowProcessingState rowProcessingState) { - if ( numberOfRows == 0 ) { - return position == 0; - } - else { - return position == numberOfRows - 1; - } + return numberOfRows == 0 + ? position == 0 + : position == numberOfRows - 1; } @Override @@ -154,9 +149,10 @@ public boolean last(RowProcessingState rowProcessingState) { position = 0; return false; } - - position = numberOfRows - 1; - return true; + else { + position = numberOfRows - 1; + return true; + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesMappingProducerStandard.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesMappingProducerStandard.java index a7af8c6dc243..5d7256703b43 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesMappingProducerStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesMappingProducerStandard.java @@ -50,8 +50,8 @@ public JdbcValuesMapping resolve( final List sqlSelections = resolvedMapping.getSqlSelections(); List resolvedSelections = null; for ( int i = 0; i < sqlSelections.size(); i++ ) { - final SqlSelection sqlSelection = sqlSelections.get( i ); - final SqlSelection resolvedSelection = sqlSelection.resolve( jdbcResultsMetadata, sessionFactory ); + final var sqlSelection = sqlSelections.get( i ); + final var resolvedSelection = sqlSelection.resolve( jdbcResultsMetadata, sessionFactory ); if ( resolvedSelection != sqlSelection ) { if ( resolvedSelections == null ) { resolvedSelections = new ArrayList<>( sqlSelections ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesMappingResolutionImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesMappingResolutionImpl.java index fb54e6a4bdc6..2e7414d4ab93 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesMappingResolutionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesMappingResolutionImpl.java @@ -22,7 +22,10 @@ public JdbcValuesMappingResolutionImpl( DomainResultAssembler[] domainResultAssemblers, boolean hasCollectionInitializers, InitializersList initializersList) { - this( domainResultAssemblers, getResultInitializers( domainResultAssemblers ), hasCollectionInitializers, initializersList ); + this( domainResultAssemblers, + getResultInitializers( domainResultAssemblers ), + hasCollectionInitializers, + initializersList ); } private JdbcValuesMappingResolutionImpl( @@ -38,7 +41,7 @@ private JdbcValuesMappingResolutionImpl( private static Initializer[] getResultInitializers(DomainResultAssembler[] resultAssemblers) { final LinkedHashSet> initializers = new LinkedHashSet<>( resultAssemblers.length ); - for ( DomainResultAssembler resultAssembler : resultAssemblers ) { + for ( var resultAssembler : resultAssemblers ) { resultAssembler.forEachResultAssembler( (initializer, list) -> list.add( initializer ), initializers ); } return initializers.toArray(Initializer.EMPTY_ARRAY); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesResultSetImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesResultSetImpl.java index 0dd467d924e0..0a20cabb211e 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesResultSetImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesResultSetImpl.java @@ -9,11 +9,8 @@ import java.util.Arrays; import java.util.BitSet; -import org.hibernate.JDBCException; import org.hibernate.QueryTimeoutException; import org.hibernate.cache.spi.QueryKey; -import org.hibernate.cache.spi.QueryResultsCache; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.exception.DataException; import org.hibernate.exception.LockTimeoutException; @@ -27,6 +24,8 @@ import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; +import static java.util.Arrays.copyOf; + /** * {@link AbstractJdbcValues} implementation for a JDBC {@link ResultSet} as the source * @@ -77,7 +76,7 @@ public JdbcValuesResultSetImpl( final int rowSize = valuesMapping.getRowSize(); this.sqlSelections = new SqlSelection[rowSize]; - for ( SqlSelection selection : valuesMapping.getSqlSelections() ) { + for ( var selection : valuesMapping.getSqlSelections() ) { this.sqlSelections[selection.getValuesArrayPosition()] = selection; } this.initializedIndexes = new BitSet( rowSize ); @@ -127,11 +126,10 @@ private static QueryCachePutManager resolveQueryCachePutManager( String queryIdentifier, CachedJdbcValuesMetadata metadataForCache) { if ( queryCacheKey != null ) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final QueryResultsCache queryCache = factory.getCache() - .getQueryResultsCache( queryOptions.getResultCacheRegionName() ); + final var factory = executionContext.getSession().getFactory(); return new QueryCachePutManagerEnabledImpl( - queryCache, + factory.getCache() + .getQueryResultsCache( queryOptions.getResultCacheRegionName() ), factory.getStatistics(), queryCacheKey, queryIdentifier, @@ -300,18 +298,16 @@ private boolean advancePrevious() { } private boolean advance(final boolean hasResult) { - if ( ! hasResult ) { - return false; + if ( hasResult ) { + readCurrentRowValues(); } - - readCurrentRowValues(); - return true; + return hasResult; } private ExecutionException makeExecutionException(String message, SQLException cause) { - final JDBCException jdbcException = - executionContext.getSession().getJdbcServices().getSqlExceptionHelper() - .convert( cause, message ); + final var jdbcException = + executionContext.getSession().getJdbcServices() + .getSqlExceptionHelper().convert( cause, message ); if ( jdbcException instanceof QueryTimeoutException || jdbcException instanceof DataException || jdbcException instanceof LockTimeoutException ) { @@ -352,7 +348,7 @@ public void finishRowProcessing(RowProcessingState rowProcessingState, boolean w } final Object objectToCache; if ( valueIndexesToCacheIndexes == null ) { - objectToCache = Arrays.copyOf( currentRowJdbcValues, currentRowJdbcValues.length ); + objectToCache = copyOf( currentRowJdbcValues, currentRowJdbcValues.length ); } else if ( rowToCacheSize < 1 ) { if ( !wasAdded ) { @@ -362,7 +358,7 @@ else if ( rowToCacheSize < 1 ) { objectToCache = currentRowJdbcValues[-rowToCacheSize]; } else { - final Object[] rowToCache = new Object[rowToCacheSize]; + final var rowToCache = new Object[rowToCacheSize]; for ( int i = 0; i < currentRowJdbcValues.length; i++ ) { final int cacheIndex = valueIndexesToCacheIndexes[i]; if ( cacheIndex != -1 ) { @@ -379,7 +375,7 @@ else if ( rowToCacheSize < 1 ) { public Object getCurrentRowValue(int valueIndex) { if ( !initializedIndexes.get( valueIndex ) ) { initializedIndexes.set( valueIndex ); - final SqlSelection sqlSelection = sqlSelections[valueIndex]; + final var sqlSelection = sqlSelections[valueIndex]; final int index = sqlSelection.getJdbcResultSetIndex(); try { currentRowJdbcValues[valueIndex] = sqlSelection.getJdbcValueExtractor().extract( diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java index a45c3200e46f..0b77d5c813db 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java @@ -12,7 +12,6 @@ import org.hibernate.engine.spi.CollectionKey; import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.PostLoadEvent; import org.hibernate.event.spi.PreLoadEvent; import org.hibernate.query.spi.QueryOptions; @@ -46,7 +45,7 @@ public JdbcValuesSourceProcessingStateStandardImpl( this.processingOptions = processingOptions; if ( executionContext.getSession().isEventSource() ) { - final EventSource eventSource = executionContext.getSession().asEventSource(); + final var eventSource = executionContext.getSession().asEventSource(); preLoadEvent = new PreLoadEvent( eventSource ); postLoadEvent = new PostLoadEvent( eventSource ); } @@ -151,33 +150,19 @@ public SharedSessionContractImplementor getSession() { public void finishUp(boolean registerSubselects) { // now we can finalize loading collections finishLoadingCollections(); - getSession().getPersistenceContextInternal() .postLoad( this, registerSubselects ? executionContext::registerLoadingEntityHolder : null ); } - private boolean isReadOnly() { - if ( getQueryOptions().isReadOnly() != null ) { - return getQueryOptions().isReadOnly(); - } - else if ( getSession() instanceof EventSource ) { - return getSession().isDefaultReadOnly(); - } - else { - return false; - } - } - /** * For Hibernate Reactive */ public void finishLoadingCollections() { if ( loadingCollectionMap != null ) { - for ( LoadingCollectionEntry loadingCollectionEntry : loadingCollectionMap.values() ) { + for ( var loadingCollectionEntry : loadingCollectionMap.values() ) { loadingCollectionEntry.finishLoading( getExecutionContext() ); } - loadingCollectionMap = null; } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/StandardJdbcValuesMapping.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/StandardJdbcValuesMapping.java index e83c28974af9..5e0736432f70 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/StandardJdbcValuesMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/StandardJdbcValuesMapping.java @@ -6,6 +6,7 @@ import java.util.BitSet; import java.util.List; +import java.util.Objects; import java.util.function.Supplier; import org.hibernate.LockMode; @@ -55,15 +56,15 @@ public StandardJdbcValuesMapping( this.domainResults = domainResults; final int rowSize = sqlSelections.size(); - final BitSet valueIndexesToCache = new BitSet( rowSize ); - for ( DomainResult domainResult : domainResults ) { + final var valueIndexesToCache = new BitSet( rowSize ); + for ( var domainResult : domainResults ) { domainResult.collectValueIndexesToCache( valueIndexesToCache ); } final int[] valueIndexesToCacheIndexes = new int[rowSize]; int cacheIndex = 0; boolean needsResolve = false; for ( int i = 0; i < valueIndexesToCacheIndexes.length; i++ ) { - final SqlSelection sqlSelection = sqlSelections.get( i ); + final var sqlSelection = sqlSelections.get( i ); needsResolve = needsResolve || sqlSelection instanceof SqlSelectionImpl selection && selection.needsResolve(); if ( valueIndexesToCache.get( i ) ) { @@ -114,7 +115,7 @@ public JdbcValuesMappingResolution resolveAssemblers(SessionFactoryImplementor s return resolution; } else { - final AssemblerCreationStateImpl creationState = + final var creationState = new AssemblerCreationStateImpl( this, sessionFactory.getSqlTranslationEngine() ); final var domainResultAssemblers = resolveAssemblers( creationState ); @@ -132,7 +133,7 @@ private DomainResultAssembler[] resolveAssemblers(AssemblerCreationState crea final int size = domainResults.size(); final List> assemblers = arrayList( size ); for ( int i = 0; i < size; i++ ) { - final DomainResultAssembler resultAssembler = + final var resultAssembler = domainResults.get( i ) .createResultAssembler( null, creationState ); assemblers.add( resultAssembler ); @@ -166,9 +167,9 @@ public AssemblerCreationStateImpl( @Override public boolean isDynamicInstantiation() { if ( dynamicInstantiation == null ) { - dynamicInstantiation = jdbcValuesMapping.getDomainResults() - .stream() - .anyMatch( domainResult -> domainResult instanceof DynamicInstantiationResult ); + dynamicInstantiation = + jdbcValuesMapping.getDomainResults().stream() + .anyMatch( domainResult -> domainResult instanceof DynamicInstantiationResult ); } return dynamicInstantiation; } @@ -177,7 +178,7 @@ public boolean isDynamicInstantiation() { public boolean containsMultipleCollectionFetches() { if ( containsMultipleCollectionFetches == null ) { int collectionFetchesCount = 0; - for ( DomainResult domainResult : jdbcValuesMapping.getDomainResults() ) { + for ( var domainResult : jdbcValuesMapping.getDomainResults() ) { if ( domainResult instanceof FetchParent fetchParent ) { collectionFetchesCount += fetchParent.getCollectionFetchesCount(); } @@ -226,16 +227,15 @@ public Initializer resolveInitializer( T resultGraphNode, InitializerParent parent, InitializerProducer producer) { - final Initializer existing = initializerMap.get( navigablePath ); - if ( existing != null ) { - if ( fetchedModelPart.getNavigableRole().equals( + final var existing = initializerMap.get( navigablePath ); + if ( existing != null + && Objects.equals( fetchedModelPart.getNavigableRole(), existing.getInitializedPart().getNavigableRole() ) ) { - RESULTS_MESSAGE_LOGGER.tracef( "Returning previously-registered initializer: %s", existing ); - return existing; - } + RESULTS_MESSAGE_LOGGER.tracef( "Returning previously-registered initializer: %s", existing ); + return existing; } - final Initializer initializer = producer.createInitializer( resultGraphNode, parent, this ); + final var initializer = producer.createInitializer( resultGraphNode, parent, this ); RESULTS_MESSAGE_LOGGER.tracef( "Registering initializer: %s", initializer ); if ( initializer instanceof AbstractImmediateCollectionInitializer ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/ListResultsConsumer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/ListResultsConsumer.java index 1698411f91c7..b467d43e4a96 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/ListResultsConsumer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/ListResultsConsumer.java @@ -10,7 +10,6 @@ import java.util.Locale; import org.hibernate.HibernateException; -import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.ResultListTransformer; import org.hibernate.sql.exec.spi.ExecutionContext; @@ -20,7 +19,6 @@ import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.spi.EntityJavaType; -import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.spi.TypeConfiguration; import org.checkerframework.checker.nullness.qual.Nullable; @@ -150,11 +148,11 @@ public List consume( rowReader.startLoading( rowProcessingState ); RuntimeException ex = null; - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + final var persistenceContext = session.getPersistenceContextInternal(); persistenceContext.beforeLoad(); persistenceContext.getLoadContexts().register( jdbcValuesSourceProcessingState ); try { - final JavaType domainResultJavaType = resolveDomainResultJavaType( + final var domainResultJavaType = resolveDomainResultJavaType( rowReader.getDomainResultResultJavaType(), rowReader.getResultJavaTypes(), session.getTypeConfiguration() @@ -162,7 +160,7 @@ public List consume( final boolean isEntityResultType = domainResultJavaType instanceof EntityJavaType; final int initialCollectionSize = Math.min( jdbcValues.getResultCountEstimate(), INITIAL_COLLECTION_SIZE_LIMIT ); - final Results results = createResults( isEntityResultType, domainResultJavaType, initialCollectionSize ); + final var results = createResults( isEntityResultType, domainResultJavaType, initialCollectionSize ); final int readRows = readRows( rowProcessingState, rowReader, isEntityResultType, results ); rowReader.finishUp( rowProcessingState ); jdbcValuesSourceProcessingState.finishUp( readRows > 1 ); @@ -209,13 +207,14 @@ private Results createResults( boolean isEntityResultType, JavaType domainResultJavaType, int initialCollectionSize) { - if ( isEntityResultType - && ( uniqueSemantic == UniqueSemantic.ALLOW || uniqueSemantic == UniqueSemantic.FILTER ) ) { - return new EntityResult<>( domainResultJavaType, initialCollectionSize ); - } - else { - return new Results<>( domainResultJavaType, initialCollectionSize ); - } + return isEntityResultType && isAllowOrFilter() + ? new EntityResult<>( domainResultJavaType, initialCollectionSize ) + : new Results<>( domainResultJavaType, initialCollectionSize ); + } + + private boolean isAllowOrFilter() { + return uniqueSemantic == UniqueSemantic.ALLOW + || uniqueSemantic == UniqueSemantic.FILTER; } private int readRows( @@ -223,17 +222,18 @@ private int readRows( RowReader rowReader, boolean isEntityResultType, Results results) { - if ( uniqueSemantic == UniqueSemantic.FILTER - || uniqueSemantic == UniqueSemantic.ASSERT && rowReader.hasCollectionInitializers() - || uniqueSemantic == UniqueSemantic.ALLOW && isEntityResultType ) { - return readUnique( rowProcessingState, rowReader, results ); - } - else if ( uniqueSemantic == UniqueSemantic.ASSERT ) { - return readUniqueAssert( rowProcessingState, rowReader, results ); - } - else { - return read( rowProcessingState, rowReader, results ); - } + return switch ( uniqueSemantic ) { + case FILTER -> + readUnique( rowProcessingState, rowReader, results ); + case ASSERT -> rowReader.hasCollectionInitializers() + ? readUnique( rowProcessingState, rowReader, results ) + : readUniqueAssert( rowProcessingState, rowReader, results ); + case ALLOW -> isEntityResultType + ? readUnique( rowProcessingState, rowReader, results ) + : read( rowProcessingState, rowReader, results ); + case NONE, NEVER -> + read( rowProcessingState, rowReader, results ); + }; } private static int read( @@ -283,35 +283,32 @@ private static int readUnique( return readRows; } + @SuppressWarnings("unchecked") //TODO: fix the unchecked casts private JavaType resolveDomainResultJavaType( Class domainResultResultJavaType, List<@Nullable JavaType> resultJavaTypes, TypeConfiguration typeConfiguration) { - final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry(); + final var javaTypeRegistry = typeConfiguration.getJavaTypeRegistry(); if ( domainResultResultJavaType != null ) { - final JavaType resultJavaType = javaTypeRegistry.resolveDescriptor( domainResultResultJavaType ); + final var resultJavaType = javaTypeRegistry.resolveDescriptor( domainResultResultJavaType ); // Could be that the user requested a more general type than the actual type, // so resolve the most concrete type since this type is used to determine equality of objects - if ( resultJavaTypes.size() == 1 && isMoreConcrete( resultJavaType, resultJavaTypes.get( 0 ) ) ) { - //noinspection unchecked + if ( resultJavaTypes.size() == 1 + && isMoreConcrete( resultJavaType, resultJavaTypes.get( 0 ) ) ) { return (JavaType) resultJavaTypes.get( 0 ); } return resultJavaType; } if ( resultJavaTypes.size() == 1 ) { - final JavaType firstJavaType = resultJavaTypes.get( 0 ); - if ( firstJavaType == null ) { - return javaTypeRegistry.getDescriptor( Object.class ); - } - else { - //noinspection unchecked - return (JavaType) firstJavaType; - } + final var firstJavaType = resultJavaTypes.get( 0 ); + return firstJavaType == null + ? (JavaType) javaTypeRegistry.resolveDescriptor( Object.class ) + : (JavaType) firstJavaType; } else { - return javaTypeRegistry.getDescriptor( Object[].class ); + return (JavaType) javaTypeRegistry.resolveDescriptor( Object[].class ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/stat/NaturalIdStatistics.java b/hibernate-core/src/main/java/org/hibernate/stat/NaturalIdStatistics.java index 3604c5d4e1e6..655f8d5fac71 100644 --- a/hibernate-core/src/main/java/org/hibernate/stat/NaturalIdStatistics.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/NaturalIdStatistics.java @@ -48,4 +48,11 @@ public interface NaturalIdStatistics extends CacheableDataStatistics, Serializab * the execution of this "natural id resolution" query */ long getExecutionMinTime(); + + /** + * The number of times (since last Statistics clearing) that natural-id value + * {@linkplain org.hibernate.metamodel.mapping.NaturalIdMapping#normalizeInput normalization} + * has been performed. + */ + long getNormalizationCount(); } diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/NaturalIdStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/NaturalIdStatisticsImpl.java index 843c2013f995..284efeb6cec9 100644 --- a/hibernate-core/src/main/java/org/hibernate/stat/internal/NaturalIdStatisticsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/NaturalIdStatisticsImpl.java @@ -24,6 +24,7 @@ public class NaturalIdStatisticsImpl extends AbstractCacheableDataStatistics imp private final AtomicLong executionMaxTime = new AtomicLong(); private final AtomicLong executionMinTime = new AtomicLong( Long.MAX_VALUE ); private final AtomicLong totalExecutionTime = new AtomicLong(); + private final AtomicLong normalizationCount = new AtomicLong(); private final Lock readLock; private final Lock writeLock; @@ -81,6 +82,11 @@ public long getExecutionMinTime() { return executionMinTime.get(); } + @Override + public long getNormalizationCount() { + return normalizationCount.get(); + } + void queryExecuted(long time) { // read lock is enough, concurrent updates are supported by the underlying type AtomicLong // this only guards executed(long, long) to be called, when another thread is executing getExecutionAvgTime() @@ -105,6 +111,10 @@ void queryExecuted(long time) { } } + void valueNormalized() { + normalizationCount.getAndIncrement(); + } + @Override public String toString() { final var text = new StringBuilder() diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java index ac256ff03224..249ee879e51a 100644 --- a/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java @@ -1044,4 +1044,9 @@ public Map getSlowQueries() { public void slowQuery(String sql, long executionTime) { slowQueries.merge( sql, executionTime, Math::max ); } + + @Override + public void normalizeNaturalId(String entityName) { + getNaturalIdStatistics( entityName ).valueNormalized(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/stat/spi/StatisticsImplementor.java b/hibernate-core/src/main/java/org/hibernate/stat/spi/StatisticsImplementor.java index 28ec8450b033..6dac7eb0a69f 100644 --- a/hibernate-core/src/main/java/org/hibernate/stat/spi/StatisticsImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/spi/StatisticsImplementor.java @@ -305,4 +305,8 @@ default Map getSlowQueries() { //For backward compatibility return emptyMap(); } + + default void normalizeNaturalId(String entityName) { + //For backward compatibility + } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java index 2df34e1b52f9..25a404c62ba1 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java @@ -139,6 +139,7 @@ protected void validateTable( ); } validateColumnType( table, column, existingColumn, metadata, dialect ); + validateColumnNullability( table, column, existingColumn ); } } @@ -164,6 +165,22 @@ protected void validateColumnType( } } + private void validateColumnNullability(Table table, Column column, ColumnInformation existingColumn) { + if ( existingColumn.getNullable() == Boolean.FALSE ) { + // the existing schema column is defined as not-nullable + if ( column.isNullable() ) { + // but it is mapped in the model as nullable + throw new SchemaManagementException( + String.format( + "Schema validation: column defined as not-null in the database, but nullable in model - [%s] in table [%s]", + column.getName(), + table.getQualifiedTableName() + ) + ); + } + } + } + protected void validateSequence(Sequence sequence, SequenceInformation sequenceInformation) { if ( sequenceInformation == null ) { throw new SchemaManagementException( diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java b/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java index ddaa61cdb263..79fe41043f8c 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java @@ -4,10 +4,6 @@ */ package org.hibernate.tuple; -import org.hibernate.HibernateException; -import org.hibernate.boot.spi.MetadataImplementor; -import org.hibernate.boot.spi.SessionFactoryOptions; -import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.generator.Generator; import org.hibernate.mapping.PersistentClass; @@ -26,6 +22,8 @@ import org.hibernate.type.EntityType; import org.hibernate.type.Type; +import static org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper.includeInBaseFetchGroup; + /** * @deprecated No direct replacement */ @@ -45,9 +43,8 @@ private PropertyFactory() { public static IdentifierProperty buildIdentifierAttribute( PersistentClass mappedEntity, Generator generator) { - Type type = mappedEntity.getIdentifier().getType(); - Property property = mappedEntity.getIdentifierProperty(); - + final var type = mappedEntity.getIdentifier().getType(); + final var property = mappedEntity.getIdentifierProperty(); if ( property == null ) { // this is a virtual id property... return new IdentifierProperty( @@ -82,9 +79,7 @@ public static VersionProperty buildVersionProperty( int attributeNumber, Property property, boolean lazyAvailable) { - - boolean lazy = lazyAvailable && property.isLazy(); - + final boolean lazy = lazyAvailable && property.isLazy(); return new VersionProperty( persister, sessionFactory, @@ -127,9 +122,7 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( Property property, boolean lazyAvailable, RuntimeModelCreationContext creationContext) { - final Type type = property.getValue().getType(); - - final NonIdentifierAttributeNature nature = decode( type ); + final var type = property.getValue().getType(); // we need to dirty check collections, since they can cause an owner // version number increment @@ -138,89 +131,82 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( // to update the cache (not the database), since in this case a null // entity reference can lose information - boolean alwaysDirtyCheck = type.isAssociationType() + final boolean alwaysDirtyCheck = type.isAssociationType() && ( (AssociationType) type ).isAlwaysDirtyChecked(); - SessionFactoryOptions sessionFactoryOptions = sessionFactory.getSessionFactoryOptions(); - final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + final boolean lazy = !includeInBaseFetchGroup( property, lazyAvailable, entityName -> { - final MetadataImplementor metadata = creationContext.getMetadata(); - final PersistentClass entityBinding = metadata.getEntityBinding( entityName ); + final var entityBinding = + creationContext.getMetadata() + .getEntityBinding( entityName ); assert entityBinding != null; return entityBinding.hasSubclasses(); }, - sessionFactoryOptions.isCollectionsInDefaultFetchGroupEnabled() + sessionFactory.getSessionFactoryOptions() + .isCollectionsInDefaultFetchGroupEnabled() ); - switch ( nature ) { - case BASIC: { - return new EntityBasedBasicAttribute( - persister, - sessionFactory, - attributeNumber, - property.getName(), - type, - new BaselineAttributeInformation.Builder() - .setLazy( lazy ) - .setInsertable( property.isInsertable() ) - .setUpdateable( property.isUpdatable() ) - .setNullable( property.isOptional() ) - .setDirtyCheckable( alwaysDirtyCheck || property.isUpdatable() ) - .setVersionable( property.isOptimisticLocked() ) - .setCascadeStyle( property.getCascadeStyle() ) - .setOnDeleteAction( property.getOnDeleteAction() ) - .setFetchMode( property.getValue().getFetchMode() ) - .createInformation() - ); - } - case COMPOSITE: { - return new EntityBasedCompositionAttribute( - persister, - sessionFactory, - attributeNumber, - property.getName(), - (CompositeType) type, - new BaselineAttributeInformation.Builder() - .setLazy( lazy ) - .setInsertable( property.isInsertable() ) - .setUpdateable( property.isUpdatable() ) - .setNullable( property.isOptional() ) - .setDirtyCheckable( alwaysDirtyCheck || property.isUpdatable() ) - .setVersionable( property.isOptimisticLocked() ) - .setCascadeStyle( property.getCascadeStyle() ) - .setOnDeleteAction( property.getOnDeleteAction() ) - .setFetchMode( property.getValue().getFetchMode() ) - .createInformation() - ); - } - case ENTITY: - case ANY: - case COLLECTION: { - return new EntityBasedAssociationAttribute( - persister, - sessionFactory, - attributeNumber, - property.getName(), - (AssociationType) type, - new BaselineAttributeInformation.Builder() - .setLazy( lazy ) - .setInsertable( property.isInsertable() ) - .setUpdateable( property.isUpdatable() ) - .setNullable( property.isOptional() ) - .setDirtyCheckable( alwaysDirtyCheck || property.isUpdatable() ) - .setVersionable( property.isOptimisticLocked() ) - .setCascadeStyle( property.getCascadeStyle() ) - .setOnDeleteAction( property.getOnDeleteAction() ) - .setFetchMode( property.getValue().getFetchMode() ) - .createInformation() - ); - } - default: { - throw new HibernateException( "Internal error" ); - } - } + return switch ( decode( type ) ) { + case BASIC -> + new EntityBasedBasicAttribute( + persister, + sessionFactory, + attributeNumber, + property.getName(), + type, + new BaselineAttributeInformation.Builder() + .setLazy( lazy ) + .setInsertable( property.isInsertable() ) + .setUpdateable( property.isUpdatable() ) + .setNullable( property.isOptional() ) + .setDirtyCheckable( alwaysDirtyCheck || property.isUpdatable() ) + .setVersionable( property.isOptimisticLocked() ) + .setCascadeStyle( property.getCascadeStyle() ) + .setOnDeleteAction( property.getOnDeleteAction() ) + .setFetchMode( property.getValue().getFetchMode() ) + .createInformation() + ); + case COMPOSITE -> + new EntityBasedCompositionAttribute( + persister, + sessionFactory, + attributeNumber, + property.getName(), + (CompositeType) type, + new BaselineAttributeInformation.Builder() + .setLazy( lazy ) + .setInsertable( property.isInsertable() ) + .setUpdateable( property.isUpdatable() ) + .setNullable( property.isOptional() ) + .setDirtyCheckable( alwaysDirtyCheck || property.isUpdatable() ) + .setVersionable( property.isOptimisticLocked() ) + .setCascadeStyle( property.getCascadeStyle() ) + .setOnDeleteAction( property.getOnDeleteAction() ) + .setFetchMode( property.getValue().getFetchMode() ) + .createInformation() + ); + case ENTITY, ANY, COLLECTION -> + new EntityBasedAssociationAttribute( + persister, + sessionFactory, + attributeNumber, + property.getName(), + (AssociationType) type, + new BaselineAttributeInformation.Builder() + .setLazy( lazy ) + .setInsertable( property.isInsertable() ) + .setUpdateable( property.isUpdatable() ) + .setNullable( property.isOptional() ) + .setDirtyCheckable( alwaysDirtyCheck || property.isUpdatable() ) + .setVersionable( property.isOptimisticLocked() ) + .setCascadeStyle( property.getCascadeStyle() ) + .setOnDeleteAction( property.getOnDeleteAction() ) + .setFetchMode( property.getValue().getFetchMode() ) + .createInformation() + ); + }; } private static NonIdentifierAttributeNature decode(Type type) { @@ -240,5 +226,4 @@ else if ( type instanceof ComponentType ) { return NonIdentifierAttributeNature.BASIC; } } - } diff --git a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java index d41f3de19ffc..5da4df670708 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java @@ -14,7 +14,6 @@ import java.util.Map; import org.hibernate.Hibernate; -import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -55,18 +54,18 @@ public abstract class AbstractStandardBasicType public AbstractStandardBasicType(JdbcType jdbcType, JavaType javaType) { this.jdbcType = jdbcType; - this.sqlTypes = new int[] { jdbcType.getDdlTypeCode() }; this.javaType = javaType; + sqlTypes = new int[] { jdbcType.getDdlTypeCode() }; - this.jdbcValueBinder = jdbcType.getBinder( javaType ); - this.jdbcValueExtractor = jdbcType.getExtractor( javaType ); - this.jdbcLiteralFormatter = jdbcType.getJdbcLiteralFormatter( javaType ); + jdbcValueBinder = jdbcType.getBinder( javaType ); + jdbcValueExtractor = jdbcType.getExtractor( javaType ); + jdbcLiteralFormatter = jdbcType.getJdbcLiteralFormatter( javaType ); //A very simple dispatch optimisation, make these a constant: - this.javaTypeClass = javaType.getJavaTypeClass(); - this.mutabilityPlan = javaType.getMutabilityPlan(); - this.javatypeComparator = javaType.getComparator(); - this.typeForEqualsHashCode = javaType.useObjectEqualsHashCode() ? null : this; + javaTypeClass = javaType.getJavaTypeClass(); + mutabilityPlan = javaType.getMutabilityPlan(); + javatypeComparator = javaType.getComparator(); + typeForEqualsHashCode = javaType.useObjectEqualsHashCode() ? null : this; } @Override @@ -94,7 +93,7 @@ public T fromString(CharSequence string) { } protected MutabilityPlan getMutabilityPlan() { - return this.mutabilityPlan; + return mutabilityPlan; } @Override @@ -174,7 +173,6 @@ public final boolean isEqual(Object x, Object y, SessionFactoryImplementor facto } @Override - @SuppressWarnings("unchecked") public boolean isEqual(Object one, Object another) { if ( one == another ) { return true; @@ -186,19 +184,15 @@ else if ( typeForEqualsHashCode == null ) { return one.equals( another ); } else { - return javaType.areEqual( (T) one, (T) another ); + return javaType.areEqual( javaType.cast( one ), javaType.cast( another ) ); } } @Override - @SuppressWarnings("unchecked") - public int getHashCode(Object x) { - if ( typeForEqualsHashCode == null ) { - return x.hashCode(); - } - else { - return javaType.extractHashCode( (T) x ); - } + public int getHashCode(Object object) { + return typeForEqualsHashCode == null + ? object.hashCode() + : javaType.extractHashCode( javaType.cast( object ) ); } @Override @@ -212,9 +206,8 @@ public final int getHashCode(Object x, SessionFactoryImplementor factory) { } @Override - @SuppressWarnings("unchecked") public final int compare(Object x, Object y) { - return this.javatypeComparator.compare( (T) x, (T) y ); + return this.javatypeComparator.compare( javaType.cast( x ) , javaType.cast( y ) ); } @Override @@ -228,9 +221,11 @@ public final boolean isDirty(Object old, Object current, boolean[] checkable, Sh } protected final boolean isDirty(Object old, Object current) { - // MutableMutabilityPlan.INSTANCE is a special plan for which we always have to assume the value is dirty, - // because we can't actually copy a value, but have no knowledge about the mutability of the java type - return getMutabilityPlan() == MutableMutabilityPlan.INSTANCE || !isSame( old, current ); + // MutableMutabilityPlan.INSTANCE is a special plan for which we always + // have to assume the value is dirty, because we can't actually copy a + // value, but have no knowledge about the mutability of the java type + return getMutabilityPlan() == MutableMutabilityPlan.INSTANCE + || !isSame( old, current ); } @Override @@ -247,22 +242,23 @@ public final void nullSafeSet( PreparedStatement st, Object value, int index, - final SharedSessionContractImplementor session) throws SQLException { - //noinspection unchecked - nullSafeSet( st, (T) value, index, (WrapperOptions) session ); + final SharedSessionContractImplementor session) + throws SQLException { + nullSafeSet( st, javaType.cast( value ) , index, (WrapperOptions) session ); } - protected void nullSafeSet(PreparedStatement st, T value, int index, WrapperOptions options) throws SQLException { + protected void nullSafeSet(PreparedStatement st, T value, int index, WrapperOptions options) + throws SQLException { getJdbcValueBinder().bind( st, value, index, options ); } @Override - @SuppressWarnings("unchecked") public final String toLoggableString(Object value, SessionFactoryImplementor factory) { - if ( value == LazyPropertyInitializer.UNFETCHED_PROPERTY || !Hibernate.isInitialized( value ) ) { - return ""; - } - return javaType.extractLoggableRepresentation( (T) value ); + return value == LazyPropertyInitializer.UNFETCHED_PROPERTY + || !Hibernate.isInitialized( value ) + ? "" + : javaType.extractLoggableRepresentation( + javaType.cast( javaType.coerce( value ) ) ); } @Override @@ -271,23 +267,17 @@ public final boolean isMutable() { } @Override - @SuppressWarnings("unchecked") - public final Object deepCopy(Object value, SessionFactoryImplementor factory) { - return deepCopy( (T) value ); - } - - protected final T deepCopy(T value) { - return getMutabilityPlan().deepCopy( value ); + public Object deepCopy(Object value, SessionFactoryImplementor factory) { + return getMutabilityPlan().deepCopy( javaType.cast( value ) ); } @Override - @SuppressWarnings("unchecked") - public final Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { - return getMutabilityPlan().disassemble( (T) value, session ); + public final Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) { + return getMutabilityPlan().disassemble( javaType.cast( value ), session ); } @Override - public final Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException { + public final Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) { return getMutabilityPlan().assemble( cached, session ); } @@ -296,16 +286,14 @@ public final void beforeAssemble(Serializable cached, SharedSessionContractImple } @Override - @SuppressWarnings("unchecked") public final Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache) { return original == null && target == null ? null - : javaType.getReplacement( (T) original, (T) target, session ); + : javaType.getReplacement( javaType.cast( original ), javaType.cast( target ), session ); } @Override - @SuppressWarnings("unchecked") public Object replace( Object original, Object target, @@ -314,6 +302,9 @@ public Object replace( Map copyCache, ForeignKeyDirection foreignKeyDirection) { return ForeignKeyDirection.FROM_PARENT == foreignKeyDirection + // TODO: use cast() .. currently failing on embeddable discriminators where + // the concrete class is passed in instead of its disciminator value +// ? javaType.getReplacement( javaType.cast( original ) , javaType.cast( target ) , session ) ? javaType.getReplacement( (T) original, (T) target, session ) : target; } @@ -325,20 +316,12 @@ public boolean canDoExtraction() { @Override public T extract(CallableStatement statement, int startIndex, final SharedSessionContractImplementor session) throws SQLException { - return getJdbcValueExtractor().extract( - statement, - startIndex, - session - ); + return getJdbcValueExtractor().extract( statement, startIndex, session ); } @Override public T extract(CallableStatement statement, String paramName, final SharedSessionContractImplementor session) throws SQLException { - return getJdbcValueExtractor().extract( - statement, - paramName, - session - ); + return getJdbcValueExtractor().extract( statement, paramName, session ); } @Override @@ -347,7 +330,8 @@ public void nullSafeSet( Object value, int index, boolean[] settable, - SharedSessionContractImplementor session) throws SQLException { + SharedSessionContractImplementor session) + throws SQLException { } @@ -356,9 +340,8 @@ public void nullSafeSet(CallableStatement st, T value, String name, SharedSessio nullSafeSet( st, value, name, (WrapperOptions) session ); } - @SuppressWarnings("unchecked") protected final void nullSafeSet(CallableStatement st, Object value, String name, WrapperOptions options) throws SQLException { - getJdbcValueBinder().bind( st, (T) value, name, options ); + getJdbcValueBinder().bind( st, javaType.cast( value ), name, options ); } @Override @@ -379,9 +362,8 @@ public CastType getCastType() { // Due to that, we have to handle some conversions in wrap/unwrap of BooleanJavaType // and the cast type determination here. Note that we interpret the converter in ConvertedBasicTypeImpl // to properly determine the correct cast type - final JdbcType jdbcType = getJdbcType(); - final int jdbcTypeCode = jdbcType.getDdlTypeCode(); - switch ( jdbcTypeCode ) { + final var jdbcType = getJdbcType(); + switch ( jdbcType.getDdlTypeCode() ) { case Types.BIT: case Types.SMALLINT: case Types.TINYINT: diff --git a/hibernate-core/src/main/java/org/hibernate/type/AdjustableBasicType.java b/hibernate-core/src/main/java/org/hibernate/type/AdjustableBasicType.java index c456e995651f..da9f9c0a38e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AdjustableBasicType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AdjustableBasicType.java @@ -25,16 +25,16 @@ default BasicType resolveIndicatedType(JdbcTypeIndicators indicators, Jav indicators, domainJtd ); - if ( resolvedJdbcType != jdbcType ) { + if ( getJavaTypeDescriptor() != domainJtd || resolvedJdbcType != jdbcType ) { return indicators.getTypeConfiguration().getBasicTypeRegistry() - .resolve( domainJtd, resolvedJdbcType, getName() ); + .resolve( domainJtd, resolvedJdbcType ); } } else { final int resolvedJdbcTypeCode = indicators.resolveJdbcTypeCode( jdbcType.getDefaultSqlTypeCode() ); - if ( resolvedJdbcTypeCode != jdbcType.getDefaultSqlTypeCode() ) { + if ( getJavaTypeDescriptor() != domainJtd || resolvedJdbcTypeCode != jdbcType.getDefaultSqlTypeCode() ) { return indicators.getTypeConfiguration().getBasicTypeRegistry() - .resolve( domainJtd, indicators.getJdbcType( resolvedJdbcTypeCode ), getName() ); + .resolve( domainJtd, indicators.getJdbcType( resolvedJdbcTypeCode ) ); } } return (BasicType) this; diff --git a/hibernate-core/src/main/java/org/hibernate/type/ArrayType.java b/hibernate-core/src/main/java/org/hibernate/type/ArrayType.java index 695f8ad614c5..d4e8d0711835 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ArrayType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ArrayType.java @@ -36,7 +36,7 @@ public class ArrayType extends CollectionType { public ArrayType(String role, String propertyRef, Class elementClass) { super(role, propertyRef ); this.elementClass = elementClass; - arrayClass = Array.newInstance(elementClass, 0).getClass(); + arrayClass = elementClass.arrayType(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java b/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java index 963bb7b436c2..13502e29806b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java @@ -6,34 +6,47 @@ import java.util.Objects; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.type.descriptor.java.AbstractArrayJavaType; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; +import static java.lang.Character.toUpperCase; + /** * A type that maps between {@link java.sql.Types#ARRAY ARRAY} and {@code T[]} * * @author Jordan Gigov * @author Christian Beikov */ -public class BasicArrayType +public final class BasicArrayType extends AbstractSingleColumnStandardBasicType implements AdjustableBasicType, BasicPluralType { private final BasicType baseDescriptor; private final String name; + private final AbstractArrayJavaType arrayTypeDescriptor; public BasicArrayType(BasicType baseDescriptor, JdbcType arrayJdbcType, JavaType arrayTypeDescriptor) { super( arrayJdbcType, arrayTypeDescriptor ); this.baseDescriptor = baseDescriptor; this.name = determineArrayTypeName( baseDescriptor ); + this.arrayTypeDescriptor = + arrayTypeDescriptor instanceof AbstractArrayJavaType arrayJavaType + ? arrayJavaType + // this only happens with contributions from hibernate-vector + // because it passes in a PrimitiveByteArrayJavaType which is + // not an AbstractArrayJavaType (this might be a bug) + : null; } static String determineElementTypeName(BasicType baseDescriptor) { final String elementName = baseDescriptor.getName(); return switch ( elementName ) { case "boolean", "byte", "char", "short", "int", "long", "float", "double" -> - Character.toUpperCase( elementName.charAt( 0 ) ) + elementName.substring( 1 ); + toUpperCase( elementName.charAt( 0 ) ) + + elementName.substring( 1 ); default -> elementName; }; } @@ -59,20 +72,50 @@ protected boolean registerUnderJavaType() { @Override public BasicType resolveIndicatedType(JdbcTypeIndicators indicators, JavaType domainJtd) { - // TODO: maybe fallback to some encoding by default if the DB doesn't support arrays natively? - // also, maybe move that logic into the ArrayJdbcType + // TODO: maybe fall back to some encoding by default if + // the database doesn't support arrays natively? + // also, maybe move that logic into the ArrayJdbcType //noinspection unchecked return (BasicType) this; } @Override public boolean equals(Object object) { - return object == this || object.getClass() == BasicArrayType.class - && Objects.equals( baseDescriptor, ( (BasicArrayType) object ).baseDescriptor ); + return object == this + || object instanceof BasicArrayType arrayType // no subtypes + && Objects.equals( baseDescriptor, arrayType.baseDescriptor ); } @Override public int hashCode() { return baseDescriptor.hashCode(); } + + // Methods required to support Horrible hack around the fact + // that java.sql.Timestamps in an array can be represented as + // instances of java.util.Date (Why do we even allow this?) + + @Override + public boolean isEqual(Object one, Object another) { + if ( arrayTypeDescriptor == null ) { + // for hibernate-vector + return super.isEqual( one, another ); + } + else if ( one == another ) { + return true; + } + else if ( one == null || another == null ) { + return false; + } + else { + return arrayTypeDescriptor.isEqual( one, another ); + } + } + + @Override + public Object deepCopy(Object value, SessionFactoryImplementor factory) { + return arrayTypeDescriptor == null + ? super.deepCopy( value, factory ) // for hibernate-vector + : arrayTypeDescriptor.deepCopy( value ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/BasicTypeReference.java b/hibernate-core/src/main/java/org/hibernate/type/BasicTypeReference.java index 3e202366a9ee..9b6d116b67ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/BasicTypeReference.java +++ b/hibernate-core/src/main/java/org/hibernate/type/BasicTypeReference.java @@ -6,6 +6,7 @@ import java.io.Serializable; +import jakarta.persistence.TemporalType; import org.hibernate.query.sqm.SqmBindableType; import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; @@ -24,9 +25,10 @@ public final class BasicTypeReference implements BindableType, Serializabl private final int sqlTypeCode; private final BasicValueConverter converter; private final boolean forceImmutable; + private final TemporalType precision; public BasicTypeReference(String name, Class javaType, int sqlTypeCode) { - this(name, javaType, sqlTypeCode, null); + this( name, javaType, sqlTypeCode, null, null, false ); } public BasicTypeReference( @@ -34,13 +36,22 @@ public BasicTypeReference( Class javaType, int sqlTypeCode, BasicValueConverter converter) { - this( name, javaType, sqlTypeCode, converter, false ); + this( name, javaType, sqlTypeCode, null, converter, false ); + } + + public BasicTypeReference( + String name, + Class javaType, + int sqlTypeCode, + TemporalType precision) { + this( name, javaType, sqlTypeCode, precision, null, false ); } private BasicTypeReference( String name, Class javaType, int sqlTypeCode, + TemporalType precision, BasicValueConverter converter, boolean forceImmutable) { this.name = name; @@ -48,6 +59,7 @@ private BasicTypeReference( this.javaType = (Class) javaType; this.sqlTypeCode = sqlTypeCode; this.converter = converter; + this.precision = precision; this.forceImmutable = forceImmutable; } @@ -73,6 +85,10 @@ public int getSqlTypeCode() { return converter; } + public TemporalType getPrecision() { + return precision; + } + public boolean isForceImmutable() { return forceImmutable; } @@ -82,6 +98,7 @@ public BasicTypeReference asImmutable() { "imm_" + name, javaType, sqlTypeCode, + precision, converter, true ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java b/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java index b4603acc2217..c1a9cf6f91d4 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/type/BasicTypeRegistry.java @@ -5,6 +5,8 @@ package org.hibernate.type; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; @@ -18,6 +20,7 @@ import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.TemporalJavaType; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.DelegatingJdbcTypeIndicators; @@ -49,6 +52,7 @@ public class BasicTypeRegistry implements Serializable { private final Map> typesByName = new ConcurrentHashMap<>(); private final Map> typeReferencesByName = new ConcurrentHashMap<>(); + private final Map>> typeReferencesByJavaTypeName = new ConcurrentHashMap<>(); public BasicTypeRegistry(TypeConfiguration typeConfiguration){ this.typeConfiguration = typeConfiguration; @@ -89,7 +93,10 @@ else if ( !name.equals( typeReference.getName() ) ) { } private BasicType createBasicType(String name, BasicTypeReference typeReference) { - final var javaType = getJavaTypeRegistry().resolveDescriptor( typeReference.getJavaType() ); + var javaType = getJavaTypeRegistry().resolveDescriptor( typeReference.getJavaType() ); + if ( javaType instanceof TemporalJavaType temporalJavaType ) { + javaType = temporalJavaType.resolveTypeForPrecision( typeReference.getPrecision(), typeConfiguration ); + } final var jdbcType = getJdbcTypeRegistry().getDescriptor( typeReference.getSqlTypeCode() ); final var createdType = createBasicType( typeReference, javaType, jdbcType ); typesByName.put( typeReference.getName(), createdType ); @@ -100,13 +107,13 @@ private BasicType createBasicType(String name, BasicTypeReference type private static BasicType createBasicType( BasicTypeReference typeReference, JavaType javaType, JdbcType jdbcType) { final String name = typeReference.getName(); - if ( typeReference.getConverter() == null ) { + final var converter = typeReference.getConverter(); + if ( converter == null ) { return typeReference.isForceImmutable() ? new ImmutableNamedBasicTypeImpl<>( javaType, jdbcType, name ) : new NamedBasicTypeImpl<>( javaType, jdbcType, name ); } else { - final var converter = typeReference.getConverter(); assert javaType == converter.getDomainJavaType(); return typeReference.isForceImmutable() ? new CustomMutabilityConvertedBasicTypeImpl<>( name, jdbcType, converter, @@ -137,8 +144,9 @@ public BasicType resolve(Class javaType, int sqlTypeCode) { return resolve( getJavaTypeRegistry().resolveDescriptor( javaType ), sqlTypeCode ); } + // no longer used public BasicType resolve(java.lang.reflect.Type javaType, int sqlTypeCode) { - return resolve( getJavaTypeRegistry().getDescriptor( javaType ), sqlTypeCode ); + return resolve( getJavaTypeRegistry().resolveDescriptor( javaType ), sqlTypeCode ); } public BasicType resolve(JavaType javaType, int sqlTypeCode) { @@ -255,14 +263,36 @@ private BasicType createIfUnregistered( if ( registeredTypeMatches( javaType, jdbcType, registeredType ) ) { return castNonNull( registeredType ); } - else { - final var createdType = creator.get(); - register( javaType, jdbcType, createdType ); - return createdType; + // Create an ad-hoc type since the java type doesn't come from the registry and is probably explicitly defined + else if ( typeConfiguration.getJavaTypeRegistry().resolveDescriptor( javaType.getJavaType() ) == javaType ) { + final var basicTypeReferences = typeReferencesByJavaTypeName.get( javaType.getTypeName() ); + if ( basicTypeReferences != null && !basicTypeReferences.isEmpty() ) { + final var jdbcTypeRegistry = typeConfiguration.getJdbcTypeRegistry(); + for ( var typeReference : basicTypeReferences ) { + if ( typeReference.getJavaType() == javaType.getJavaTypeClass() ) { + @SuppressWarnings("unchecked") // safe, we just checked + final var castTypeReference = (BasicTypeReference) typeReference; + if ( jdbcTypeRegistry.getDescriptor( typeReference.getSqlTypeCode() ) == jdbcType ) { + final var basicType = typesByName.get( typeReference.getName() ); + if ( registeredTypeMatches( javaType, jdbcType, basicType ) ) { + @SuppressWarnings("unchecked") // safe, we checked in registeredTypeMatches() + final var castType = (BasicType) basicType; + return castType; + } + else { + return createBasicType( castTypeReference.getName(), castTypeReference ); + } + } + } + } + } } + final var createdType = creator.get(); + register( javaType, jdbcType, createdType ); + return createdType; } - private static boolean registeredTypeMatches(JavaType javaType, JdbcType jdbcType, BasicType registeredType) { + private static boolean registeredTypeMatches(JavaType javaType, JdbcType jdbcType, @Nullable BasicType registeredType) { return registeredType != null && registeredType.getJdbcType() == jdbcType && registeredType.getMappedJavaType() == javaType; @@ -333,7 +363,7 @@ public void addTypeReferenceRegistrationKey(String typeReferenceKey, String... a throw new IllegalArgumentException( "Couldn't find type reference with name: " + typeReferenceKey ); } for ( String additionalTypeReferenceKey : additionalTypeReferenceKeys ) { - typeReferencesByName.put( additionalTypeReferenceKey, basicTypeReference ); + addTypeReference( additionalTypeReferenceKey, basicTypeReference ); } } @@ -383,7 +413,7 @@ public void addPrimeEntry(BasicTypeReference type, String legacyTypeClassName // Legacy name registration if ( isNotEmpty( legacyTypeClassName ) ) { - typeReferencesByName.put( legacyTypeClassName, type ); + addTypeReference( legacyTypeClassName, type ); } // explicit registration keys @@ -406,7 +436,8 @@ private void applyRegistrationKeys(BasicType type, String[] keys) { // Incredibly verbose logging disabled // LOG.tracef( "Adding type registration %s -> %s", key, type ); - final Type old = typesByName.put( key, type ); +// final Type old = + typesByName.put( key, type ); // if ( old != null && old != type ) { // LOG.tracef( // "Type registration key [%s] overrode previous entry : `%s`", @@ -427,18 +458,31 @@ private void applyRegistrationKeys(BasicTypeReference type, String[] keys) { // Incidentally, this might also help with map lookup efficiency. key = key.intern(); - // Incredibly verbose logging disabled -// LOG.tracef( "Adding type registration %s -> %s", key, type ); - - final BasicTypeReference old = typeReferencesByName.put( key, type ); -// if ( old != null && old != type ) { -// LOG.tracef( -// "Type registration key [%s] overrode previous entry : `%s`", -// key, -// old -// ); -// } + addTypeReference( key, type ); } } } + + private void addTypeReference(String name, BasicTypeReference typeReference) { + // Incredibly verbose logging disabled +// LOG.tracef( "Adding type registration %s -> %s", key, type ); + +// final BasicTypeReference old = + typeReferencesByName.put( name, typeReference ); +// if ( old != null && old != type ) { +// LOG.tracef( +// "Type registration key [%s] overrode previous entry : `%s`", +// key, +// old +// ); +// } + + final var basicTypeReferences = typeReferencesByJavaTypeName.computeIfAbsent( + typeReference.getJavaType().getTypeName(), + s -> new ArrayList<>() + ); + if ( !basicTypeReferences.contains( typeReference ) ) { + basicTypeReferences.add( typeReference ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java b/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java index 8c2f14f57efc..4b9aed3fd203 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java @@ -22,25 +22,19 @@ import org.hibernate.engine.spi.CascadeStyles; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.mapping.Component; -import org.hibernate.mapping.Property; -import org.hibernate.mapping.Value; -import org.hibernate.metamodel.mapping.EmbeddableDiscriminatorMapping; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; -import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.metamodel.spi.EmbeddableInstantiator; -import org.hibernate.metamodel.spi.EmbeddableRepresentationStrategy; -import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.query.sqm.tree.domain.SqmEmbeddableDomainType; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.spi.CompositeTypeImplementor; import static jakarta.persistence.metamodel.Type.PersistenceType.EMBEDDABLE; +import static org.hibernate.internal.util.StringHelper.join; import static org.hibernate.internal.util.StringHelper.unqualify; import static org.hibernate.metamodel.mapping.EntityDiscriminatorMapping.DISCRIMINATOR_ROLE_NAME; @@ -84,7 +78,7 @@ public ComponentType(Component component, int[] originalPropertyOrder, boolean m this.isKey = component.isKey(); this.propertySpan = component.getPropertySpan(); this.originalPropertyOrder = originalPropertyOrder; - final Value discriminator = component.getDiscriminator(); + final var discriminator = component.getDiscriminator(); final int length = propertySpan + (component.isPolymorphic() ? 1 : 0); this.propertyNames = new String[length]; this.propertyTypes = new Type[length]; @@ -99,7 +93,7 @@ public ComponentType(Component component, int[] originalPropertyOrder, boolean m .supportsCascadeDelete(); int i = 0; - for ( Property property : component.getProperties() ) { + for ( var property : component.getProperties() ) { this.propertyNames[i] = property.getName(); this.propertyTypes[i] = property.getValue().getType(); this.propertyNullability[i] = property.isOptional(); @@ -148,7 +142,7 @@ public int getColumnSpan(MappingContext mapping) throws MappingException { @Override public int[] getSqlTypeCodes(MappingContext mappingContext) throws MappingException { //Not called at runtime so doesn't matter if it's slow :) - final int[] sqlTypes = new int[getColumnSpan( mappingContext )]; + final var sqlTypes = new int[getColumnSpan( mappingContext )]; int n = 0; for ( int i = 0; i < propertySpan; i++ ) { int[] subtypes = propertyTypes[i].getSqlTypeCodes( mappingContext ); @@ -180,8 +174,8 @@ public boolean isSame(Object x, Object y) throws HibernateException { return true; } // null value and empty component are considered equivalent - final Object[] xvalues = getPropertyValues( x ); - final Object[] yvalues = getPropertyValues( y ); + final var xvalues = getPropertyValues( x ); + final var yvalues = getPropertyValues( y ); for ( int i = 0; i < propertySpan; i++ ) { if ( !propertyTypes[i].isSame( xvalues[i], yvalues[i] ) ) { return false; @@ -225,7 +219,8 @@ public int compare(final Object x, final Object y) { return 0; } for ( int i = 0; i < propertySpan; i++ ) { - int propertyCompare = propertyTypes[i].compare( getPropertyValue( x, i ), getPropertyValue( y, i ) ); + final int propertyCompare = + propertyTypes[i].compare( getPropertyValue( x, i ), getPropertyValue( y, i ) ); if ( propertyCompare != 0 ) { return propertyCompare; } @@ -239,7 +234,8 @@ public int compare(Object x, Object y, SessionFactoryImplementor sessionFactory) return 0; } for ( int i = 0; i < propertySpan; i++ ) { - int propertyCompare = propertyTypes[i].compare( getPropertyValue( x, i ), getPropertyValue( y, i ), sessionFactory ); + final int propertyCompare = + propertyTypes[i].compare( getPropertyValue( x, i ), getPropertyValue( y, i ), sessionFactory ); if ( propertyCompare != 0 ) { return propertyCompare; } @@ -298,9 +294,10 @@ public boolean isDirty(final Object x, final Object y, final boolean[] checkable return false; } // null value and empty component are considered equivalent + final var context = session.getFactory().getRuntimeMetamodels(); int loc = 0; for ( int i = 0; i < propertySpan; i++ ) { - int len = propertyTypes[i].getColumnSpan( session.getFactory().getRuntimeMetamodels() ); + final int len = propertyTypes[i].getColumnSpan( context ); if ( len <= 1 ) { final boolean dirty = ( len == 0 || checkable[loc] ) && propertyTypes[i].isDirty( getPropertyValue( x, i ), getPropertyValue( y, i ), session ); @@ -309,7 +306,7 @@ public boolean isDirty(final Object x, final Object y, final boolean[] checkable } } else { - final boolean[] subcheckable = new boolean[len]; + final var subcheckable = new boolean[len]; System.arraycopy( checkable, loc, subcheckable, 0, len ); final boolean dirty = propertyTypes[i].isDirty( getPropertyValue( x, i ), @@ -336,10 +333,11 @@ public boolean isModified( return false; } // null value and empty components are considered equivalent + final var context = session.getFactory().getRuntimeMetamodels(); int loc = 0; for ( int i = 0; i < propertySpan; i++ ) { - final int len = propertyTypes[i].getColumnSpan( session.getFactory().getRuntimeMetamodels() ); - final boolean[] subcheckable = new boolean[len]; + final int len = propertyTypes[i].getColumnSpan( context ); + final var subcheckable = new boolean[len]; System.arraycopy( checkable, loc, subcheckable, 0, len ); if ( propertyTypes[i].isModified( getPropertyValue( old, i ), getPropertyValue( current, i ), subcheckable, session ) ) { @@ -354,10 +352,11 @@ public boolean isModified( @Override public void nullSafeSet(PreparedStatement st, Object value, int begin, SharedSessionContractImplementor session) throws HibernateException, SQLException { - final Object[] subvalues = nullSafeGetValues( value ); + final var context = session.getFactory().getRuntimeMetamodels(); + final var subvalues = nullSafeGetValues( value ); for ( int i = 0; i < propertySpan; i++ ) { propertyTypes[i].nullSafeSet( st, subvalues[i], begin, session ); - begin += propertyTypes[i].getColumnSpan( session.getFactory().getRuntimeMetamodels() ); + begin += propertyTypes[i].getColumnSpan( context ); } } @@ -369,10 +368,11 @@ public void nullSafeSet( boolean[] settable, SharedSessionContractImplementor session) throws HibernateException, SQLException { - final Object[] subvalues = nullSafeGetValues( value ); + final var context = session.getFactory().getRuntimeMetamodels(); + final var subvalues = nullSafeGetValues( value ); int loc = 0; for ( int i = 0; i < propertySpan; i++ ) { - int len = propertyTypes[i].getColumnSpan( session.getFactory().getRuntimeMetamodels() ); + int len = propertyTypes[i].getColumnSpan( context ); //noinspection StatementWithEmptyBody if ( len == 0 ) { //noop @@ -394,12 +394,9 @@ else if ( len == 1 ) { } private Object[] nullSafeGetValues(Object value) { - if ( value == null ) { - return new Object[propertySpan]; - } - else { - return getPropertyValues( value ); - } + return value == null + ? new Object[ propertySpan ] + : getPropertyValues( value ); } @Override @@ -420,9 +417,9 @@ else if ( component instanceof Object[] array ) { return array[i]; } else { - final EmbeddableMappingType embeddableMappingType = embeddableTypeDescriptor(); + final var embeddableMappingType = embeddableTypeDescriptor(); if ( embeddableMappingType.isPolymorphic() ) { - final EmbeddableMappingType.ConcreteEmbeddableType concreteEmbeddableType = + final var concreteEmbeddableType = embeddableMappingType.findSubtypeBySubclass( component.getClass().getName() ); return concreteEmbeddableType.declaresAttribute( i ) ? embeddableMappingType.getValue( component, i ) @@ -479,7 +476,7 @@ public String toLoggableString(Object value, SessionFactoryImplementor factory) } else { final Map result = new HashMap<>(); - final Object[] values = getPropertyValues( value ); + final var values = getPropertyValues( value ); for ( int i = 0; i < propertyTypes.length; i++ ) { if ( values[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { result.put( propertyNames[i], "" ); @@ -503,7 +500,7 @@ public Object deepCopy(Object component, SessionFactoryImplementor factory) { return null; } else { - final Object[] values = getPropertyValues( component ); + final var values = getPropertyValues( component ); for ( int i = 0; i < propertySpan; i++ ) { values[i] = propertyTypes[i].deepCopy( values[i], factory ); } @@ -512,7 +509,7 @@ public Object deepCopy(Object component, SessionFactoryImplementor factory) { //not absolutely necessary, but helps for some //equals()/hashCode() implementations - final PropertyAccess parentAccess = mappingModelPart().getParentInjectionAttributePropertyAccess(); + final var parentAccess = mappingModelPart().getParentInjectionAttributePropertyAccess(); if ( parentAccess != null ) { parentAccess.getSetter().set( result, parentAccess.getGetter().get( component ) ); } @@ -609,17 +606,18 @@ public Serializable disassemble(Object value, SharedSessionContractImplementor s return null; } else { - final EmbeddableMappingType mappingType = embeddableTypeDescriptor(); + final var mappingType = embeddableTypeDescriptor(); final boolean polymorphic = mappingType.isPolymorphic(); - final Object[] values = new Object[propertySpan + (polymorphic ? 1 : 0)]; - final Object[] propertyValues = getPropertyValues( value ); + final var values = new Object[propertySpan + (polymorphic ? 1 : 0)]; + final var propertyValues = getPropertyValues( value ); int i = 0; for ( ; i < propertySpan; i++ ) { values[i] = propertyTypes[i].disassemble( propertyValues[i], session, owner ); } if ( polymorphic ) { - final EmbeddableDiscriminatorMapping discriminatorMapping = mappingType.getDiscriminatorMapping(); - final Object discriminatorValue = discriminatorMapping.getDiscriminatorValue( value.getClass().getName() ); + final var discriminatorMapping = mappingType.getDiscriminatorMapping(); + final Object discriminatorValue = + discriminatorMapping.getDiscriminatorValue( value.getClass().getName() ); values[i] = discriminatorMapping.disassemble( discriminatorValue, session ); } return values; @@ -633,17 +631,18 @@ public Serializable disassemble(Object value, SessionFactoryImplementor sessionF return null; } else { - final EmbeddableMappingType mappingType = embeddableTypeDescriptor(); + final var mappingType = embeddableTypeDescriptor(); final boolean polymorphic = mappingType.isPolymorphic(); - final Object[] values = new Object[propertySpan + (polymorphic ? 1 : 0)]; - final Object[] propertyValues = getPropertyValues( value ); + final var values = new Object[propertySpan + (polymorphic ? 1 : 0)]; + final var propertyValues = getPropertyValues( value ); int i = 0; for ( ; i < propertyTypes.length; i++ ) { values[i] = propertyTypes[i].disassemble( propertyValues[i], sessionFactory ); } if ( polymorphic ) { - final EmbeddableDiscriminatorMapping discriminatorMapping = mappingType.getDiscriminatorMapping(); - final Object discriminatorValue = discriminatorMapping.getDiscriminatorValue( value.getClass().getName() ); + final var discriminatorMapping = mappingType.getDiscriminatorMapping(); + final Object discriminatorValue = + discriminatorMapping.getDiscriminatorValue( value.getClass().getName() ); values[i] = discriminatorMapping.disassemble( discriminatorValue, null ); } return values; @@ -657,22 +656,22 @@ public Object assemble(Serializable object, SharedSessionContractImplementor ses return null; } else { - final EmbeddableMappingType mappingType = embeddableTypeDescriptor(); - final Object[] values = (Object[]) object; + final var mappingType = embeddableTypeDescriptor(); + final var values = (Object[]) object; final boolean polymorphic = mappingType.isPolymorphic(); - final Object[] assembled = new Object[values.length - ( polymorphic ? 1 : 0 )]; + final var assembled = new Object[values.length - ( polymorphic ? 1 : 0 )]; int i = 0; for ( ; i < assembled.length; i++ ) { assembled[i] = propertyTypes[i].assemble( (Serializable) values[i], session, owner ); } - final EmbeddableRepresentationStrategy representation = mappingType.getRepresentationStrategy(); - final EmbeddableInstantiator instantiator = polymorphic + final var representation = mappingType.getRepresentationStrategy(); + final var instantiator = polymorphic ? representation.getInstantiatorForDiscriminator( values[i] ) : representation.getInstantiator(); final Object instance = instantiator.instantiate( () -> assembled ); - final PropertyAccess parentInjectionAccess = mappingModelPart.getParentInjectionAttributePropertyAccess(); + final var parentInjectionAccess = mappingModelPart.getParentInjectionAttributePropertyAccess(); if ( parentInjectionAccess != null ) { parentInjectionAccess.getSetter().set( instance, owner ); } @@ -693,12 +692,12 @@ public boolean[] getPropertyNullability() { @Override public boolean[] toColumnNullness(Object value, MappingContext mapping) { - final boolean[] result = new boolean[getColumnSpan( mapping )]; + final var result = new boolean[getColumnSpan( mapping )]; if ( value != null ) { - final Object[] values = getPropertyValues( value ); //TODO!!!!!!! + final var values = getPropertyValues( value ); //TODO!!!!!!! int loc = 0; for ( int i = 0; i < propertyTypes.length; i++ ) { - final boolean[] propertyNullness = propertyTypes[i].toColumnNullness( values[i], mapping ); + final var propertyNullness = propertyTypes[i].toColumnNullness( values[i], mapping ); System.arraycopy( propertyNullness, 0, result, loc, propertyNullness.length ); loc += propertyNullness.length; } @@ -713,7 +712,7 @@ public boolean isEmbedded() { @Override public int getPropertyIndex(String name) { - final String[] names = getPropertyNames(); + final var names = getPropertyNames(); for ( int i = 0, max = names.length; i < max; i++ ) { if ( names[i].equals( name ) ) { return i; @@ -721,7 +720,7 @@ public int getPropertyIndex(String name) { } throw new PropertyNotFoundException( "Could not resolve attribute '" + name + "' of '" + getReturnedClassName() + "'" - + " (must be one of '" + StringHelper.join("', '", names) + "')" + + " (must be one of '" + join("', '", names) + "')" ); } @@ -741,13 +740,13 @@ public boolean canDoExtraction() { @Override public JdbcType getJdbcType() { - final SelectableMapping aggregateMapping = embeddableTypeDescriptor().getAggregateMapping(); + final var aggregateMapping = embeddableTypeDescriptor().getAggregateMapping(); return aggregateMapping == null ? null : aggregateMapping.getJdbcMapping().getJdbcType(); } private boolean determineIfProcedureParamExtractionCanBePerformed() { - for ( Type propertyType : propertyTypes ) { - if ( !(propertyType instanceof ProcedureParameterExtractionAware parameterExtractionAware) + for ( var propertyType : propertyTypes ) { + if ( !( propertyType instanceof ProcedureParameterExtractionAware parameterExtractionAware ) || !parameterExtractionAware.canDoExtraction() ) { return false; } @@ -769,7 +768,7 @@ public Object extract(CallableStatement statement, int startIndex, SharedSession boolean notNull = false; for ( int i = 0; i < propertySpan; i++ ) { // we know this cast is safe from canDoExtraction - final Type propertyType = propertyTypes[i]; + final var propertyType = propertyTypes[i]; final var extractionAware = (ProcedureParameterExtractionAware) propertyType; final Object value = extractionAware.extract( statement, currentIndex, session ); if ( value == null ) { @@ -808,11 +807,12 @@ public Object extract(CallableStatement statement, String paramName, SharedSessi } private Object resolve(Object[] value) { - final EmbeddableMappingType mappingType = embeddableTypeDescriptor(); - final EmbeddableRepresentationStrategy representation = mappingType.getRepresentationStrategy(); - final EmbeddableInstantiator instantiator = + final var mappingType = embeddableTypeDescriptor(); + final var representation = mappingType.getRepresentationStrategy(); + final var instantiator = mappingType.isPolymorphic() - // the discriminator here is the composite class because it gets converted to the domain type when extracted + // the discriminator here is the composite class because + // it gets converted to the domain type when extracted ? representation.getInstantiatorForClass( ((Class) value[value.length - 1]).getName() ) : representation.getInstantiator(); return instantiator.instantiate( () -> value ); @@ -827,8 +827,8 @@ private ValueExtractor jdbcValueExtractor() { } protected final EmbeddableInstantiator instantiator(Object compositeInstance) { - final EmbeddableMappingType mappingType = embeddableTypeDescriptor(); - final EmbeddableRepresentationStrategy representationStrategy = mappingType.getRepresentationStrategy(); + final var mappingType = embeddableTypeDescriptor(); + final var representationStrategy = mappingType.getRepresentationStrategy(); if ( mappingType.isPolymorphic() ) { final String compositeClassName = compositeInstance != null @@ -881,9 +881,8 @@ public EmbeddableValuedModelPart mappingModelPart() { @Override public Object replacePropertyValues(Object component, Object[] values, SharedSessionContractImplementor session) throws HibernateException { - if ( !isMutable() ) { - return instantiator( component ).instantiate( () -> values ); - } - return CompositeTypeImplementor.super.replacePropertyValues( component, values, session ); + return isMutable() + ? CompositeTypeImplementor.super.replacePropertyValues( component, values, session ) + : instantiator( component ).instantiate( () -> values ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/CustomType.java b/hibernate-core/src/main/java/org/hibernate/type/CustomType.java index 446157f45f9a..c2b82fc313f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/CustomType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/CustomType.java @@ -93,7 +93,7 @@ public CustomType(UserType userType, String[] registrationKeys, TypeConfigura else { // create a JdbcType adapter that uses the UserType binder/extract handling jdbcType = new UserTypeJdbcTypeAdapter<>( userType, mappedJavaType ); - jdbcJavaType = jdbcType.getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration ); + jdbcJavaType = jdbcType.getRecommendedJavaType( null, null, typeConfiguration ); valueExtractor = jdbcType.getExtractor( mappedJavaType ); valueBinder = jdbcType.getBinder( mappedJavaType ); jdbcLiteralFormatter = diff --git a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java index f0dcaff0c920..12cc140f8bb9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java @@ -461,7 +461,7 @@ private Object getUniqueKey(Object value, SharedSessionContractImplementor sessi if ( lazyInitializer != null ) { // If the value is a Proxy and the property access is field, the value returned by // attributeMapping.getAttributeMetadata().getPropertyAccess().getGetter().get( object ) - // is always null except for the id, we need the to use the proxy implementation to + // is always null except for the id, and we need to use the proxy implementation to // extract the property value. value = lazyInitializer.getImplementation(); } @@ -530,7 +530,7 @@ private String loggableString(Object entity, EntityPersister persister) { return associatedEntityName + "#" + entity; } else { - final StringBuilder result = new StringBuilder().append( associatedEntityName ); + final var result = new StringBuilder().append( associatedEntityName ); if ( persister.hasIdentifierProperty() ) { result.append( '#' ).append( identifierString( entity, persister ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java b/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java index 8c16770b3caf..85deda4063cc 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java +++ b/hibernate-core/src/main/java/org/hibernate/type/StandardBasicTypes.java @@ -11,8 +11,6 @@ import java.sql.Blob; import java.sql.Clob; import java.sql.NClob; -import java.sql.Time; -import java.sql.Timestamp; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; @@ -30,6 +28,7 @@ import java.util.TimeZone; import java.util.UUID; +import jakarta.persistence.TemporalType; import org.hibernate.type.spi.TypeConfiguration; /** @@ -345,13 +344,12 @@ private StandardBasicTypes() { // Date / time data /** - * The standard Hibernate type for mapping {@link Duration} to JDBC {@link org.hibernate.type.SqlTypes#INTERVAL_SECOND INTERVAL_SECOND} - * or {@link org.hibernate.type.SqlTypes#NUMERIC NUMERIC} as a fallback. + * The standard Hibernate type for mapping {@link Duration} to JDBC {@link org.hibernate.type.SqlTypes#DURATION DURATION}. */ public static final BasicTypeReference DURATION = new BasicTypeReference<>( "Duration", Duration.class, - SqlTypes.INTERVAL_SECOND + SqlTypes.DURATION ); /** @@ -494,8 +492,9 @@ private StandardBasicTypes() { */ public static final BasicTypeReference TIME = new BasicTypeReference<>( "time", - Time.class, - SqlTypes.TIME + java.util.Date.class, + SqlTypes.TIME, + TemporalType.TIME ); /** @@ -504,8 +503,9 @@ private StandardBasicTypes() { */ public static final BasicTypeReference DATE = new BasicTypeReference<>( "date", - java.sql.Date.class, - SqlTypes.DATE + java.util.Date.class, + SqlTypes.DATE, + TemporalType.DATE ); /** @@ -514,8 +514,42 @@ private StandardBasicTypes() { */ public static final BasicTypeReference TIMESTAMP = new BasicTypeReference<>( "timestamp", - Timestamp.class, - SqlTypes.TIMESTAMP + java.util.Date.class, + SqlTypes.TIMESTAMP, + TemporalType.TIMESTAMP + ); + + /** + * The standard Hibernate type for mapping {@link java.sql.Time} to JDBC + * {@link org.hibernate.type.SqlTypes#TIMESTAMP TIMESTAMP}. + */ + public static final BasicTypeReference SQL_TIME = new BasicTypeReference<>( + "sql_time", + java.sql.Time.class, + SqlTypes.TIME, + TemporalType.TIME + ); + + /** + * The standard Hibernate type for mapping {@link java.sql.Date} to JDBC + * {@link org.hibernate.type.SqlTypes#DATE DATE}. + */ + public static final BasicTypeReference SQL_DATE = new BasicTypeReference<>( + "sql_date", + java.sql.Date.class, + SqlTypes.DATE, + TemporalType.DATE + ); + + /** + * The standard Hibernate type for mapping {@link java.sql.Timestamp} to JDBC + * {@link org.hibernate.type.SqlTypes#TIMESTAMP TIMESTAMP}. + */ + public static final BasicTypeReference SQL_TIMESTAMP = new BasicTypeReference<>( + "sql_timestamp", + java.sql.Timestamp.class, + SqlTypes.TIMESTAMP, + TemporalType.TIMESTAMP ); /** @@ -525,7 +559,8 @@ private StandardBasicTypes() { public static final BasicTypeReference CALENDAR = new BasicTypeReference<>( "calendar", Calendar.class, - SqlTypes.TIMESTAMP + SqlTypes.TIMESTAMP, + TemporalType.TIMESTAMP ); /** @@ -535,7 +570,8 @@ private StandardBasicTypes() { public static final BasicTypeReference CALENDAR_DATE = new BasicTypeReference<>( "calendar_date", Calendar.class, - SqlTypes.DATE + SqlTypes.DATE, + TemporalType.DATE ); /** @@ -545,7 +581,8 @@ private StandardBasicTypes() { public static final BasicTypeReference CALENDAR_TIME = new BasicTypeReference<>( "calendar_time", Calendar.class, - SqlTypes.TIME + SqlTypes.TIME, + TemporalType.TIME ); @@ -1174,21 +1211,42 @@ public static void prime(TypeConfiguration typeConfiguration) { DATE, "org.hibernate.type.DateType", basicTypeRegistry, - "date", java.sql.Date.class.getName() + "date" ); handle( TIME, "org.hibernate.type.TimeType", basicTypeRegistry, - "time", java.sql.Time.class.getName() + "time" ); handle( TIMESTAMP, "org.hibernate.type.TimestampType", basicTypeRegistry, - "timestamp", java.sql.Timestamp.class.getName(), Date.class.getName() + "timestamp", Date.class.getName() + ); + + handle( + SQL_DATE, + null, + basicTypeRegistry, + "sql_date", java.sql.Date.class.getName() + ); + + handle( + SQL_TIME, + null, + basicTypeRegistry, + "sql_time", java.sql.Time.class.getName() + ); + + handle( + SQL_TIMESTAMP, + null, + basicTypeRegistry, + "sql_timestamp", java.sql.Timestamp.class.getName() ); handle( diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/ArrayConverter.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/ArrayConverter.java index 0b7076ee3f47..715406a406df 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/ArrayConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/ArrayConverter.java @@ -4,93 +4,87 @@ */ package org.hibernate.type.descriptor.converter.internal; -import java.lang.reflect.Array; - import org.hibernate.internal.build.AllowReflection; +import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.java.JavaType; +import static java.lang.reflect.Array.newInstance; +import static org.hibernate.internal.util.ReflectHelper.arrayClass; + /** * Given a {@link BasicValueConverter} for array elements, handles conversion * to and from an array of the converted element type. * * @param the unconverted element type * @param the converted element type - * @param the unconverted array type - * @param the converted array type + * @param the unconverted array type (same as {@code E[]}) */ @AllowReflection -public class ArrayConverter implements BasicValueConverter { +public class ArrayConverter implements BasicValueConverter { private final BasicValueConverter elementConverter; private final JavaType domainJavaType; - private final JavaType relationalJavaType; + private final JavaType relationalJavaType; + private final Class arrayClass; public ArrayConverter( BasicValueConverter elementConverter, JavaType domainJavaType, - JavaType relationalJavaType) { + JavaType relationalJavaType, + BasicType elementType) { this.elementConverter = elementConverter; this.domainJavaType = domainJavaType; this.relationalJavaType = relationalJavaType; + this.arrayClass = arrayClass( elementType.getJavaType() ); } @Override - public T toDomainValue(S relationalForm) { + public T toDomainValue(F[] relationalForm) { if ( relationalForm == null ) { return null; } else { - final Class elementClass = elementConverter.getDomainJavaType().getJavaTypeClass(); - if ( relationalForm.getClass().getComponentType() == elementClass) { - //noinspection unchecked - return (T) relationalForm; - } - else { - //noinspection unchecked - return convertTo( (F[]) relationalForm, elementClass ); - } + final var elementClass = + elementConverter.getDomainJavaType().getJavaTypeClass(); + return relationalForm.getClass().getComponentType() == elementClass + ? domainJavaType.cast( relationalForm ) + : convertTo( relationalJavaType.cast( relationalForm ), elementClass ); } } private T convertTo(F[] relationalArray, Class elementClass) { //TODO: the following implementation only handles conversion between non-primitive arrays! - //noinspection unchecked - final E[] domainArray = (E[]) Array.newInstance( elementClass, relationalArray.length ); + final var domainArray = + arrayClass.cast( newInstance( elementClass, relationalArray.length ) ); for ( int i = 0; i < relationalArray.length; i++ ) { domainArray[i] = elementConverter.toDomainValue( relationalArray[i] ); } - //noinspection unchecked - return (T) domainArray; + return domainJavaType.cast( domainArray ); } @Override - public S toRelationalValue(T domainForm) { + public F[] toRelationalValue(T domainForm) { if ( domainForm == null ) { return null; } else { - final Class elementClass = elementConverter.getRelationalJavaType().getJavaTypeClass(); - if ( domainForm.getClass().getComponentType() == elementClass) { - //noinspection unchecked - return (S) domainForm; - } - else { - //noinspection unchecked - return convertFrom((E[]) domainForm, elementClass); - } + final Class elementClass = + elementConverter.getRelationalJavaType().getJavaTypeClass(); + return domainForm.getClass().getComponentType() == elementClass + ? relationalJavaType.cast( domainForm ) + : convertFrom( arrayClass.cast( domainForm ), elementClass ); } } - private S convertFrom(E[] domainArray, Class elementClass) { + private F[] convertFrom(E[] domainArray, Class elementClass) { //TODO: the following implementation only handles conversion between non-primitive arrays! - //noinspection unchecked - final F[] relationalArray = (F[]) Array.newInstance( elementClass, domainArray.length ); + final var relationalArray = + relationalJavaType.cast( newInstance( elementClass, domainArray.length ) ); for ( int i = 0; i < domainArray.length; i++ ) { relationalArray[i] = elementConverter.toRelationalValue( domainArray[i] ); } - //noinspection unchecked - return (S) relationalArray; + return relationalArray; } @Override @@ -99,7 +93,7 @@ public JavaType getDomainJavaType() { } @Override - public JavaType getRelationalJavaType() { + public JavaType getRelationalJavaType() { return relationalJavaType; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/AttributeConverterBean.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/AttributeConverterBean.java index 51b9a3fb5909..0cdc917c1cda 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/AttributeConverterBean.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/AttributeConverterBean.java @@ -74,14 +74,16 @@ private JavaType getTypeDescriptor( private MutabilityPlan getMutabilityPlan( ManagedBean> attributeConverterBean, JpaAttributeConverterCreationContext context) { - final MutabilityPlan mutabilityPlan = + final var mutabilityPlan = RegistryHelper.INSTANCE.determineMutabilityPlan( - attributeConverterBean.getBeanClass(), + (java.lang.reflect.Type) + attributeConverterBean.getBeanClass(), context.getTypeConfiguration() ); + //noinspection unchecked return mutabilityPlan == null ? new AttributeConverterMutabilityPlan<>( this, true ) - : mutabilityPlan; + : (MutabilityPlan) mutabilityPlan; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/ConverterHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/ConverterHelper.java index 5c2b3ca5d8b0..9b8622d8ba84 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/ConverterHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/ConverterHelper.java @@ -11,8 +11,8 @@ import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; -import static org.hibernate.internal.util.GenericsHelper.extractClass; -import static org.hibernate.internal.util.GenericsHelper.extractParameterizedType; +import static org.hibernate.internal.util.GenericsHelper.erasedType; +import static org.hibernate.internal.util.GenericsHelper.typeArguments; /** * @author Gavin King @@ -21,28 +21,34 @@ public class ConverterHelper { public static BasicValueConverter createValueConverter( AttributeConverter converter, JavaTypeRegistry registry) { - final var converterType = extractParameterizedType( converter.getClass(), AttributeConverter.class ); - final var typeArguments = converterType.getActualTypeArguments(); - final var domainJavaClass = extractClass( typeArguments[0] ); - final var relationalJavaClass = extractClass( typeArguments[1] ); + final var typeArguments = + typeArguments( AttributeConverter.class, + converter.getClass() ); + @SuppressWarnings("unchecked") // perfectly safe + final var domainJavaClass = (Class) erasedType( typeArguments[0] ); + @SuppressWarnings("unchecked") // perfectly safe + final var relationalJavaClass = (Class) erasedType( typeArguments[1] ); return new AttributeConverterInstance<>( converter, - registry.getDescriptor( domainJavaClass ), - registry.getDescriptor( relationalJavaClass ) + registry.resolveDescriptor( domainJavaClass ), + registry.resolveDescriptor( relationalJavaClass ) ); } public static JpaAttributeConverter createJpaAttributeConverter( ManagedBean> bean, JavaTypeRegistry registry) { - final var converterType = extractParameterizedType( bean.getBeanClass(), AttributeConverter.class ); - final var typeArguments = converterType.getActualTypeArguments(); - final var domainJavaClass = extractClass( typeArguments[0] ); - final var relationalJavaClass = extractClass( typeArguments[1] ); + final var typeArguments = + typeArguments( AttributeConverter.class, + bean.getBeanClass() ); + @SuppressWarnings("unchecked") // perfectly safe + final var domainJavaClass = (Class) erasedType( typeArguments[0] ); + @SuppressWarnings("unchecked") // perfectly safe + final var relationalJavaClass = (Class) erasedType( typeArguments[1] ); return new AttributeConverterBean<>( bean, registry.resolveDescriptor( bean.getBeanClass() ), - registry.getDescriptor( domainJavaClass ), - registry.getDescriptor( relationalJavaClass ) + registry.resolveDescriptor( domainJavaClass ), + registry.resolveDescriptor( relationalJavaClass ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractArrayJavaType.java index d7d1207db2bd..3a2fb269d8d7 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractArrayJavaType.java @@ -19,7 +19,8 @@ import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.spi.TypeConfiguration; -import static java.lang.reflect.Array.newInstance; + +import static org.hibernate.internal.util.ReflectHelper.arrayClass; @AllowReflection public abstract class AbstractArrayJavaType extends AbstractClassJavaType @@ -97,8 +98,8 @@ BasicType createTypeUsingConverter( ColumnTypeInformation columnTypeInformation, JdbcTypeIndicators indicators, BasicValueConverter valueConverter) { - final var convertedElementClass = valueConverter.getRelationalJavaType().getJavaTypeClass(); - final var convertedArrayClass = newInstance( convertedElementClass, 0 ).getClass(); + final var convertedArrayClass = + arrayClass( valueConverter.getRelationalJavaType().getJavaTypeClass() ); final var relationalJavaType = typeConfiguration.getJavaTypeRegistry() .resolveDescriptor( convertedArrayClass ); @@ -106,7 +107,7 @@ BasicType createTypeUsingConverter( elementType, arrayJdbcType( typeConfiguration, elementType, columnTypeInformation, indicators ), this, - new ArrayConverter<>( valueConverter, this, relationalJavaType ) + new ArrayConverter<>( valueConverter, this, relationalJavaType, elementType ) ); } @@ -123,4 +124,15 @@ BasicType resolveType( () -> new BasicArrayType<>( elementType, arrayJdbcType, arrayJavaType ) ); } + // Methods required to support Horrible hack around the fact + // that java.sql.Timestamps in an array can be represented as + // instances of java.util.Date (Why do we even allow this?) + + public T deepCopy(Object value) { + return getMutabilityPlan().deepCopy( cast( value ) ); + } + + public boolean isEqual(Object one, Object another) { + return areEqual( cast( one ), cast( another) ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractClassJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractClassJavaType.java index 430bb9b04f6e..e84b29138fb7 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractClassJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractClassJavaType.java @@ -81,7 +81,7 @@ public Class getJavaType() { } @Override - public Class getJavaTypeClass() { + public final Class getJavaTypeClass() { return getJavaType(); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTemporalJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTemporalJavaType.java index d1a0079326ce..ec59cf36a426 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTemporalJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTemporalJavaType.java @@ -33,7 +33,7 @@ public AbstractTemporalJavaType( } @Override - public final TemporalJavaType resolveTypeForPrecision( + public TemporalJavaType resolveTypeForPrecision( TemporalType precision, TypeConfiguration typeConfiguration) { if ( precision == null ) { @@ -48,26 +48,25 @@ public final TemporalJavaType resolveTypeForPrecision( } } - private TemporalJavaType forMissingPrecision(TypeConfiguration typeConfiguration) { - //noinspection unchecked,rawtypes - return (TemporalJavaType) this; + private TemporalJavaType forMissingPrecision(TypeConfiguration typeConfiguration) { + return this; } - protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { + protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { throw new UnsupportedOperationException( - this + " as `jakarta.persistence.TemporalType.TIMESTAMP` not supported" + getTypeName() + " as TemporalType.TIMESTAMP not supported" ); } - protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { + protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { throw new UnsupportedOperationException( - this + " as `jakarta.persistence.TemporalType.DATE` not supported" + getTypeName() + " as TemporalType.DATE not supported" ); } - protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { + protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { throw new UnsupportedOperationException( - this + " as `jakarta.persistence.TemporalType.TIME` not supported" + getTypeName() + " as TemporalType.TIME not supported" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayJavaType.java index 15fc1675d5ec..8ecada319add 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayJavaType.java @@ -111,7 +111,7 @@ public String extractLoggableRepresentation(T[] value) { } @Override - public boolean areEqual(T[] one, T[] another) { + public boolean areEqual(Object[] one, Object[] another) { if ( one == null && another == null ) { return true; } @@ -123,7 +123,13 @@ public boolean areEqual(T[] one, T[] another) { } int l = one.length; for ( int i = 0; i < l; i++ ) { - if ( !getElementJavaType().areEqual( one[i], another[i] )) { + final var elementJavaType = getElementJavaType(); + if ( !elementJavaType.areEqual( + // Horrible hack around the fact that java.sql.Timestamps + // can be represented as instances of java.util.Date + // (Why do we even allow this? We deprecated java.sql stuff!) + elementJavaType.cast( elementJavaType.coerce( one[i] ) ), + elementJavaType.cast( elementJavaType.coerce( another[i] ) ) ) ) { return false; } } @@ -256,24 +262,21 @@ public X unwrap(T[] value, Class type, WrapperOptions options) { } if ( type.isInstance( value ) ) { - //noinspection unchecked - return (X) value; + return type.cast( value ); } else if ( type == byte[].class ) { - return (X) toBytes( value ); + return type.cast( toBytes( value ) ); } else if ( type == BinaryStream.class ) { - //noinspection unchecked - return (X) new ArrayBackedBinaryStream( toBytes( value ) ); + return type.cast( new ArrayBackedBinaryStream( toBytes( value ) ) ); } else if ( type.isArray() ) { final var preferredJavaTypeClass = type.getComponentType(); - final Object[] unwrapped = (Object[]) newInstance( preferredJavaTypeClass, value.length ); + final var unwrapped = (Object[]) newInstance( preferredJavaTypeClass, value.length ); for ( int i = 0; i < value.length; i++ ) { unwrapped[i] = getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options ); } - //noinspection unchecked - return (X) unwrapped; + return type.cast( unwrapped ); } throw unknownUnwrap( type ); @@ -296,52 +299,71 @@ public T[] wrap(X value, WrapperOptions options) { } } - final var elementJavaType = getElementJavaType(); - if ( value instanceof Object[] raw ) { - final var componentClass = elementJavaType.getJavaTypeClass(); - //noinspection unchecked - final var wrapped = (T[]) newInstance( componentClass, raw.length ); - if ( componentClass.isAssignableFrom( value.getClass().getComponentType() ) ) { - for (int i = 0; i < raw.length; i++) { - //noinspection unchecked - wrapped[i] = (T) raw[i]; - } - } - else { - for ( int i = 0; i < raw.length; i++ ) { - wrapped[i] = elementJavaType.wrap( raw[i], options ); - } - } - return wrapped; + if ( value instanceof Object[] array ) { + return wrapObjectArray( value, array, options ); } else if ( value instanceof byte[] bytes ) { return fromBytes( bytes ); } else if ( value instanceof BinaryStream binaryStream ) { - // When the value is a BinaryStream, this is a deserialization request + // When the value is a BinaryStream, + // this is a deserialization request return fromBytes( binaryStream.getBytes() ); } - else if ( elementJavaType.isInstance( value ) ) { - // Support binding a single element as parameter value - //noinspection unchecked - final var wrapped = (T[]) newInstance( elementJavaType.getJavaTypeClass(), 1 ); - //noinspection unchecked - wrapped[0] = (T) value; - return wrapped; - } else if ( value instanceof Collection collection ) { - //noinspection unchecked - final var wrapped = (T[]) newInstance( elementJavaType.getJavaTypeClass(), collection.size() ); - int i = 0; - for ( Object e : collection ) { - wrapped[i++] = elementJavaType.wrap( e, options ); - } - return wrapped; + return wrapCollection( collection, options ); + } + else if ( getElementJavaType().isInstance( value ) ) { + // Support binding a single element as a parameter value + return wrapSingleElement( value, options ); } throw unknownWrap( value.getClass() ); } + private T[] wrapCollection(Collection collection, WrapperOptions options) { + final var arrayClass = getJavaTypeClass(); + final var elementJavaType = getElementJavaType(); + final var wrapped = newArray( arrayClass, elementJavaType, collection.size() ); + int i = 0; + for ( Object element : collection ) { + wrapped[i++] = elementJavaType.wrap( element, options ); + } + return wrapped; + } + + private < X> T[] wrapSingleElement(X value, WrapperOptions options) { + final var arrayClass = getJavaTypeClass(); + final var elementJavaType = getElementJavaType(); + final var wrapped = newArray( arrayClass, elementJavaType, 1 ); + wrapped[0] = elementJavaType.wrap( value, options ); + return wrapped; + } + + private T[] wrapObjectArray(X value, Object[] array, WrapperOptions options) { + final var arrayClass = getJavaTypeClass(); + final var elementJavaType = getElementJavaType(); + final var wrapped = newArray( arrayClass, elementJavaType, array.length ); + // I suppose this code was there as an optimization, + // but it doesn't really look necessary to me +// if ( elementJavaType.getJavaTypeClass() +// .isAssignableFrom( value.getClass().getComponentType() ) ) { +// for ( int i = 0; i < array.length; i++) { +// wrapped[i] = elementJavaType.cast( array[i] ); +// } +// } +// else { + for ( int i = 0; i < array.length; i++ ) { + wrapped[i] = elementJavaType.wrap( array[i], options ); + } +// } + return wrapped; + } + + private static T[] newArray(Class arrayClass, JavaType elementJavaType, int length) { + return arrayClass.cast( newInstance( elementJavaType.getJavaTypeClass(), length ) ); + } + private static byte[] toBytes(T[] array) { if ( array.getClass().getComponentType().isEnum() ) { final byte[] bytes = new byte[array.length]; @@ -361,33 +383,52 @@ private static byte[] toBytes(T[] array) { private T[] fromBytes(byte[] bytes) { final var elementClass = getElementJavaType().getJavaTypeClass(); + final var arrayClass = getJavaTypeClass(); if ( elementClass.isEnum() ) { - final T[] enumConstants = elementClass.getEnumConstants(); - final var array = (Object[]) newInstance( elementClass, bytes.length ); + final var enumConstants = elementClass.getEnumConstants(); + final var array = newArray( arrayClass, getElementJavaType(), bytes.length ); for (int i = 0; i < bytes.length; i++ ) { // null enum value was encoded as -1 array[i] = bytes[i] == -1 ? null : enumConstants[bytes[i]]; } - //noinspection unchecked - return (T[]) array; - + return array; } else { // When the value is a byte[], this is a deserialization request - //noinspection unchecked - return (T[]) SerializationHelper.deserialize(bytes); + return arrayClass.cast( SerializationHelper.deserialize( bytes ) ); } } + // Methods required to support Horrible hack around the fact + // that java.sql.Timestamps in an array can be represented as + // instances of java.util.Date (Why do we even allow this?) + + @Override + public T[] deepCopy(Object value) { + final var mutabilityPlan = + (ArrayMutabilityPlan) + super.getMutabilityPlan(); + return mutabilityPlan.deepCopy( (Object[]) value ); + } + + @Override + public boolean isEqual(Object one, Object another) { + return areEqual( (Object[]) one, (Object[]) another ); + } + @AllowReflection private static class ArrayMutabilityPlan implements MutabilityPlan { private final Class componentClass; private final MutabilityPlan componentPlan; + private final Class arrayClass; + private final JavaType baseDescriptor; public ArrayMutabilityPlan(JavaType baseDescriptor) { + this.baseDescriptor = baseDescriptor; this.componentClass = baseDescriptor.getJavaTypeClass(); this.componentPlan = baseDescriptor.getMutabilityPlan(); + this.arrayClass = arrayClass( componentClass ); } @Override @@ -396,16 +437,21 @@ public boolean isMutable() { } @Override - public T[] deepCopy(T[] value) { + public T[] deepCopy(Object[] value) { if ( value == null ) { return null; } - //noinspection unchecked - final T[] copy = (T[]) newInstance( componentClass, value.length ); - for ( int i = 0; i < value.length; i ++ ) { - copy[ i ] = componentPlan.deepCopy( value[ i ] ); + else { + final var copy = arrayClass.cast( newInstance( componentClass, value.length ) ); + for ( int i = 0; i < value.length; i++ ) { + copy[i] = componentPlan.deepCopy( + // Horrible hack around the fact that java.sql.Timestamps + // can be represented as instances of java.util.Date + // (Why do we even allow this? We deprecated java.sql stuff!) + baseDescriptor.cast( baseDescriptor.coerce( value[i] ) ) ); + } + return copy; } - return copy; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayMutabilityPlan.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayMutabilityPlan.java index 0298aff86f0d..6dddd6a5dcb0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayMutabilityPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayMutabilityPlan.java @@ -3,34 +3,37 @@ * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.descriptor.java; + import org.hibernate.internal.build.AllowReflection; -import java.lang.reflect.Array; +import static java.lang.reflect.Array.getLength; +import static java.lang.reflect.Array.newInstance; /** - * A mutability plan for arrays. Specifically arrays of immutable element type; since the elements themselves - * are immutable, a shallow copy is enough. + * A mutability plan for arrays. Specifically arrays of immutable element type; + * since the elements themselves are immutable, a shallow copy is enough. * * @author Steve Ebersole * * @deprecated Use {@link ImmutableObjectArrayMutabilityPlan#get()} for object arrays, - * or implement a dedicated mutability plan for primitive arrays - * (see for example {@link ShortPrimitiveArrayJavaType}'s mutability plan). + * or implement a dedicated mutability plan for primitive arrays + * (see for example {@link ShortPrimitiveArrayJavaType}'s mutability plan). */ -@Deprecated +@Deprecated(forRemoval = true, since = "7.0") public class ArrayMutabilityPlan extends MutableMutabilityPlan { public static final ArrayMutabilityPlan INSTANCE = new ArrayMutabilityPlan(); @SuppressWarnings({ "unchecked", "SuspiciousSystemArraycopy" }) @AllowReflection public T deepCopyNotNull(T value) { - if ( ! value.getClass().isArray() ) { + final var valueClass = value.getClass(); + if ( !valueClass.isArray() ) { // ugh! cannot find a way to properly define the type signature here - throw new IllegalArgumentException( "Value was not an array [" + value.getClass().getName() + "]" ); + throw new IllegalArgumentException( "Value was not an array [" + valueClass.getName() + "]" ); } - final int length = Array.getLength( value ); - T copy = (T) Array.newInstance( value.getClass().getComponentType(), length ); + final int length = getLength( value ); + final Object copy = newInstance( valueClass.getComponentType(), length ); System.arraycopy( value, 0, copy, 0, length ); - return copy; + return (T) copy; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BasicJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BasicJavaType.java index 77eb3f485218..58032644ecda 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BasicJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BasicJavaType.java @@ -34,6 +34,7 @@ default JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) { @Override default T fromString(CharSequence string) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException( "Type " + getTypeName() + + " does not support conversion from String"); } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigDecimalJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigDecimalJavaType.java index 8accc2bf5555..4d6c75c1e10e 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigDecimalJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigDecimalJavaType.java @@ -37,6 +37,11 @@ public boolean isInstance(Object value) { return value instanceof BigDecimal; } + @Override + public BigDecimal cast(Object value) { + return (BigDecimal) value; + } + @Override public boolean areEqual(BigDecimal one, BigDecimal another) { return one == another @@ -48,37 +53,36 @@ public int extractHashCode(BigDecimal value) { return value.intValue(); } - @SuppressWarnings("unchecked") public X unwrap(BigDecimal value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( BigDecimal.class.isAssignableFrom( type ) ) { - return (X) value; + return type.cast( value ); } if ( BigInteger.class.isAssignableFrom( type ) ) { - return (X) value.toBigIntegerExact(); + return type.cast( value.toBigIntegerExact() ); } if ( Byte.class.isAssignableFrom( type ) ) { - return (X) Byte.valueOf( value.byteValue() ); + return type.cast( value.byteValue() ); } if ( Short.class.isAssignableFrom( type ) ) { - return (X) Short.valueOf( value.shortValue() ); + return type.cast( value.shortValue() ); } if ( Integer.class.isAssignableFrom( type ) ) { - return (X) Integer.valueOf( value.intValue() ); + return type.cast( value.intValue() ); } if ( Long.class.isAssignableFrom( type ) ) { - return (X) Long.valueOf( value.longValue() ); + return type.cast( value.longValue() ); } if ( Double.class.isAssignableFrom( type ) ) { - return (X) Double.valueOf( value.doubleValue() ); + return type.cast( value.doubleValue() ); } if ( Float.class.isAssignableFrom( type ) ) { - return (X) Float.valueOf( value.floatValue() ); + return type.cast( value.floatValue() ); } if ( String.class.isAssignableFrom( type ) ) { - return (X) value.toString(); + return type.cast( value.toString() ); } throw unknownUnwrap( type ); } @@ -126,7 +130,7 @@ public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) { } @Override - public BigDecimal coerce(X value, CoercionContext coercionContext) { + public BigDecimal coerce(Object value) { if ( value == null ) { return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigIntegerJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigIntegerJavaType.java index 5f9eae0892c6..5b4a44bd0563 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigIntegerJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigIntegerJavaType.java @@ -39,43 +39,47 @@ public boolean isInstance(Object value) { return value instanceof BigInteger; } + @Override + public BigInteger cast(Object value) { + return (BigInteger) value; + } + @Override public int extractHashCode(BigInteger value) { return value.intValue(); } @Override - @SuppressWarnings("unchecked") public X unwrap(BigInteger value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( BigInteger.class.isAssignableFrom( type ) ) { - return (X) value; + return type.cast( value ); } if ( BigDecimal.class.isAssignableFrom( type ) ) { - return (X) new BigDecimal( value ); + return type.cast( new BigDecimal( value ) ); } if ( Byte.class.isAssignableFrom( type ) ) { - return (X) Byte.valueOf( value.byteValue() ); + return type.cast( Byte.valueOf( value.byteValue() ) ); } if ( Short.class.isAssignableFrom( type ) ) { - return (X) Short.valueOf( value.shortValue() ); + return type.cast( value.shortValue() ); } if ( Integer.class.isAssignableFrom( type ) ) { - return (X) Integer.valueOf( value.intValue() ); + return type.cast( value.intValue() ); } if ( Long.class.isAssignableFrom( type ) ) { - return (X) Long.valueOf( value.longValue() ); + return type.cast( value.longValue() ); } if ( Double.class.isAssignableFrom( type ) ) { - return (X) Double.valueOf( value.doubleValue() ); + return type.cast( value.doubleValue() ); } if ( Float.class.isAssignableFrom( type ) ) { - return (X) Float.valueOf( value.floatValue() ); + return type.cast( value.floatValue() ); } if ( String.class.isAssignableFrom( type ) ) { - return (X) value.toString(); + return type.cast( value.toString() ); } throw unknownUnwrap( type ); } @@ -128,7 +132,7 @@ public int getDefaultSqlScale(Dialect dialect, JdbcType jdbcType) { } @Override - public BigInteger coerce(X value, CoercionContext coercionContext) { + public BigInteger coerce(Object value) { if ( value == null ) { return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BlobJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BlobJavaType.java index 22a6c5ed834c..823b664dd31d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BlobJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BlobJavaType.java @@ -70,6 +70,11 @@ public boolean isInstance(Object value) { return value instanceof Blob; } + @Override + public Blob cast(Object value) { + return (Blob) value; + } + @Override public String extractLoggableRepresentation(Blob value) { return value == null ? "null" : "{blob}"; @@ -109,7 +114,6 @@ public Blob getReplacement(Blob original, Blob target, SharedSessionContractImpl } @Override - @SuppressWarnings("unchecked") public X unwrap(Blob value, Class type, WrapperOptions options) { if ( value == null ) { return null; @@ -117,17 +121,17 @@ public X unwrap(Blob value, Class type, WrapperOptions options) { try { if ( Blob.class.isAssignableFrom( type ) ) { - return (X) options.getLobCreator().toJdbcBlob( value ); + return type.cast( options.getLobCreator().toJdbcBlob( value ) ); } else if ( byte[].class.isAssignableFrom( type )) { if (value instanceof BlobImplementer blobImplementer) { // if the incoming Blob is a wrapper, just grab the bytes from its BinaryStream - return (X) blobImplementer.getUnderlyingStream().getBytes(); + return type.cast( blobImplementer.getUnderlyingStream().getBytes() ); } else { try { // otherwise extract the bytes from the stream manually - return (X) value.getBinaryStream().readAllBytes(); + return type.cast( value.getBinaryStream().readAllBytes() ); } catch ( IOException e ) { throw new HibernateException( "IOException occurred reading a binary value", e ); @@ -136,20 +140,20 @@ else if ( byte[].class.isAssignableFrom( type )) { } else if ( BinaryStream.class.isAssignableFrom( type ) ) { if (value instanceof BlobImplementer blobImplementer) { - return (X) blobImplementer.getUnderlyingStream(); + return type.cast( blobImplementer.getUnderlyingStream() ); } else { - return (X) new StreamBackedBinaryStream( value.getBinaryStream(), value.length() ); + return type.cast( new StreamBackedBinaryStream( value.getBinaryStream(), value.length() ) ); } } else if ( InputStream.class.isAssignableFrom( type ) ) { if (value instanceof BlobImplementer blobImplementer) { // if the incoming Blob is a wrapper, just pass along its BinaryStream - return (X) blobImplementer.getUnderlyingStream().getInputStream(); + return type.cast( blobImplementer.getUnderlyingStream().getInputStream() ); } else { // otherwise we need to build a BinaryStream... - return (X) value.getBinaryStream(); + return type.cast( value.getBinaryStream() ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanJavaType.java index 49466879b8a3..41553b340ab9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanJavaType.java @@ -10,6 +10,7 @@ import org.hibernate.type.descriptor.java.spi.PrimitiveJavaType; import org.hibernate.type.descriptor.jdbc.JdbcType; +import static java.lang.Boolean.parseBoolean; import static java.lang.Character.toUpperCase; import static org.hibernate.internal.util.CharSequenceHelper.regionMatchesIgnoreCase; @@ -28,6 +29,7 @@ public class BooleanJavaType extends AbstractClassJavaType implements private final char characterValueFalse; private final char characterValueTrueLC; + private final char characterValueFalseLC; private final String stringValueTrue; private final String stringValueFalse; @@ -42,6 +44,7 @@ public BooleanJavaType(char characterValueTrue, char characterValueFalse) { this.characterValueFalse = toUpperCase( characterValueFalse ); characterValueTrueLC = Character.toLowerCase( characterValueTrue ); + characterValueFalseLC = Character.toLowerCase( characterValueFalse ); stringValueTrue = String.valueOf( characterValueTrue ); stringValueFalse = String.valueOf( characterValueFalse ); @@ -59,7 +62,7 @@ public String toString(Boolean value) { @Override public Boolean fromString(CharSequence string) { - return Boolean.valueOf( string.toString() ); + return parseBoolean( string.toString() ); } @Override @@ -67,6 +70,11 @@ public boolean isInstance(Object value) { return value instanceof Boolean; } + @Override + public Boolean cast(Object value) { + return (Boolean) value; + } + @Override public Boolean fromEncodedString(CharSequence charSequence, int start, int end) { return switch ( charSequence.charAt( start ) ) { @@ -76,32 +84,31 @@ public Boolean fromEncodedString(CharSequence charSequence, int start, int end) }; } - @SuppressWarnings("unchecked") @Override public X unwrap(Boolean value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( Boolean.class.isAssignableFrom( type ) || type == Object.class ) { - return (X) value; + return type.cast( value ); } if ( Byte.class.isAssignableFrom( type ) ) { - return (X) toByte( value ); + return type.cast( toByte( value ) ); } if ( Short.class.isAssignableFrom( type ) ) { - return (X) toShort( value ); + return type.cast( toShort( value ) ); } if ( Integer.class.isAssignableFrom( type ) ) { - return (X) toInteger( value ); + return type.cast( toInteger( value ) ); } if ( Long.class.isAssignableFrom( type ) ) { - return (X) toLong( value ); + return type.cast( toLong( value ) ); } if ( Character.class.isAssignableFrom( type ) ) { - return (X) Character.valueOf( value ? characterValueTrue : characterValueFalse ); + return type.cast( value ? characterValueTrue : characterValueFalse ); } if ( String.class.isAssignableFrom( type ) ) { - return (X) (value ? stringValueTrue : stringValueFalse); + return type.cast( (value ? stringValueTrue : stringValueFalse) ); } throw unknownUnwrap( type ); } @@ -118,16 +125,35 @@ public Boolean wrap(X value, WrapperOptions options) { return number.intValue() != 0; } if (value instanceof Character character) { - return isTrue( character ); + if ( isTrue( character ) ) { + return true; + } + if ( isFalse( character ) ) { + return false; + } + throw new IllegalArgumentException( "Cannot convert Character value '" + character + "' to Boolean" ); } if (value instanceof String string) { - return isTrue( string ); + if ( isTrue( string ) ) { + return true; + } + if ( isFalse( string ) ) { + return false; + } + throw new IllegalArgumentException( "Cannot convert value '" + string + "' to Boolean" ); } throw unknownWrap( value.getClass() ); } private boolean isTrue(String strValue) { - return strValue != null && !strValue.isEmpty() && isTrue( strValue.charAt(0) ); + return strValue != null + && !strValue.isEmpty() + && isTrue( strValue.charAt(0) ); + } + + private boolean isFalse(String strValue) { + return strValue != null + && ( strValue.isEmpty() || isFalse( strValue.charAt(0) ) ); } private boolean isTrue(char charValue) { @@ -135,6 +161,11 @@ private boolean isTrue(char charValue) { || charValue == characterValueTrueLC; } + private boolean isFalse(char charValue) { + return charValue == characterValueFalse + || charValue == characterValueFalseLC; + } + public int toInt(Boolean value) { return value ? 1 : 0; } @@ -194,18 +225,18 @@ public int getDefaultSqlScale(Dialect dialect, JdbcType jdbcType) { public String getCheckCondition(String columnName, JdbcType jdbcType, BasicValueConverter converter, Dialect dialect) { if ( converter != null ) { if ( jdbcType.isString() ) { - final Object falseValue = converter.toRelationalValue( false ); - final Object trueValue = converter.toRelationalValue( true ); - final String[] values = getPossibleStringValues( converter, falseValue, trueValue ); - return dialect.getCheckCondition( columnName, values ); + return dialect.getCheckCondition( columnName, + getPossibleStringValues( converter, + converter.toRelationalValue( false ), + converter.toRelationalValue( true ) ) ); } else if ( jdbcType.isInteger() ) { @SuppressWarnings("unchecked") final var numericConverter = (BasicValueConverter) converter; - final Number falseValue = numericConverter.toRelationalValue( false ); - final Number trueValue = numericConverter.toRelationalValue( true ); - final Long[] values = getPossibleNumericValues( numericConverter, falseValue, trueValue ); - return dialect.getCheckCondition( columnName, values ); + return dialect.getCheckCondition( columnName, + getPossibleNumericValues( numericConverter, + numericConverter.toRelationalValue( false ), + numericConverter.toRelationalValue( true ) ) ); } } return null; @@ -221,7 +252,7 @@ private static Long[] getPossibleNumericValues( } catch ( NullPointerException ignored ) { } - final Long[] values = new Long[nullValue != null ? 3 : 2]; + final var values = new Long[nullValue != null ? 3 : 2]; values[0] = falseValue != null ? falseValue.longValue() : null; values[1] = trueValue != null ? trueValue.longValue() : null; if ( nullValue != null ) { @@ -240,7 +271,7 @@ private static String[] getPossibleStringValues( } catch ( NullPointerException ignored ) { } - final String[] values = new String[nullValue != null ? 3 : 2]; + final var values = new String[nullValue != null ? 3 : 2]; values[0] = falseValue != null ? falseValue.toString() : null; values[1] = trueValue != null ? trueValue.toString() : null; if ( nullValue != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanPrimitiveArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanPrimitiveArrayJavaType.java index 182ca0db7c6d..f8e48bdef5df 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanPrimitiveArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanPrimitiveArrayJavaType.java @@ -106,7 +106,7 @@ public X unwrap(boolean[] value, Class type, WrapperOptions options) { } if ( type.isInstance( value ) ) { - return (X) value; + return type.cast( value ); } else if ( Object[].class.isAssignableFrom( type ) ) { final Class preferredJavaTypeClass = type.getComponentType(); @@ -114,16 +114,15 @@ else if ( Object[].class.isAssignableFrom( type ) ) { for ( int i = 0; i < value.length; i++ ) { unwrapped[i] = getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options ); } - return (X) unwrapped; + return type.cast( unwrapped ); } else if ( type == byte[].class ) { // byte[] can only be requested if the value should be serialized - return (X) SerializationHelper.serialize( value ); + return type.cast( SerializationHelper.serialize( value ) ); } else if ( type == BinaryStream.class ) { // BinaryStream can only be requested if the value should be serialized - //noinspection unchecked - return (X) new ArrayBackedBinaryStream( SerializationHelper.serialize( value ) ); + return type.cast( new ArrayBackedBinaryStream( SerializationHelper.serialize( value ) ) ); } else if ( type.isArray() ) { final Class preferredJavaTypeClass = type.getComponentType(); @@ -131,7 +130,7 @@ else if ( type.isArray() ) { for ( int i = 0; i < value.length; i++ ) { Array.set( unwrapped, i, getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options ) ); } - return (X) unwrapped; + return type.cast( unwrapped ); } throw unknownUnwrap( type ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteArrayJavaType.java index ed0ca91b2e74..ac9efa800946 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteArrayJavaType.java @@ -19,6 +19,9 @@ import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; +import static java.lang.Byte.toUnsignedInt; +import static java.lang.Integer.toHexString; + /** * Descriptor for {@code Byte[]} handling, which disallows {@code null} elements. * This {@link JavaType} is useful if the domain model uses {@code Byte[]} and wants to map to {@link SqlTypes#VARBINARY}. @@ -38,6 +41,11 @@ public boolean isInstance(Object value) { return value instanceof byte[]; } + @Override + public Byte[] cast(Object value) { + return (Byte[]) value; + } + @Override public boolean areEqual(Byte[] one, Byte[] another) { return one == another @@ -57,22 +65,22 @@ public int extractHashCode(Byte[] bytes) { public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) { // match legacy behavior final var descriptor = indicators.getJdbcType( indicators.resolveJdbcTypeCode( SqlTypes.VARBINARY ) ); - return descriptor instanceof AdjustableJdbcType - ? ( (AdjustableJdbcType) descriptor ).resolveIndicatedType( indicators, this ) + return descriptor instanceof AdjustableJdbcType adjustableJdbcType + ? adjustableJdbcType.resolveIndicatedType( indicators, this ) : descriptor; } @Override public String toString(Byte[] bytes) { - final StringBuilder buf = new StringBuilder(); + final var string = new StringBuilder(); for ( Byte aByte : bytes ) { - final String hexStr = Integer.toHexString( Byte.toUnsignedInt(aByte) ); + final String hexStr = toHexString( toUnsignedInt( aByte ) ); if ( hexStr.length() == 1 ) { - buf.append( '0' ); + string.append( '0' ); } - buf.append( hexStr ); + string.append( hexStr ); } - return buf.toString(); + return string.toString(); } @Override public Byte[] fromString(CharSequence string) { @@ -90,26 +98,25 @@ public Byte[] fromString(CharSequence string) { return bytes; } - @SuppressWarnings("unchecked") @Override public X unwrap(Byte[] value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( Byte[].class.isAssignableFrom( type ) ) { - return (X) value; + return type.cast( value ); } if ( byte[].class.isAssignableFrom( type ) ) { - return (X) unwrapBytes( value ); + return type.cast( unwrapBytes( value ) ); } if ( InputStream.class.isAssignableFrom( type ) ) { - return (X) new ByteArrayInputStream( unwrapBytes( value ) ); + return type.cast( new ByteArrayInputStream( unwrapBytes( value ) ) ); } if ( BinaryStream.class.isAssignableFrom( type ) ) { - return (X) new ArrayBackedBinaryStream( unwrapBytes( value ) ); + return type.cast( new ArrayBackedBinaryStream( unwrapBytes( value ) ) ); } if ( Blob.class.isAssignableFrom( type ) ) { - return (X) options.getLobCreator().createBlob( unwrapBytes( value ) ); + return type.cast( options.getLobCreator().createBlob( unwrapBytes( value ) ) ); } throw unknownUnwrap( type ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteJavaType.java index 1052f2a58d3f..7e143c378b53 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteJavaType.java @@ -49,32 +49,36 @@ public boolean isInstance(Object value) { return value instanceof Byte; } - @SuppressWarnings("unchecked") + @Override + public Byte cast(Object value) { + return (Byte) value; + } + @Override public X unwrap(Byte value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( Byte.class.isAssignableFrom( type ) || type == Object.class ) { - return (X) value; + return type.cast( value ); } if ( Short.class.isAssignableFrom( type ) ) { - return (X) Short.valueOf( value.shortValue() ); + return type.cast( value.shortValue() ); } if ( Integer.class.isAssignableFrom( type ) ) { - return (X) Integer.valueOf( value.intValue() ); + return type.cast( value.intValue() ); } if ( Long.class.isAssignableFrom( type ) ) { - return (X) Long.valueOf( value.longValue() ); + return type.cast( value.longValue() ); } if ( Double.class.isAssignableFrom( type ) ) { - return (X) Double.valueOf( value.doubleValue() ); + return type.cast( value.doubleValue() ); } if ( Float.class.isAssignableFrom( type ) ) { - return (X) Float.valueOf( value.floatValue() ); + return type.cast( value.floatValue() ); } if ( String.class.isAssignableFrom( type ) ) { - return (X) value.toString(); + return type.cast( value.toString() ); } throw unknownUnwrap( type ); } @@ -132,7 +136,7 @@ public int getDefaultSqlScale(Dialect dialect, JdbcType jdbcType) { } @Override - public Byte coerce(X value, CoercionContext coercionContext) { + public Byte coerce(Object value) { if ( value == null ) { return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarDateJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarDateJavaType.java index db5446e701ca..757345cb9875 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarDateJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarDateJavaType.java @@ -30,7 +30,7 @@ protected CalendarDateJavaType() { super( Calendar.class, CalendarJavaType.CalendarMutabilityPlan.INSTANCE, CalendarComparator.INSTANCE ); } - @Override + @Override @SuppressWarnings("deprecation") public TemporalType getPrecision() { return TemporalType.DATE; } @@ -40,27 +40,28 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { return context.getJdbcType( Types.DATE ); } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) this; + @Override + protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { + return this; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) CalendarJavaType.INSTANCE; + @Override + protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { + return CalendarJavaType.INSTANCE; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) CalendarTimeJavaType.INSTANCE; + @Override + protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { + return CalendarTimeJavaType.INSTANCE; } public String toString(Calendar value) { - return JdbcDateJavaType.INSTANCE.toString( value.getTime() ); + return JdbcDateJavaType.INSTANCE.toString( + new java.sql.Date( value.getTime().getTime() ) ); } public Calendar fromString(CharSequence string) { - Calendar result = new GregorianCalendar(); + final var result = new GregorianCalendar(); result.setTime( JdbcDateJavaType.INSTANCE.fromString( string.toString() ) ); return result; } @@ -88,25 +89,39 @@ public int extractHashCode(Calendar value) { return hashCode; } - @SuppressWarnings("unchecked") + @Override + public boolean isInstance(Object value) { + return value instanceof Calendar; + } + + @Override + public Calendar cast(Object value) { + return (Calendar) value; + } + + @Override + public Calendar coerce(Object value) { + return wrap( value, null ); + } + public X unwrap(Calendar value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( Calendar.class.isAssignableFrom( type ) ) { - return (X) value; + return type.cast( value ); } if ( java.sql.Date.class.isAssignableFrom( type ) ) { - return (X) new java.sql.Date( value.getTimeInMillis() ); + return type.cast( new java.sql.Date( value.getTimeInMillis() ) ); } if ( java.sql.Time.class.isAssignableFrom( type ) ) { - return (X) new java.sql.Time( value.getTimeInMillis() % 86_400_000 ); + return type.cast( new java.sql.Time( value.getTimeInMillis() % 86_400_000 ) ); } if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) { - return (X) new java.sql.Timestamp( value.getTimeInMillis() ); + return type.cast( new java.sql.Timestamp( value.getTimeInMillis() ) ); } if ( Date.class.isAssignableFrom( type ) ) { - return (X) new Date( value.getTimeInMillis() ); + return type.cast( new Date( value.getTimeInMillis() ) ); } throw unknownUnwrap( type ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarJavaType.java index 3b23b21b162f..9d69570a57b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarJavaType.java @@ -39,7 +39,7 @@ protected CalendarJavaType() { super( Calendar.class, CalendarMutabilityPlan.INSTANCE, CalendarComparator.INSTANCE ); } - @Override + @Override @SuppressWarnings("deprecation") public TemporalType getPrecision() { return TemporalType.TIMESTAMP; } @@ -49,19 +49,19 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { return context.getJdbcType( Types.TIMESTAMP ); } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) this; + @Override + protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { + return this; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) CalendarDateJavaType.INSTANCE; + @Override + protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { + return CalendarDateJavaType.INSTANCE; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) CalendarTimeJavaType.INSTANCE; + @Override + protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { + return CalendarTimeJavaType.INSTANCE; } public String toString(Calendar value) { @@ -69,7 +69,7 @@ public String toString(Calendar value) { } public Calendar fromString(CharSequence string) { - Calendar result = new GregorianCalendar(); + final var result = new GregorianCalendar(); result.setTime( DateJavaType.INSTANCE.fromString( string.toString() ) ); return result; } @@ -105,25 +105,39 @@ public int extractHashCode(Calendar value) { return hashCode; } - @SuppressWarnings("unchecked") + @Override + public boolean isInstance(Object value) { + return value instanceof Calendar; + } + + @Override + public Calendar cast(Object value) { + return (Calendar) value; + } + + @Override + public Calendar coerce(Object value) { + return wrap( value, null ); + } + public X unwrap(Calendar value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( Calendar.class.isAssignableFrom( type ) ) { - return (X) value; + return type.cast( value ); } if ( java.sql.Date.class.isAssignableFrom( type ) ) { - return (X) new java.sql.Date( value.getTimeInMillis() ); + return type.cast( new java.sql.Date( value.getTimeInMillis() ) ); } if ( java.sql.Time.class.isAssignableFrom( type ) ) { - return (X) new java.sql.Time( value.getTimeInMillis() % 86_400_000 ); + return type.cast( new java.sql.Time( value.getTimeInMillis() % 86_400_000 ) ); } if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) { - return (X) new java.sql.Timestamp( value.getTimeInMillis() ); + return type.cast( new java.sql.Timestamp( value.getTimeInMillis() ) ); } if ( java.util.Date.class.isAssignableFrom( type ) ) { - return (X) new java.util.Date( value.getTimeInMillis() ); + return type.cast( new java.util.Date( value.getTimeInMillis() ) ); } throw unknownUnwrap( type ); } @@ -136,7 +150,7 @@ else if (value instanceof Calendar calendar) { return calendar; } else if ( value instanceof java.util.Date date ) { - final Calendar cal = new GregorianCalendar(); + final var cal = new GregorianCalendar(); cal.setTime( date ); return cal; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTimeJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTimeJavaType.java index 077c1fdb50c9..6fed6017fe7c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTimeJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CalendarTimeJavaType.java @@ -4,6 +4,7 @@ */ package org.hibernate.type.descriptor.java; +import java.sql.Time; import java.sql.Types; import java.util.Calendar; import java.util.Date; @@ -30,7 +31,7 @@ protected CalendarTimeJavaType() { super( Calendar.class, CalendarJavaType.CalendarMutabilityPlan.INSTANCE, CalendarComparator.INSTANCE ); } - @Override + @Override @SuppressWarnings("deprecation") public TemporalType getPrecision() { return TemporalType.TIME; } @@ -40,27 +41,28 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { return context.getJdbcType( Types.TIME ); } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) this; + @Override + protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { + return this; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) CalendarJavaType.INSTANCE; + @Override + protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { + return CalendarJavaType.INSTANCE; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) CalendarDateJavaType.INSTANCE; + @Override + protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { + return CalendarDateJavaType.INSTANCE; } public String toString(Calendar value) { - return JdbcTimeJavaType.INSTANCE.toString( value.getTime() ); + return JdbcTimeJavaType.INSTANCE.toString( + new Time( value.getTime().getTime() ) ); } public Calendar fromString(CharSequence string) { - Calendar result = new GregorianCalendar(); + final var result = new GregorianCalendar(); result.setTime( JdbcTimeJavaType.INSTANCE.fromString( string.toString() ) ); return result; } @@ -90,25 +92,24 @@ public int extractHashCode(Calendar value) { return hashCode; } - @SuppressWarnings("unchecked") public X unwrap(Calendar value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( Calendar.class.isAssignableFrom( type ) ) { - return (X) value; + return type.cast( value ); } if ( java.sql.Date.class.isAssignableFrom( type ) ) { - return (X) new java.sql.Date( value.getTimeInMillis() ); + return type.cast( new java.sql.Date( value.getTimeInMillis() ) ); } if ( java.sql.Time.class.isAssignableFrom( type ) ) { - return (X) new java.sql.Time( value.getTimeInMillis() % 86_400_000 ); + return type.cast( new java.sql.Time( value.getTimeInMillis() % 86_400_000 ) ); } if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) { - return (X) new java.sql.Timestamp( value.getTimeInMillis() ); + return type.cast( new java.sql.Timestamp( value.getTimeInMillis() ) ); } if ( Date.class.isAssignableFrom( type ) ) { - return (X) new Date( value.getTimeInMillis() ); + return type.cast( new Date( value.getTimeInMillis() ) ); } throw unknownUnwrap( type ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterArrayJavaType.java index 1f75125b1db2..551ff813ac9b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterArrayJavaType.java @@ -37,6 +37,11 @@ public boolean isInstance(Object value) { return value instanceof Character[]; } + @Override + public Character[] cast(Object value) { + return (Character[]) value; + } + @Override public String toString(Character[] value) { return new String( unwrapChars( value ) ); @@ -71,29 +76,28 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) { : descriptor; } - @SuppressWarnings("unchecked") @Override public X unwrap(Character[] value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( Character[].class.isAssignableFrom( type ) ) { - return (X) value; + return type.cast( value ); } if ( String.class.isAssignableFrom( type ) ) { - return (X) new String( unwrapChars( value ) ); + return type.cast( new String( unwrapChars( value ) ) ); } if ( NClob.class.isAssignableFrom( type ) ) { - return (X) options.getLobCreator().createNClob( new String( unwrapChars( value ) ) ); + return type.cast( options.getLobCreator().createNClob( new String( unwrapChars( value ) ) ) ); } if ( Clob.class.isAssignableFrom( type ) ) { - return (X) options.getLobCreator().createClob( new String( unwrapChars( value ) ) ); + return type.cast( options.getLobCreator().createClob( new String( unwrapChars( value ) ) ) ); } if ( Reader.class.isAssignableFrom( type ) ) { - return (X) new StringReader( new String( unwrapChars( value ) ) ); + return type.cast( new StringReader( new String( unwrapChars( value ) ) ) ); } if ( CharacterStream.class.isAssignableFrom( type ) ) { - return (X) new CharacterStreamImpl( new String( unwrapChars( value ) ) ); + return type.cast( new CharacterStreamImpl( new String( unwrapChars( value ) ) ) ); } throw unknownUnwrap( type ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterJavaType.java index fe16e90592cc..817e8fca31bd 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterJavaType.java @@ -45,20 +45,24 @@ public boolean isInstance(Object value) { return value instanceof Character; } - @SuppressWarnings("unchecked") + @Override + public Character cast(Object value) { + return (Character) value; + } + @Override public X unwrap(Character value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( Character.class.isAssignableFrom( type ) || type == Object.class ) { - return (X) value; + return type.cast( value ); } if ( String.class.isAssignableFrom( type ) ) { - return (X) value.toString(); + return type.cast( value.toString() ); } if ( Number.class.isAssignableFrom( type ) ) { - return (X) Short.valueOf( (short) value.charValue() ); + return type.cast( (short) value.charValue() ); } throw unknownUnwrap( type ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClassJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClassJavaType.java index 02c0e203681a..90754167e741 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClassJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClassJavaType.java @@ -26,6 +26,11 @@ public boolean isInstance(Object value) { return value instanceof Class; } + @Override + public Class cast(Object value) { + return (Class) value; + } + @Override public boolean useObjectEqualsHashCode() { return true; @@ -51,16 +56,15 @@ public Class fromString(CharSequence string) { } @Override - @SuppressWarnings("unchecked") public X unwrap(Class value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( Class.class.isAssignableFrom( type ) ) { - return (X) value; + return type.cast( value ); } if ( String.class.isAssignableFrom( type ) ) { - return (X) toString( value ); + return type.cast( toString( value ) ); } throw unknownUnwrap( type ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobJavaType.java index 398d7ee68491..2ff3bd8fb2ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobJavaType.java @@ -52,6 +52,11 @@ public boolean isInstance(Object value) { return value instanceof Clob; } + @Override + public Clob cast(Object value) { + return (Clob) value; + } + @Override public String extractLoggableRepresentation(Clob value) { return value == null ? "null" : "{clob}"; @@ -81,7 +86,6 @@ public Clob getReplacement(Clob original, Clob target, SharedSessionContractImpl .mergeClob( original, target, session ); } - @SuppressWarnings("unchecked") public X unwrap(final Clob value, Class type, WrapperOptions options) { if ( value == null ) { return null; @@ -89,36 +93,36 @@ public X unwrap(final Clob value, Class type, WrapperOptions options) { try { if ( Clob.class.isAssignableFrom( type ) ) { - return (X) options.getLobCreator().toJdbcClob( value ); + return type.cast( options.getLobCreator().toJdbcClob( value ) ); } else if ( String.class.isAssignableFrom( type ) ) { if (value instanceof ClobImplementer clobImplementer) { // if the incoming Clob is a wrapper, just grab the string from its CharacterStream - return (X) clobImplementer.getUnderlyingStream().asString(); + return type.cast( clobImplementer.getUnderlyingStream().asString() ); } else { // otherwise extract the bytes from the stream manually - return (X) extractString( value.getCharacterStream() ); + return type.cast( extractString( value.getCharacterStream() ) ); } } else if ( Reader.class.isAssignableFrom( type ) ) { if (value instanceof ClobImplementer clobImplementer) { // if the incoming NClob is a wrapper, just pass along its BinaryStream - return (X) clobImplementer.getUnderlyingStream().asReader(); + return type.cast( clobImplementer.getUnderlyingStream().asReader() ); } else { // otherwise we need to build a CharacterStream... - return (X) value.getCharacterStream(); + return type.cast( value.getCharacterStream() ); } } else if ( CharacterStream.class.isAssignableFrom( type ) ) { if (value instanceof ClobImplementer clobImplementer) { // if the incoming Clob is a wrapper, just pass along its CharacterStream - return (X) clobImplementer.getUnderlyingStream(); + return type.cast( clobImplementer.getUnderlyingStream() ); } else { // otherwise we need to build a CharacterStream... - return (X) value.getCharacterStream(); + return type.cast( value.getCharacterStream() ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CurrencyJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CurrencyJavaType.java index a2fa030de8fb..9799b675ea1d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CurrencyJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CurrencyJavaType.java @@ -27,6 +27,11 @@ public boolean isInstance(Object value) { return value instanceof Currency; } + @Override + public Currency cast(Object value) { + return (Currency) value; + } + @Override public boolean useObjectEqualsHashCode() { return true; @@ -42,16 +47,15 @@ public Currency fromString(CharSequence string) { return Currency.getInstance( string.toString() ); } - @SuppressWarnings("unchecked") public X unwrap(Currency value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( Currency.class.isAssignableFrom( type ) ) { - return (X) value; + return type.cast( value ); } if ( String.class.isAssignableFrom( type ) ) { - return (X) value.getCurrencyCode(); + return type.cast( value.getCurrencyCode() ); } throw unknownUnwrap( type ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DateJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DateJavaType.java index 430a8664a5f8..f6680becfc12 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DateJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DateJavaType.java @@ -4,18 +4,14 @@ */ package org.hibernate.type.descriptor.java; +import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; -import java.time.format.DateTimeParseException; -import java.time.temporal.ChronoField; -import java.time.temporal.TemporalAccessor; import java.util.Calendar; import java.util.Date; -import java.util.GregorianCalendar; import jakarta.persistence.TemporalType; -import org.hibernate.HibernateException; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.descriptor.WrapperOptions; @@ -30,17 +26,54 @@ */ public class DateJavaType extends AbstractTemporalJavaType implements VersionJavaType { public static final DateJavaType INSTANCE = new DateJavaType(); + private final @SuppressWarnings("deprecation") TemporalType precision; public static class DateMutabilityPlan extends MutableMutabilityPlan { - public static final DateMutabilityPlan INSTANCE = new DateMutabilityPlan(); + @SuppressWarnings("deprecation") + public static final DateMutabilityPlan INSTANCE = + new DateMutabilityPlan( TemporalType.TIMESTAMP ); + + private final @SuppressWarnings("deprecation") TemporalType precision; + + public DateMutabilityPlan(@SuppressWarnings("deprecation") TemporalType precision) { + this.precision = precision; + } + @Override public Date deepCopyNotNull(Date value) { - return new Date( value.getTime() ); + if ( value instanceof java.sql.Timestamp timestamp ) { + return JdbcTimestampJavaType.TimestampMutabilityPlan.INSTANCE.deepCopyNotNull( timestamp ); + } + else if ( value instanceof java.sql.Date date ) { + return JdbcDateJavaType.DateMutabilityPlan.INSTANCE.deepCopyNotNull( date ); + } + else if ( value instanceof java.sql.Time time ) { + return JdbcTimeJavaType.TimeMutabilityPlan.INSTANCE.deepCopyNotNull( time ); + } + else { + return switch ( precision ) { + case TIMESTAMP -> toTimestamp( value ); + case DATE -> toDate( value ); + case TIME -> toTime( value ); + }; + } } } + @SuppressWarnings("deprecation") public DateJavaType() { super( Date.class, DateMutabilityPlan.INSTANCE ); + this.precision = TemporalType.TIMESTAMP; + } + + /** + * A {@link Date} may be used to represent a date, time, or timestamp, + * each of which have different semantics at the Java level. Therefore, + * we distinguish these usages based on the given {@code TemporalType}. + */ + private DateJavaType(@SuppressWarnings("deprecation") TemporalType precision) { + super( Date.class, new DateMutabilityPlan( precision ) ); + this.precision = precision; } @Override @@ -49,53 +82,57 @@ public boolean isInstance(Object value) { } @Override - public TemporalType getPrecision() { - return TemporalType.TIMESTAMP; + public Date cast(Object value) { + return (Date) value; } @Override - public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) { - // this "Date" is really a timestamp - return dialect.getDefaultTimestampPrecision(); + public @SuppressWarnings("deprecation") TemporalType getPrecision() { + return precision; } @Override - public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { - return context.getJdbcType( Types.TIMESTAMP ); - } - - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) JdbcDateJavaType.INSTANCE; + public TemporalJavaType resolveTypeForPrecision( + @SuppressWarnings("deprecation") TemporalType precision, + TypeConfiguration typeConfiguration) { + return precision == null ? this : new DateJavaType( precision ); } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) JdbcTimestampJavaType.INSTANCE; + @Override + public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) { + return switch ( precision ) { + case TIMESTAMP -> JdbcTimestampJavaType.INSTANCE.getDefaultSqlPrecision( dialect, jdbcType ); + case DATE -> JdbcDateJavaType.INSTANCE.getDefaultSqlPrecision( dialect, jdbcType ); + case TIME -> JdbcTimeJavaType.INSTANCE.getDefaultSqlPrecision( dialect, jdbcType ); + }; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) JdbcTimeJavaType.INSTANCE; + @Override + public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { + return context.getJdbcType( switch ( precision ) { + case TIMESTAMP -> Types.TIMESTAMP; + case DATE -> Types.DATE; + case TIME -> Types.TIME; + } ); } @Override public String toString(Date value) { - return JdbcTimestampJavaType.LITERAL_FORMATTER.format( value.toInstant() ); +// return JdbcTimestampJavaType.LITERAL_FORMATTER.format( value.toInstant() ); + return switch ( precision ) { + case TIMESTAMP -> JdbcTimestampJavaType.INSTANCE.toString( toTimestamp( value ) ); + case DATE -> JdbcDateJavaType.INSTANCE.toString( toDate( value ) ); + case TIME -> JdbcTimeJavaType.INSTANCE.toString( toTime( value ) ); + }; } @Override public Date fromString(CharSequence string) { - try { - final TemporalAccessor accessor = JdbcTimestampJavaType.LITERAL_FORMATTER.parse( string ); - return new Date( - accessor.getLong( ChronoField.INSTANT_SECONDS ) * 1000L - + accessor.get( ChronoField.NANO_OF_SECOND ) / 1_000_000 - ); - } - catch ( DateTimeParseException pe) { - throw new HibernateException( "could not parse timestamp string" + string, pe ); - } + return switch ( precision ) { + case TIMESTAMP -> JdbcTimestampJavaType.INSTANCE.fromString( string ); + case DATE -> JdbcDateJavaType.INSTANCE.fromString( string ); + case TIME -> JdbcTimeJavaType.INSTANCE.fromString( string ); + }; } @Override @@ -103,79 +140,68 @@ public boolean areEqual(Date one, Date another) { if ( one == another) { return true; } - return !( one == null || another == null ) && one.getTime() == another.getTime(); - - } - - @Override + return one != null && another != null + && switch ( precision ) { + case DATE -> JdbcDateJavaType.INSTANCE.areEqual( toDate( one ), toDate( another ) ); + case TIME -> JdbcTimeJavaType.INSTANCE.areEqual( toTime( one ), toTime( another ) ); + case TIMESTAMP -> + // emulate legacy behavior (good or not) + one instanceof Timestamp timestamp && another instanceof Timestamp anotherTimestamp + ? JdbcTimestampJavaType.INSTANCE.areEqual( timestamp, anotherTimestamp ) + : one.getTime() == another.getTime(); + }; + } + + @Override @SuppressWarnings("deprecation") public int extractHashCode(Date value) { - Calendar calendar = Calendar.getInstance(); + var calendar = Calendar.getInstance(); calendar.setTime( value ); - return CalendarJavaType.INSTANCE.extractHashCode( calendar ); + int hashCode = 1; + if ( precision == TemporalType.TIMESTAMP ) { + hashCode = 31 * hashCode + calendar.get(Calendar.MILLISECOND); + } + if ( precision != TemporalType.DATE ) { + hashCode = 31 * hashCode + calendar.get(Calendar.SECOND); + hashCode = 31 * hashCode + calendar.get(Calendar.MINUTE); + hashCode = 31 * hashCode + calendar.get(Calendar.HOUR_OF_DAY); + } + if ( precision != TemporalType.TIME ) { + hashCode = 31 * hashCode + calendar.get(Calendar.DAY_OF_MONTH); + hashCode = 31 * hashCode + calendar.get(Calendar.MONTH); + hashCode = 31 * hashCode + calendar.get(Calendar.YEAR); + } + return hashCode; } - @SuppressWarnings("unchecked") @Override public X unwrap(Date value, Class type, WrapperOptions options) { - if ( value == null ) { - return null; - } - if ( java.sql.Date.class.isAssignableFrom( type ) ) { - final java.sql.Date rtn = value instanceof java.sql.Date - ? ( java.sql.Date ) value - : new java.sql.Date( value.getTime() ); - return (X) rtn; - } - if ( java.sql.Time.class.isAssignableFrom( type ) ) { - final java.sql.Time rtn = value instanceof java.sql.Time - ? ( java.sql.Time ) value - : new java.sql.Time( value.getTime() % 86_400_000 ); - return (X) rtn; - } - if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) { - final java.sql.Timestamp rtn = value instanceof Timestamp - ? ( java.sql.Timestamp ) value - : new java.sql.Timestamp( value.getTime() ); - return (X) rtn; - } - if ( Date.class.isAssignableFrom( type ) ) { - return (X) value; - } - if ( Calendar.class.isAssignableFrom( type ) ) { - final GregorianCalendar cal = new GregorianCalendar(); - cal.setTimeInMillis( value.getTime() ); - return (X) cal; - } - if ( Long.class.isAssignableFrom( type ) ) { - return (X) Long.valueOf( value.getTime() ); - } - throw unknownUnwrap( type ); + return switch ( precision ) { + case TIMESTAMP -> JdbcTimestampJavaType.INSTANCE.unwrap( toTimestamp( value ), type, options ); + case DATE -> JdbcDateJavaType.INSTANCE.unwrap( toDate( value ), type, options ); + case TIME -> JdbcTimeJavaType.INSTANCE.unwrap( toTime( value ), type, options ); + }; } + @Override public Date wrap(X value, WrapperOptions options) { - if ( value == null ) { - return null; - } - if (value instanceof Date date) { - return date; - } - - if (value instanceof Long longValue) { - return new Date( longValue ); - } - - if (value instanceof Calendar calendar) { - return new Date( calendar.getTimeInMillis() ); - } + return switch ( precision ) { + case TIMESTAMP -> JdbcTimestampJavaType.INSTANCE.wrap( value, options ); + case DATE -> JdbcDateJavaType.INSTANCE.wrap( value, options ); + case TIME -> JdbcTimeJavaType.INSTANCE.wrap( value, options ); + }; + } - throw unknownWrap( value.getClass() ); + @Override + public Object coerce(Object value) { + return wrap( value, null ); } @Override public boolean isWider(JavaType javaType) { - return switch ( javaType.getTypeName() ) { - case "java.sql.Date", "java.sql.Timestamp", "java.util.Calendar" -> true; - default -> false; + return switch ( precision ) { + case TIMESTAMP -> JdbcTimestampJavaType.INSTANCE.isWider( javaType ); + case DATE -> JdbcDateJavaType.INSTANCE.isWider( javaType ); + case TIME -> JdbcTimeJavaType.INSTANCE.isWider( javaType ); }; } @@ -195,4 +221,21 @@ public Date seed( Integer precision, Integer scale, SharedSessionContractImplementor session) { return Timestamp.from( ClockHelper.forPrecision( precision, session ).instant() ); } + + private static Timestamp toTimestamp(Date date) { + return date instanceof Timestamp timestamp + ? timestamp + : JdbcTimestampJavaType.wrapSqlTimestamp( date ); + } + + private static Time toTime(Date date) { + return date instanceof Time time + ? time + : JdbcTimeJavaType.toTime( date ); + } + + private static java.sql.Date toDate(java.util.Date value) { + return JdbcDateJavaType.toDate( value ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoubleJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoubleJavaType.java index 3bf60f6c2497..6ee06c11b55c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoubleJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoubleJavaType.java @@ -53,38 +53,42 @@ public boolean isInstance(Object value) { return value instanceof Double; } - @SuppressWarnings("unchecked") + @Override + public Double cast(Object value) { + return (Double) value; + } + @Override public X unwrap(Double value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( Double.class.isAssignableFrom( type ) || type == Object.class ) { - return (X) value; + return type.cast( value ); } if ( Float.class.isAssignableFrom( type ) ) { - return (X) Float.valueOf( value.floatValue() ); + return type.cast( value.floatValue() ); } if ( Byte.class.isAssignableFrom( type ) ) { - return (X) Byte.valueOf( value.byteValue() ); + return type.cast( value.byteValue() ); } if ( Short.class.isAssignableFrom( type ) ) { - return (X) Short.valueOf( value.shortValue() ); + return type.cast( value.shortValue() ); } if ( Integer.class.isAssignableFrom( type ) ) { - return (X) Integer.valueOf( value.intValue() ); + return type.cast( value.intValue() ); } if ( Long.class.isAssignableFrom( type ) ) { - return (X) Long.valueOf( value.longValue() ); + return type.cast( value.longValue() ); } if ( BigInteger.class.isAssignableFrom( type ) ) { - return (X) BigInteger.valueOf( value.longValue() ); + return type.cast( BigInteger.valueOf( value.longValue() ) ); } if ( BigDecimal.class.isAssignableFrom( type ) ) { - return (X) BigDecimal.valueOf( value ); + return type.cast( BigDecimal.valueOf( value ) ); } if ( String.class.isAssignableFrom( type ) ) { - return (X) value.toString(); + return type.cast( value.toString() ); } throw unknownUnwrap( type ); } @@ -160,7 +164,7 @@ public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) { } @Override - public Double coerce(X value, CoercionContext coercionContext) { + public Double coerce(Object value) { if ( value == null ) { return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoublePrimitiveArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoublePrimitiveArrayJavaType.java index 0ed6cc8f997f..10218ee8a963 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoublePrimitiveArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoublePrimitiveArrayJavaType.java @@ -41,6 +41,11 @@ public boolean isInstance(Object value) { return value instanceof double[]; } + @Override + public double[] cast(Object value) { + return (double[]) value; + } + @Override public String extractLoggableRepresentation(double[] value) { return value == null ? super.extractLoggableRepresentation( null ) : Arrays.toString( value ); @@ -106,7 +111,7 @@ public X unwrap(double[] value, Class type, WrapperOptions options) { } if ( type.isInstance( value ) ) { - return (X) value; + return type.cast( value ); } else if ( Object[].class.isAssignableFrom( type ) ) { final Class preferredJavaTypeClass = type.getComponentType(); @@ -114,16 +119,15 @@ else if ( Object[].class.isAssignableFrom( type ) ) { for ( int i = 0; i < value.length; i++ ) { unwrapped[i] = getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options ); } - return (X) unwrapped; + return type.cast( unwrapped ); } else if ( type == byte[].class ) { // byte[] can only be requested if the value should be serialized - return (X) SerializationHelper.serialize( value ); + return type.cast( SerializationHelper.serialize( value ) ); } else if ( type == BinaryStream.class ) { // BinaryStream can only be requested if the value should be serialized - //noinspection unchecked - return (X) new ArrayBackedBinaryStream( SerializationHelper.serialize( value ) ); + return type.cast( new ArrayBackedBinaryStream( SerializationHelper.serialize( value ) ) ); } else if ( type.isArray() ) { final Class preferredJavaTypeClass = type.getComponentType(); @@ -131,7 +135,7 @@ else if ( type.isArray() ) { for ( int i = 0; i < value.length; i++ ) { Array.set( unwrapped, i, getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options ) ); } - return (X) unwrapped; + return type.cast( unwrapped ); } throw unknownUnwrap( type ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DurationJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DurationJavaType.java index fa26767f4f6f..5412aa4e194b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DurationJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DurationJavaType.java @@ -48,6 +48,11 @@ public boolean isInstance(Object value) { return value instanceof Duration; } + @Override + public Duration cast(Object value) { + return (Duration) value; + } + @Override public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { return context.getTypeConfiguration() @@ -88,28 +93,27 @@ public Duration fromString(CharSequence string) { } @Override - @SuppressWarnings("unchecked") public X unwrap(Duration duration, Class type, WrapperOptions options) { if ( duration == null ) { return null; } if ( Duration.class.isAssignableFrom( type ) ) { - return (X) duration; + return type.cast( duration ); } if ( BigDecimal.class.isAssignableFrom( type ) ) { - return (X) new BigDecimal( duration.getSeconds() ) + return type.cast( new BigDecimal( duration.getSeconds() ) .movePointRight( 9 ) - .add( new BigDecimal( duration.getNano() ) ); + .add( new BigDecimal( duration.getNano() ) ) ); } if ( String.class.isAssignableFrom( type ) ) { - return (X) duration.toString(); + return type.cast( duration.toString() ); } if ( Long.class.isAssignableFrom( type ) ) { - return (X) Long.valueOf( duration.toNanos() ); + return type.cast( duration.toNanos() ); } throw unknownUnwrap( type ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaType.java index 6f8edb9dfe94..8273f2cf908d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/EnumJavaType.java @@ -96,28 +96,31 @@ public T fromString(CharSequence string) { } @Override - @SuppressWarnings("unchecked") public X unwrap(T value, Class type, WrapperOptions options) { if ( String.class.equals( type ) ) { - return (X) toName( value ); + return type.cast( toName( value ) ); } else if ( Long.class.equals( type ) ) { - return (X) toLong( value ); + return type.cast( toLong( value ) ); } else if ( Integer.class.equals( type ) ) { - return (X) toInteger( value ); + return type.cast( toInteger( value ) ); } else if ( Short.class.equals( type ) ) { - return (X) toShort( value ); + return type.cast( toShort( value ) ); } else if ( Byte.class.equals( type ) ) { - return (X) toByte( value ); + return type.cast( toByte( value ) ); + } + else if ( type.isInstance( value )) { + return type.cast( value ); + } + else { + throw unknownUnwrap( type ); } - return (X) value; } @Override - @SuppressWarnings("unchecked") public T wrap(X value, WrapperOptions options) { if ( value == null ) { return null; @@ -140,8 +143,14 @@ else if ( value instanceof Byte byteValue ) { else if ( value instanceof Number number ) { return fromLong( number.longValue() ); } + else if ( getJavaType().isInstance( value ) ) { + return (T) value; + } + else if ( isInstance( value ) ) { + return cast( value ); + } else { - return (T) value; + throw unknownWrap( value.getClass() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatJavaType.java index 4a6c6e9e8f5d..458640f20522 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatJavaType.java @@ -52,38 +52,42 @@ public boolean isInstance(Object value) { return value instanceof Float; } - @SuppressWarnings("unchecked") + @Override + public Float cast(Object value) { + return (Float) value; + } + @Override public X unwrap(Float value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( Float.class.isAssignableFrom( type ) || type == Object.class ) { - return (X) value; + return type.cast( value ); } if ( Double.class.isAssignableFrom( type ) ) { - return (X) Double.valueOf( value.doubleValue() ); + return type.cast( value.doubleValue() ); } if ( Byte.class.isAssignableFrom( type ) ) { - return (X) Byte.valueOf( value.byteValue() ); + return type.cast( value.byteValue() ); } if ( Short.class.isAssignableFrom( type ) ) { - return (X) Short.valueOf( value.shortValue() ); + return type.cast( value.shortValue() ); } if ( Integer.class.isAssignableFrom( type ) ) { - return (X) Integer.valueOf( value.intValue() ); + return type.cast( value.intValue() ); } if ( Long.class.isAssignableFrom( type ) ) { - return (X) Long.valueOf( value.longValue() ); + return type.cast( value.longValue() ); } if ( BigInteger.class.isAssignableFrom( type ) ) { - return (X) BigInteger.valueOf( value.longValue() ); + return type.cast( BigInteger.valueOf( value.longValue() ) ); } if ( BigDecimal.class.isAssignableFrom( type ) ) { - return (X) BigDecimal.valueOf( value ); + return type.cast( BigDecimal.valueOf( value ) ); } if ( String.class.isAssignableFrom( type ) ) { - return (X) value.toString(); + return type.cast( value.toString() ); } throw unknownUnwrap( type ); } @@ -158,7 +162,7 @@ public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) { } @Override - public Float coerce(X value, CoercionContext coercionContext) { + public Float coerce(Object value) { if ( value == null ) { return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatPrimitiveArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatPrimitiveArrayJavaType.java index 25f431b6a114..bd67839519c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatPrimitiveArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatPrimitiveArrayJavaType.java @@ -41,6 +41,11 @@ public boolean isInstance(Object value) { return value instanceof float[]; } + @Override + public float[] cast(Object value) { + return (float[]) value; + } + @Override public String extractLoggableRepresentation(float[] value) { return value == null ? super.extractLoggableRepresentation( null ) : Arrays.toString( value ); @@ -106,7 +111,7 @@ public X unwrap(float[] value, Class type, WrapperOptions options) { } if ( type.isInstance( value ) ) { - return (X) value; + return type.cast( value ); } else if ( Object[].class.isAssignableFrom( type ) ) { final Class preferredJavaTypeClass = type.getComponentType(); @@ -114,16 +119,15 @@ else if ( Object[].class.isAssignableFrom( type ) ) { for ( int i = 0; i < value.length; i++ ) { unwrapped[i] = getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options ); } - return (X) unwrapped; + return type.cast( unwrapped ); } else if ( type == byte[].class ) { // byte[] can only be requested if the value should be serialized - return (X) SerializationHelper.serialize( value ); + return type.cast( SerializationHelper.serialize( value ) ); } else if ( type == BinaryStream.class ) { // BinaryStream can only be requested if the value should be serialized - //noinspection unchecked - return (X) new ArrayBackedBinaryStream( SerializationHelper.serialize( value ) ); + return type.cast( new ArrayBackedBinaryStream( SerializationHelper.serialize( value ) ) ); } else if ( type.isArray() ) { final Class preferredJavaTypeClass = type.getComponentType(); @@ -131,7 +135,7 @@ else if ( type.isArray() ) { for ( int i = 0; i < value.length; i++ ) { Array.set( unwrapped, i, getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options ) ); } - return (X) unwrapped; + return type.cast( unwrapped ); } throw unknownUnwrap( type ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InetAddressJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InetAddressJavaType.java index 1d0d56a15d4c..1d742aa2cfd5 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InetAddressJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InetAddressJavaType.java @@ -51,20 +51,19 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) { return indicators.getJdbcType( SqlTypes.INET ); } - @SuppressWarnings("unchecked") @Override public X unwrap(InetAddress value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( InetAddress.class.isAssignableFrom( type ) ) { - return (X) value; + return type.cast( value ); } if ( byte[].class.isAssignableFrom( type ) ) { - return (X) value.getAddress(); + return type.cast( value.getAddress() ); } if ( String.class.isAssignableFrom( type ) ) { - return (X) value.getHostAddress(); + return type.cast( value.getHostAddress() ); } throw unknownUnwrap( type ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaType.java index 5b78c6fa8e6f..9c75946c7b47 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaType.java @@ -48,23 +48,28 @@ public boolean isInstance(Object value) { } @Override + public Instant cast(Object value) { + return (Instant) value; + } + + @Override @SuppressWarnings("deprecation") public TemporalType getPrecision() { return TemporalType.TIMESTAMP; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) this; + @Override + protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { + return this; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) this; + @Override + protected TemporalJavaType forTimestampPrecision(TypeConfiguration typeConfiguration) { + return this; } - @Override @SuppressWarnings("unchecked") - protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { - return (TemporalJavaType) this; + @Override + protected TemporalJavaType forTimePrecision(TypeConfiguration typeConfiguration) { + return this; } @Override @@ -88,22 +93,21 @@ public Instant fromString(CharSequence string) { } @Override - @SuppressWarnings("unchecked") public X unwrap(Instant instant, Class type, WrapperOptions options) { if ( instant == null ) { return null; } if ( Instant.class.isAssignableFrom( type ) ) { - return (X) instant; + return type.cast( instant ); } if ( OffsetDateTime.class.isAssignableFrom( type ) ) { - return (X) instant.atOffset( ZoneOffset.UTC ); + return type.cast( instant.atOffset( ZoneOffset.UTC ) ); } if ( Calendar.class.isAssignableFrom( type ) ) { - return (X) GregorianCalendar.from( instant.atZone( ZoneOffset.UTC ) ); + return type.cast( GregorianCalendar.from( instant.atZone( ZoneOffset.UTC ) ) ); } if ( Timestamp.class.isAssignableFrom( type ) ) { @@ -119,27 +123,27 @@ public X unwrap(Instant instant, Class type, WrapperOptions options) { */ final ZonedDateTime zonedDateTime = instant.atZone( ZoneId.systemDefault() ); if ( zonedDateTime.getYear() < 1905 ) { - return (X) Timestamp.valueOf( zonedDateTime.toLocalDateTime() ); + return type.cast( Timestamp.valueOf( zonedDateTime.toLocalDateTime() ) ); } else { - return (X) Timestamp.from( instant ); + return type.cast( Timestamp.from( instant ) ); } } if ( java.sql.Date.class.isAssignableFrom( type ) ) { - return (X) new java.sql.Date( instant.toEpochMilli() ); + return type.cast( new java.sql.Date( instant.toEpochMilli() ) ); } if ( java.sql.Time.class.isAssignableFrom( type ) ) { - return (X) new java.sql.Time( instant.toEpochMilli() % 86_400_000 ); + return type.cast( new java.sql.Time( instant.toEpochMilli() % 86_400_000 ) ); } if ( Date.class.isAssignableFrom( type ) ) { - return (X) Date.from( instant ); + return type.cast( Date.from( instant ) ); } if ( Long.class.isAssignableFrom( type ) ) { - return (X) Long.valueOf( instant.toEpochMilli() ); + return type.cast( instant.toEpochMilli() ); } throw unknownUnwrap( type ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerJavaType.java index cd83479a604b..e6e3364f5e8b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerJavaType.java @@ -49,38 +49,42 @@ public boolean isInstance(Object value) { return value instanceof Integer; } - @SuppressWarnings("unchecked") + @Override + public Integer cast(Object value) { + return (Integer) value; + } + @Override public X unwrap(Integer value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( Integer.class.isAssignableFrom( type ) || type == Object.class ) { - return (X) value; + return type.cast( value ); } if ( Byte.class.isAssignableFrom( type ) ) { - return (X) Byte.valueOf( value.byteValue() ); + return type.cast( value.byteValue() ); } if ( Short.class.isAssignableFrom( type ) ) { - return (X) Short.valueOf( value.shortValue() ); + return type.cast( value.shortValue() ); } if ( Long.class.isAssignableFrom( type ) ) { - return (X) Long.valueOf( value.longValue() ); + return type.cast( value.longValue() ); } if ( Double.class.isAssignableFrom( type ) ) { - return (X) Double.valueOf( value.doubleValue() ); + return type.cast( value.doubleValue() ); } if ( Float.class.isAssignableFrom( type ) ) { - return (X) Float.valueOf( value.floatValue() ); + return type.cast( value.floatValue() ); } if ( BigInteger.class.isAssignableFrom( type ) ) { - return (X) BigInteger.valueOf( value ); + return type.cast( BigInteger.valueOf( value ) ); } if ( BigDecimal.class.isAssignableFrom( type ) ) { - return (X) BigDecimal.valueOf( value ); + return type.cast( BigDecimal.valueOf( value ) ); } if ( String.class.isAssignableFrom( type ) ) { - return (X) value.toString(); + return type.cast( value.toString() ); } throw unknownUnwrap( type ); } @@ -148,7 +152,7 @@ public int getDefaultSqlScale(Dialect dialect, JdbcType jdbcType) { } @Override - public Integer coerce(Object value, CoercionContext coercionContext) { + public Integer coerce(Object value) { if ( value == null ) { return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerPrimitiveArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerPrimitiveArrayJavaType.java index 703e12cf1fbd..5bc19094ea44 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerPrimitiveArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerPrimitiveArrayJavaType.java @@ -41,6 +41,11 @@ public boolean isInstance(Object value) { return value instanceof int[]; } + @Override + public int[] cast(Object value) { + return (int[]) value; + } + @Override public String extractLoggableRepresentation(int[] value) { return value == null ? super.extractLoggableRepresentation( null ) : Arrays.toString( value ); @@ -106,7 +111,7 @@ public X unwrap(int[] value, Class type, WrapperOptions options) { } if ( type.isInstance( value ) ) { - return (X) value; + return type.cast( value ); } else if ( Object[].class.isAssignableFrom( type ) ) { final Class preferredJavaTypeClass = type.getComponentType(); @@ -114,16 +119,15 @@ else if ( Object[].class.isAssignableFrom( type ) ) { for ( int i = 0; i < value.length; i++ ) { unwrapped[i] = getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options ); } - return (X) unwrapped; + return type.cast( unwrapped ); } else if ( type == byte[].class ) { // byte[] can only be requested if the value should be serialized - return (X) SerializationHelper.serialize( value ); + return type.cast( SerializationHelper.serialize( value ) ); } else if ( type == BinaryStream.class ) { // BinaryStream can only be requested if the value should be serialized - //noinspection unchecked - return (X) new ArrayBackedBinaryStream( SerializationHelper.serialize( value ) ); + return type.cast( new ArrayBackedBinaryStream( SerializationHelper.serialize( value ) ) ); } else if ( type.isArray() ) { final Class preferredJavaTypeClass = type.getComponentType(); @@ -131,7 +135,7 @@ else if ( type.isArray() ) { for ( int i = 0; i < value.length; i++ ) { Array.set( unwrapped, i, getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options ) ); } - return (X) unwrapped; + return type.cast( unwrapped ); } throw unknownUnwrap( type ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java index be7453a46d61..c248d76982de 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java @@ -100,11 +100,31 @@ default String getTypeName() { * but some descriptors need specialized semantics, for example, the descriptors for * {@link JdbcDateJavaType java.sql.Date}, {@link JdbcTimeJavaType java.sql.Time}, and * {@link JdbcTimestampJavaType java.sql.Timestamp}. + *

+ * For {@link org.hibernate.type.descriptor.java.spi.EntityJavaType}, this method + * handles proxies in a semantically correct way, by checking the entity instance + * underlying the proxy object. */ default boolean isInstance(Object value) { return getJavaTypeClass().isInstance( value ); } + /** + * Apply a simple type cast to the given value, without attempting any sort of + * {@linkplain #coerce(Object) coercion} or {@linkplain #wrap wrapping}. This + * method is provided as a convenient way to avoid an unchecked cast to a type + * variable. Use {@code javaType.cast(value)} instead of {@code (T) value} + * wherever possible. + *

+ * Usually just {@link #getJavaTypeClass() getJavaTypeClass().}{@link Class#cast cast(value)}, + * but overridden in some cases as an "optimization". This optimization is + * almost certainly unnecessary, and might even indeed be harmful, since + * {@code Class.cast()} is an intrinsic. + */ + default T cast(Object value) { + return getJavaTypeClass().cast( value ); + } + /** * Retrieve the {@linkplain MutabilityPlan mutability plan} for this Java type. */ @@ -113,12 +133,11 @@ default MutabilityPlan getMutabilityPlan() { } default T getReplacement(T original, T target, SharedSessionContractImplementor session) { - if ( !getMutabilityPlan().isMutable() || target != null && areEqual( original, target ) ) { - return original; - } - else { - return getMutabilityPlan().deepCopy( original ); - } + final var mutabilityPlan = getMutabilityPlan(); + return !mutabilityPlan.isMutable() + || target != null && areEqual( original, target ) + ? original + : mutabilityPlan.deepCopy( original ); } /** @@ -131,7 +150,7 @@ default T getDefaultValue() { } /** - * Obtain the "recommended" {@link JdbcType SQL type descriptor} + * Obtain the "recommended" {@linkplain JdbcType SQL type descriptor} * for this Java type. Often, but not always, the source of this * recommendation is the JDBC specification. * @@ -312,13 +331,40 @@ default boolean isWider(JavaType javaType) { } @FunctionalInterface + @Deprecated(forRemoval = true, since = "7.2") interface CoercionContext { TypeConfiguration getTypeConfiguration(); } - default T coerce(X value, CoercionContext coercionContext) { - //noinspection unchecked - return (T) value; + /** + * Coerce the given value to this type, if possible. + *

+ * This method differs from {@link #wrap wrap()} in that it allows + * simple, basic, implicit type conversions, and does not require + * {@link WrapperOptions}. The {@code wrap()} method may be thought + * of as offering explicitly requested type conversions driven by a + * choice of {@link JdbcType}. + *

+ * An implementation of this method reports failure in one of two + * ways, by: + *

    + *
  • throwing {@link CoercionException}, or + *
  • simply returning the given uncoerced value. + *
+ *

+ * Therefore, this method is declared to return {@link Object}. + * In case immediate coercion is required, the following idiom + * may be used: + *

javaType.cast(javaType.coerce(value))
+ * + * @param value The value to coerce + * @return The coerced value, or the given value if no coercion was + * possible + * @throws CoercionException if coercion fails + */ + @Incubating + default Object coerce(Object value) { + return value; } /** diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateJavaType.java index a60835ae643b..ebad46f62753 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateJavaType.java @@ -6,13 +6,13 @@ import java.sql.Types; import java.time.LocalDate; -import java.time.LocalTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAccessor; import java.util.Calendar; -import java.util.Date; +import java.sql.Date; import java.util.GregorianCalendar; import org.hibernate.HibernateException; @@ -55,25 +55,28 @@ public JdbcDateJavaType() { super( Date.class, DateMutabilityPlan.INSTANCE ); } - @Override + @Override @SuppressWarnings("deprecation") public TemporalType getPrecision() { return TemporalType.DATE; } @Override public Class getJavaType() { - // wrong, but needed for backward compatibility - //noinspection unchecked, rawtypes - return (Class) java.sql.Date.class; + return java.sql.Date.class; } @Override public boolean isInstance(Object value) { // this check holds true for java.sql.Date as well - return value instanceof Date + return value instanceof java.util.Date && !( value instanceof java.sql.Time ); } + @Override + public Date cast(Object value) { + return (Date) value; + } + @Override public boolean areEqual(Date one, Date another) { if ( one == another ) { @@ -110,81 +113,57 @@ public int extractHashCode(Date value) { } @Override - public Date coerce(Object value, CoercionContext coercionContext) { + public Date coerce(Object value) { return wrap( value, null ); } - @SuppressWarnings("unchecked") @Override - public Object unwrap(Date value, Class type, WrapperOptions options) { + public X unwrap(Date value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( LocalDate.class.isAssignableFrom( type ) ) { - return unwrapLocalDate( value ); + return type.cast( unwrapLocalDate( value ) ); } if ( java.sql.Date.class.isAssignableFrom( type ) ) { - return unwrapSqlDate( value ); + return type.cast( value ); } if ( java.util.Date.class.isAssignableFrom( type ) ) { - return value; + return type.cast( value ); } if ( Long.class.isAssignableFrom( type ) ) { - return unwrapDateEpoch( value ); + return type.cast( toDateEpoch( value ) ); } if ( String.class.isAssignableFrom( type ) ) { - return toString( value ); + return type.cast( toString( value ) ); } if ( Calendar.class.isAssignableFrom( type ) ) { final var gregorianCalendar = new GregorianCalendar(); - gregorianCalendar.setTimeInMillis( unwrapDateEpoch( value ) ); - return gregorianCalendar; + gregorianCalendar.setTimeInMillis( toDateEpoch( value ) ); + return type.cast( gregorianCalendar ); } if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) { - return new java.sql.Timestamp( unwrapDateEpoch( value ) ); + return type.cast( new java.sql.Timestamp( toDateEpoch( value ) ) ); } if ( java.sql.Time.class.isAssignableFrom( type ) ) { - throw new IllegalArgumentException( "Illegal attempt to treat `java.sql.Date` as `java.sql.Time`" ); + throw new IllegalArgumentException( "Illegal attempt to treat 'java.sql.Date' as 'java.sql.Time'" ); } throw unknownUnwrap( type ); } - private LocalDate unwrapLocalDate(Date value) { + private LocalDate unwrapLocalDate(java.util.Date value) { return value instanceof java.sql.Date date ? date.toLocalDate() - : new java.sql.Date( unwrapDateEpoch( value ) ).toLocalDate(); - } - - private java.sql.Date unwrapSqlDate(Date value) { - if ( value instanceof java.sql.Date date ) { - final long dateEpoch = toDateEpoch( date.getTime() ); - return dateEpoch == date.getTime() ? date : new java.sql.Date( dateEpoch ); - } - return new java.sql.Date( unwrapDateEpoch( value ) ); - - } - - private static long unwrapDateEpoch(Date value) { - return toDateEpoch( value.getTime() ); - } - - private static long toDateEpoch(long value) { - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis( value ); - calendar.set(Calendar.HOUR_OF_DAY, 0); - calendar.clear(Calendar.MINUTE); - calendar.clear(Calendar.SECOND); - calendar.clear(Calendar.MILLISECOND); - return calendar.getTimeInMillis(); + : new java.sql.Date( toDateEpoch( value ) ).toLocalDate(); } @Override @@ -205,8 +184,8 @@ public Date wrap(Object value, WrapperOptions options) { return new java.sql.Date( toDateEpoch( calendar.getTimeInMillis() ) ); } - if ( value instanceof Date date ) { - return unwrapSqlDate( date ); + if ( value instanceof java.util.Date date ) { + return toDate( date ); } if ( value instanceof LocalDate localDate ) { @@ -216,16 +195,42 @@ public Date wrap(Object value, WrapperOptions options) { throw unknownWrap( value.getClass() ); } - @Override - public String toString(Date value) { - if ( value instanceof java.sql.Date ) { - return LITERAL_FORMATTER.format( ( (java.sql.Date) value ).toLocalDate() ); + static java.sql.Date toDate(java.util.Date value) { + if ( value instanceof java.sql.Date date ) { + final long millis = date.getTime(); + final long dateEpoch = toDateEpoch( millis ); + return dateEpoch == millis ? date : new java.sql.Date( dateEpoch ); } else { - return LITERAL_FORMATTER.format( LocalDate.ofInstant( value.toInstant(), ZoneOffset.systemDefault() ) ); + return new java.sql.Date( toDateEpoch( value ) ); } } + static long toDateEpoch(java.util.Date value) { + return toDateEpoch( value.getTime() ); + } + + static long toDateEpoch(long value) { + final var calendar = Calendar.getInstance(); + calendar.setTimeInMillis( value ); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.clear(Calendar.MINUTE); + calendar.clear(Calendar.SECOND); + calendar.clear(Calendar.MILLISECOND); + return calendar.getTimeInMillis(); + } + + private static TemporalAccessor fromDate(java.util.Date value) { + return value instanceof java.sql.Date date + ? date.toLocalDate() + : LocalDate.ofInstant( value.toInstant(), ZoneOffset.systemDefault() ); + } + + @Override + public String toString(Date value) { + return LITERAL_FORMATTER.format( fromDate( value ) ); + } + @Override public Date fromString(CharSequence string) { try { @@ -250,12 +255,7 @@ public Date fromEncodedString(CharSequence charSequence, int start, int end) { @Override public void appendEncodedString(SqlAppender sb, Date value) { - if ( value instanceof java.sql.Date ) { - LITERAL_FORMATTER.formatTo( ( (java.sql.Date) value ).toLocalDate(), sb ); - } - else { - LITERAL_FORMATTER.formatTo( LocalTime.ofInstant( value.toInstant(), ZoneOffset.systemDefault() ), sb ); - } + LITERAL_FORMATTER.formatTo( fromDate( value ), sb ); } @Override @@ -264,9 +264,8 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { } @Override - protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { - //noinspection unchecked - return (TemporalJavaType) this; + protected TemporalJavaType forDatePrecision(TypeConfiguration typeConfiguration) { + return this; } public static class DateMutabilityPlan extends MutableMutabilityPlan { @@ -274,12 +273,7 @@ public static class DateMutabilityPlan extends MutableMutabilityPlan { @Override public Date deepCopyNotNull(Date value) { - if ( value instanceof java.sql.Date ) { - return value; - } - else { - return new java.sql.Date( value.getTime() ); - } + return new java.sql.Date( value.getTime() ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeJavaType.java index 0ae67b8a4367..1d868df7fc70 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeJavaType.java @@ -37,7 +37,7 @@ * to {@link Time} values. This capability is shared with * {@link JdbcDateJavaType} and {@link JdbcTimestampJavaType}. */ -public class JdbcTimeJavaType extends AbstractTemporalJavaType { +public class JdbcTimeJavaType extends AbstractTemporalJavaType