diff --git a/.gitignore b/.gitignore index 8b8ed3008fe80279f037efea50cbbc34d6e64adc..4b79d87c01e9057910221737ae02b401a15803b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ # Typically *NIX text editors, by default, append '~' to files on saving to make backups *~ -# Gradle work directory +# Gradle work directory and caches .gradle +.gradletasknamecache # Build output directies /target diff --git a/.travis.yml b/.travis.yml index cfa43e211c27a619ea9257120decce542e39a5b7..6fc0e918dc3c2b50df754ba9c038dc1cf4099d74 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,20 @@ +dist: trusty language: java -jdk: - - oraclejdk8 -install: +jobs: + include: + - stage: Oracle JDK 8 + jdk: oraclejdk8 + - stage: AdoptOpenJDK 11.0.3 + install: + - curl -L -o install-jdk.sh https://github.com/sormuras/bach/raw/master/install-jdk.sh + - source ./install-jdk.sh --target ./openjdk11 --url https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.3%2B7/OpenJDK11U-jdk_x64_linux_hotspot_11.0.3_7.tar.gz + +before_script: + - java -version - ./gradlew assemble script: - - travis_wait 45 ./gradlew check + - ./gradlew check -Plog-test-progress=true before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ diff --git a/build.gradle b/build.gradle index aebe54d8e18a89c88fb514b097282b1b6329e7db..9118f34eda49c4e6605a2e0840804f350cd5e5c8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,3 @@ -import org.apache.tools.ant.filters.ReplaceTokens - /* * Hibernate, Relational Persistence for Idiomatic Java * @@ -11,34 +9,66 @@ buildscript { repositories { jcenter() mavenCentral() - maven { - name "jboss-snapshots" - url "http://snapshots.jboss.org/maven2/" - } } dependencies { - classpath 'org.hibernate.build.gradle:gradle-maven-publish-auth:2.0.1' - classpath 'org.hibernate.build.gradle:hibernate-matrix-testing:2.0.0.Final' + classpath 'org.hibernate.build.gradle:hibernate-matrix-testing:3.0.0.Final' classpath 'org.hibernate.build.gradle:version-injection-plugin:1.0.0' - classpath 'org.hibernate.build.gradle:gradle-xjc-plugin:1.0.2.Final' - classpath 'gradle.plugin.com.github.lburgazzoli:gradle-karaf-plugin:0.1.1' + classpath 'gradle.plugin.com.github.lburgazzoli:gradle-karaf-plugin:0.5.1' classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.7' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.3' - classpath 'de.thetaphi:forbiddenapis:2.5' + classpath 'de.thetaphi:forbiddenapis:3.0.1' } } plugins { - id 'com.gradle.build-scan' version '1.9' - id 'me.champeau.buildscan-recipes' version '0.1.7' + id 'me.champeau.buildscan-recipes' version '0.2.3' + id 'io.github.gradle-nexus.publish-plugin' version '1.1.0' + id 'nu.studer.credentials' version '2.1' + id 'org.hibernate.build.xjc' version '2.0.1' apply false + id 'org.hibernate.build.maven-repo-auth' version '3.0.3' apply false + id 'biz.aQute.bnd' version '5.1.1' apply false +} + +ext { + sonatypeOssrhUser = project.findProperty( 'SONATYPE_OSSRH_USER' ) + sonatypeOssrhPassword = project.findProperty( 'SONATYPE_OSSRH_PASSWORD' ) +} + +File versionFile = file( "${rootProject.projectDir}/gradle/version.properties" ) + +ext { + ormVersionFile = versionFile + baselineJavaVersion = '1.8' + ormVersion = HibernateVersion.fromFile( versionFile, project ) + // Override during releases + if ( project.hasProperty( 'releaseVersion' ) ) { + ormVersion = new HibernateVersion( project.releaseVersion, project ) + } + jpaVersion = new JpaVersion('2.2') +} + +// The Gradle Nexus Publish Plugin must be applied to the root project and requires group and version + +group = 'org.hibernate' +version = project.ormVersion.fullName + +nexusPublishing { + repositories { + sonatype { + username = project.sonatypeOssrhUser + password = project.sonatypeOssrhPassword + } + } } allprojects { repositories { mavenCentral() - maven { - name "jboss-snapshots" - url "http://snapshots.jboss.org/maven2/" + //Allow loading additional dependencies from a local path; + //useful to load JDBC drivers which can not be distributed in public. + if (System.env['ADDITIONAL_REPO'] != null) { + flatDir { + dirs "${System.env.ADDITIONAL_REPO}" + } } } apply plugin: 'idea' @@ -46,7 +76,8 @@ allprojects { // minimize changes, at least for now (gradle uses 'build' by default).. buildDir = "target" - apply from: rootProject.file( 'gradle/base-information.gradle' ) + group = 'org.hibernate' + version = project.ormVersion.fullName } @@ -57,11 +88,22 @@ task release { description = "The task performed when we are performing a release build. Relies on " + "the fact that subprojects will appropriately define a release task " + "themselves if they have any release-related activities to perform" + + doFirst { + def javaVersionsInUse = [gradle.ext.javaVersions.main.compiler, gradle.ext.javaVersions.main.release, + gradle.ext.javaVersions.test.compiler, gradle.ext.javaVersions.test.release, + gradle.ext.javaVersions.test.launcher].toSet() + // Force to release with JDK 8. It used to not work on JDK11 because of the hibernate-orm-modules module, + // but this limitation might be resolved now that this module has been deleted? + if ( javaVersionsInUse != [JavaLanguageVersion.of( 8 )].toSet() ) { + throw new IllegalStateException( "Please use JDK 8 to perform the release. Currently using: ${javaVersionsInUse}" ) + } + } } task publish { description = "The task performed when we want to just publish maven artifacts. Relies on " + - "the fact that subprojects will have a task named pubappropriately define a release task " + + "the fact that subprojects will appropriately define a release task " + "themselves if they have any release-related activities to perform" } @@ -79,24 +121,80 @@ task ciBuild { wrapper { - gradleVersion = '4.8' + // To upgrade the version of gradle used in the wrapper, run: + // ./gradlew wrapper --gradle-version NEW_VERSION distributionType = Wrapper.DistributionType.ALL } buildScan { - licenseAgreementUrl = 'https://gradle.com/terms-of-service' - licenseAgree = 'yes' + termsOfServiceUrl = 'https://gradle.com/terms-of-service' + termsOfServiceAgree = 'yes' +} +buildScanRecipes { recipe 'git-commit', baseUrl: 'https://github.com/hibernate/hibernate-orm/tree' } +class JpaVersion { + /** The *normal* name (1.0, 2.0, ..) */ + final String name; + + final String osgiName + + JpaVersion(String version){ + this.name = version + this.osgiName = version + ".0" + } + + @Override + String toString() { + return name + } +} + +class HibernateVersion { + final String fullName + final String majorVersion + final String family + + final String osgiVersion + final boolean isSnapshot + HibernateVersion(String fullName, Project project) { + this.fullName = fullName + final String[] hibernateVersionComponents = fullName.split( '\\.' ) + this.majorVersion = hibernateVersionComponents[0] + this.family = hibernateVersionComponents[0] + '.' + hibernateVersionComponents[1] + this.isSnapshot = fullName.endsWith( '-SNAPSHOT' ) + this.osgiVersion = isSnapshot ? family + '.' + hibernateVersionComponents[2] + '.SNAPSHOT' : fullName + } + + static HibernateVersion fromFile(File file, Project project){ + def fullName = readVersionProperties(file) + return new HibernateVersion(fullName, project) + } + private static String readVersionProperties(File file) { + if ( !file.exists() ) { + throw new GradleException( "Version file $file.canonicalPath does not exists" ) + } + Properties versionProperties = new Properties() + file.withInputStream { + stream -> versionProperties.load( stream ) + } + return versionProperties.hibernateVersion + } + + @Override + String toString() { + return this.fullName + } +} //idea { // project { diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index a818b185b2153100100722ef32a8783a955b2258..100ad3774226dfff58b7303602fade223f812c95 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -7,15 +7,6 @@ repositories { mavenCentral() jcenter() - - maven { - name 'jboss-nexus' - url "http://repository.jboss.org/nexus/content/groups/public/" - } - maven { - name "jboss-snapshots" - url "http://snapshots.jboss.org/maven2/" - } } apply plugin: "groovy" @@ -27,5 +18,11 @@ dependencies { compile localGroovy() compile 'org.hibernate.build.gradle:gradle-animalSniffer-plugin:1.0.1.Final' - compile 'org.hibernate.build.gradle:hibernate-matrix-testing:2.0.0.Final' + compile 'org.hibernate.build.gradle:hibernate-matrix-testing:3.0.0.Final' +} + +tasks.withType( GroovyCompile ) { + options.encoding = 'UTF-8' + sourceCompatibility = 8 + targetCompatibility = 8 } diff --git a/changelog.txt b/changelog.txt index bd4c9e7eb7ce8258091f5251e0fef6f9d4d3f348..880a74c6176dff6cef94838947b4b0144fd93c7e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,691 @@ Hibernate 5 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 5.3.36.Final (February 08, 2024) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32245 + +** Bug + * [HHH-13965] - AssertionError exception is thrown by SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest when executing on Eclipse OpenJ9 VM AdoptOpenJDK (v. 11.0.6) + * [HHH-13912] - AssertionError exception is thrown by SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest when executing on IBM JDK 8 + + +Changes in 5.3.35.Final (January 22, 2024) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32244 + +** Bug + * [HHH-17380] - Persisting an entity with a non generated id and @MapsId throws PropertyValueException + + +Changes in 5.3.34.Final (December 08, 2023) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32227 + +** Bug + * [HHH-17532] - Load-collection fails with NPE when collection is part of embeddable + + +Changes in 5.3.33.Final (December 01, 2023) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32221 + +** Bug + * [HHH-13179] - Unionsubclass 2nd level caching no longer works for XML mappings in 5.3 and 5.4 + + +Changes in 5.3.32.Final (September 27, 2023) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32183 + +** Bug + * [HHH-17196] - Documentation for @NaturalId should be more explicit about non-nullability + * [HHH-14676] - PostgreSQLSkipAutoCommitTest ends up with a NPE on PostgreSQL Plus 13.1 + * [HHH-14669] - CriteriaLiteralWithSingleQuoteTest: SAP ASE16.0 does not support grouping by column heading or alias + +** Task + * [HHH-17197] - Add check for illegal combo of to-one + natural-id + not-found + + +Changes in 5.3.31.Final (August 07, 2023) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32159 + +** Bug + * [HHH-16586] - When merging a persisted entity with a null Version, Hibernate treats entity as transient instead of throwing an Exception + + +Changes in 5.3.30.Final (June 01, 2023) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32151 + +** Bug + * [HHH-16485] - Insert ordering doesn't consider root entity names + * [HHH-15602] - ByteBuddy enhancement generates faulty code with many-to-many associations + + +Changes in 5.3.29.Final (March 29, 2023) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32098 + +** Bug + * [HHH-14514] - Auto evict collection cache not work when use transactional cache concurrency strategy + * [HHH-13627] - Updated items do not get invalidated when cachemode is set to CacheMode.GET + + +Changes in 5.3.28.Final (August 15, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32092 + +** Bug + * [HHH-15425] - org.hibernate.QueryException: could not resolve property is thrown when Hibernate criteria tries to select the id of an association annotated with @NotFound + + +Changes in 5.3.27.Final (June 13, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32064 + +** Bug + * [HHH-14843] - Enhanced embeddable identifiers need to be serializable + * [HHH-14768] - hibernate-jpamodelgen fails to generate metamodel for recursive type variable definition + * [HHH-14724] - Metamodel generates invalid model classes for converters and user types + * [HHH-12338] - Incorrect metamodel for basic collections + +** Task + * [HHH-13948] - EnhancedSetterImpl should define writeReplace + + +Changes in 5.3.26.Final (April 08, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32011 + +** Bug + * [HHH-14649] - Oracle limit handler create wrong sql query when multiple spaces are present in the query + +** Improvement + * [HHH-15106] - fk() SQM function + * [HHH-14624] - Oracle from version 12 started supporting the `offset ? rows fetch next ? rows only`syntax for pagination + +** Task + * [HHH-15026] - Upgrade to Log4J 2.17.1 + * [HHH-14987] - Upgrade to Log4j 2.17.0 + + +Changes in 5.3.25.Final (December 15, 2021) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32002 + +** Bug + * [HHH-14972] - log4j2 <= 2.14.1 has an RCE (CVE-2021-44228) + * [HHH-14229] - Foreign key is created even ConstraintMode.NO_CONSTRAINT specified + +** Task + * [HHH-14979] - Upgrade to Log4J 2 2.16.0 + * [HHH-14635] - Upgrade to latest JUnit and to Log4j 2 + + +Changes in 5.3.24.Final (November 16, 2021) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31988 + +** Bug + * [HHH-14540] - Interceptor instance is shared between ORM session and Enver's temporary session resulting in multiple calls. + + +Changes in 5.3.23.Final (September 29, 2021) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31973 + +** Bug + * [HHH-14840] - IBM Db2 11.1 fails on TransientOverride test cases + * [HHH-14796] - Cannot replace an existing JPQL NamedQuery with a native NamedQuery + * [HHH-14790] - Fix Gradle build failure with JDK 11 + +** Deprecation + * [HHH-14847] - Deprecate JMX integration + * [HHH-14845] - Deprecate JACC integration + + +Changes in 5.3.22.Final (August 20, 2021) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31962 + +** Task + * [HHH-14788] - Upgrade to Byteman 4.0.16 + * [HHH-14787] - Upgrade ORM 5.3 to Javassist 3.27.0-GA + * [HHH-14786] - Upgrade ORM 5.3 to Weld 3.1.8.Final + * [HHH-14785] - Upgrade ORM 5.3 to Hibernate Validator 6.0.22.Final + * [HHH-14784] - Upgrade ORM 5.3 to Hibernate Commons Annotations 5.0.5.Final + * [HHH-14783] - Upgrade Hibernate ORM 5.3 to Joda Time 2.9.7 + * [HHH-14782] - Upgrade Hibernate ORM 5.3 to Classmate 1.5.1 + * [HHH-14781] - Upgrade to JBoss Logging 3.4.2.Final + * [HHH-14771] - Upgrade to Byte Buddy 1.11.12 + * [HHH-14709] - Upgrade to Gradle 6.7.1 and move to Gradle's built-in way of testing Java modules + * [HHH-14372] - Upgrade to Weld 3.1.5.Final in tests + * [HHH-14371] - Add --add-opens options required for tests + * [HHH-14370] - Add --add-opens options required for Gradle + * [HHH-13821] - Update to Byte Buddy 1.10.7 + + +Changes in 5.3.21.Final (July 16, 2021) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31897 + +** Bug + * [HHH-14616] - Optimistic Lock throws org.hibernate.exception.SQLGrammarException: could not retrieve version + * [HHH-14608] - Merge causes StackOverflow when JPA proxy compliance is enabled + * [HHH-14537] - EntityNotFoundException thrown when non-existing association with @NotFound(IGNORE) mapped has proxy in PersistenceContext + * [HHH-14247] - Automatic release scripts, wrong Jira release url + * [HHH-14199] - setDataBase gradle task failed + * [HHH-14030] - hibernate-orm fails to build locally on German Win10 + * [HHH-13875] - Optional one-to-one does not always join the associated entity table when querying + * [HHH-12842] - Non-optional OneToOne relation can't be lazy loaded anymore + * [HHH-12436] - Attempted to assign id from null one-to-one property + * [HHH-12320] - @JoinColumn's foreign key custom name does not work with @MapsId + +** Improvement + * [HHH-14083] - Gradle, add task to automate the CI release process + +** Task + * [HHH-14697] - TimePropertyTest fails on MySQL 8.0 with a ComparisonFailure + * [HHH-14640] - Backport support for automating releases that push to Sonatype to 5.3 branch (includes Gradle upgrade) + * [HHH-14513] - Move publishing release artifacts from BinTray + * [HHH-14315] - Upgrade to Gradle 6.7 and use toolchains for per-JDK builds + * [HHH-14283] - Review tuning of JVM parameters for the build + * [HHH-14144] - Explicitly set localization assumptions for the build and testsuite + * [HHH-14111] - Upgrade build dependencies to test JDK15/JDK16 + * [HHH-13925] - Upgrade to Gradle 6.3 + * [HHH-13689] - Replace uses of the deprecated osgi plugin with the biz.aQute.bnd plugin + * [HHH-13685] - Upgrade to Gradle 5 + * [HHH-13682] - Generate Java 13/14 bytecode for tests when building with JDK13/14 + + +Changes in 5.3.20.Final (November 16th, 2020) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31894/tab/release-report-all-issues + +** Bug + * [HHH-14257] - An Entity A with a map collection having as index an Embeddable with a an association to the Entity A fails with a NPE + +** Task + * [HHH-14225] - CVE-2020-25638 Potential for SQL injection on use_sql_comments logging enabled + * [HHH-14324] - Add .gradletasknamecache to .gitignore + +** Improvement + * [HHH-14325] - Add Query hint for specifying "query spaces" for native queries + + +Changes in 5.3.19.Final (November 10th, 2020) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31874/tab/release-report-all-issues + +** Bug + * [HHH-13310] - getParameterValue() not working for collections + * [HHH-14275] - Broken link to Infinispan User Guide in Hibernate 5.3 User Guide + +** Task + * [HHH-14309] - Improve `BulkOperationCleanupAction#affectedEntity` + +** Sub-task + * [HHH-14196] - Add parsing of persistence.xml/orm.xml documents in the EE 9 namespace + + +Changes in 5.3.18.Final (August 5th, 2020) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31849/tab/release-report-all-issues + +** Bug + * [HHH-12268] - LazyInitializationException thrown from lazy collection when batch fetching enabled and owning entity refreshed with lock + * [HHH-13110] - @PreUpdate method on a Embeddable null on the parent caused NullPointerException + * [HHH-13936] - No auto transaction joining from SessionImpl.doFlush + * [HHH-14077] - CVE-2019-14900 SQL injection issue using JPA Criteria API + +** Task + * [HHH-14013] - Upgrade to Hibernate Validator 6.0.20.Final + * [HHH-14096] - Removal of unused code: XMLHelper and its SAXReader factory helper + * [HHH-14103] - Add test cases showing that an entity's transient attribute can be overridden to be persistent in entity subclasses + +Changes in 5.3.17.Final (April 30th, 2020) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31835/tab/release-report-all-issues + +** Bug + * [HHH-13695] - DDL export forgets to close a Statement + +** Task + * [HHH-13953] - Upgrade dom4j to 2.1.3 + +** Improvement + * [HHH-13960] - Add SAXReader sec features to match the defaults + +Changes in 5.3.16.Final (March 27th, 2020) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31822/tab/release-report-all-issues + +** Bug + * [HHH-13184] - Oracle dialect detection does not return latest dialect in the default case + * [HHH-13891] - ProxyFactory should not be built if any ID or property getter/setter methods are final + * [HHH-13910] - MySQL57Dialect selected by automatic dialect resolution when using MySQL 8.0 database + +** Task + * [HHH-13822] - OSGi integration tests need to be able to download dependencies from Maven Central using HTTPS + +** Improvement + * [HHH-12977] - Update latest dialect for MySQL + * [HHH-13851] - Rework initialization of ProxyFactoryFactory to move responsibility out of PojoEntityTuplizer + +Changes in 5.3.15.Final (January 7th, 2020) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31809/tab/release-report-all-issues + +** Bug + * [HHH-13433] - EntityManager.find() should only check for roll-back-only condition if there is an active JTA transaction, otherwise ORM should throw convert( e, lockOptions ) + * [HHH-13651] - NPE on flushing when ElementCollection field contains null element + * [HHH-13675] - Optimize PersistentBag.groupByEqualityHash() + * [HHH-13737] - Add debug logging and a test case for HHH-13433 + +** Improvement + * [HHH-12858] - integration overrides during JPA bootstrap ought to override all logically related settings + * [HHH-13432] - Have EntityManagerFactory expose persistence.xml `jta-data-source` element as a `javax.persistence.nonJtaDataSource` property + +Changes in 5.3.14.Final (November 7th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31801/tab/release-report-all-issues + +** Bug + * [HHH-13307] - On release of batch it still contained JDBC statements using JTA + * [HHH-13633] - Bugs join-fetching a collection when scrolling with a stateless session using enhancement as proxy + * [HHH-13634] - PersistenceContext can get cleared before load completes using StatelessSessionImpl + * [HHH-13640] - Uninitialized HibernateProxy mapped as NO_PROXY gets initialized when reloaded with enhancement-as-proxy enabled + * [HHH-13653] - Uninitialized entity does not get initialized when a setter is called with enhancement-as-proxy enabled + * [HHH-13698] - Hibernate does not recognize MySQL 8 error code 3572 as PessimisticLockException + +Changes in 5.3.13.Final (October 8th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31792/tab/release-report-all-issues + +** Bug + * [HHH-13586] - ClassCastException when using a single region name for both entity and query results + * [HHH-13645] - StatsNamedContainer#getOrCompute throws NullPointerException when computed value is null + +** Improvement + * [HHH-13130] - Provide Gradle-based bytecode enhancement as a task separate from the compileJava task + +Changes in 5.3.12.Final (September 11th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31784/tab/release-report-all-issues + +** Bug + * [HHH-12968] - Flush is not flushing inserts for inherited tables before a select within a transaction + * [HHH-12990] - JPA Model generator does not work in Java 9+ + * [HHH-13128] - Missing jaxb-runtime dependency for hibernate-jpamodelgen + * [HHH-13580] - LocalTimeTest#writeThenNativeRead* and OffsetTimeTest#writeThenNativeRead* failing on MySQL + * [HHH-13581] - LocalTimeTest#writeThenRead* and OffsetTimeTest#writeThenRead* failing on MariaDB + * [HHH-13582] - LocalDateTest failures on MySQL + * [HHH-13590] - TransientObjectException merging a non-proxy association to a HibernateProxy + * [HHH-13592] - AutoFlushEvent#isFlushRequired is always false + * [HHH-13607] - Exception thrown while flushing uninitialized enhanced proxy with immutable natural ID + * [HHH-13611] - Restore EntityMetamodel constructor to take SessionFactoryImplementor argument instead of PersisterCreationContext. + * [HHH-13616] - Enable the hibernate-orm-modules test for JDK 11 + +** Task + * [HHH-13007] - No longer use net.bytebuddy.experimental=true when testing on JDK11 + * [HHH-13043] - Upgrade to JAXB 2.3 + * [HHH-13271] - Javadoc build failures on JDK 12 + * [HHH-13275] - Re-introduce usage of net.bytebuddy.experimental=true when testing on JDK > 11 + * [HHH-13415] - Improve build compatibility with JDK11.0.3 + * [HHH-13419] - Support building javadoc with JDK 11.0.3 + * [HHH-13421] - Disable OSGi testing for JDK 11+ + * [HHH-13504] - Upgrade ByteBuddy to 1.9.11 + * [HHH-13605] - InstantTest, OffsetDateTimeTest, ZonedDateTimeTest fail for MariaDB on CI + +** Improvement + * [HHH-12946] - Include JAXB as a dependency as it's not provided by JDK 11 + * [HHH-13022] - Make OSGi integration work on JDK11 + * [HHH-13069] - Update the links to JBoss Nexus to use the direct repository over https + * [HHH-13127] - Document JAXB dependencies should be added for using hibernate-jpamodelgen in Eclipse IDE + * [HHH-13428] - Minor cleanup of build scripts + + +Changes in 5.3.11.Final (August 15th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31770/tab/release-report-all-issues + +** Bug + * [HHH-13357] - OffsetTimeTest fails using TimeAsTimestampRemappingH2Dialect in non-GMT European time zones + * [HHH-13379] - Regression of Instant serialization + * [HHH-13424] - Table nullability should not depend on JpaCompliance.isJpaCacheComplianceEnabled() + * [HHH-13455] - Enabling Enhancement as a Proxy causes IllegalStateException when using Javassist + * [HHH-13459] - Unit test lock up when they run on PostgreSQL + * [HHH-13460] - FetchGraphTest is failing on MariaDB + * [HHH-13466] - ClassCastException when changing a Collection association to a Set if @PreUpdate listener exists + * [HHH-13492] - OptimisticLockException after locking, refreshing, and updating an entity + * [HHH-13505] - NullPointerException thrown by StatisticsImpl#getCacheRegionStatistics + * [HHH-13514] - Calling the wrong method inside SessionDelegatorBaseImpl#createStoredProcedureQuery + * [HHH-13544] - Restore logged warning on jdbc code mapping issue in NationalizedTypeMappings + * [HHH-13550] - Fix Oracle failure for test added by HHH-13424 + * [HHH-13554] - QueryAndSQLTest.testNativeQueryWithFormulaAttributeWithoutAlias() fails on Oracle, MSSQL, Sybase, DB2, MariaDB + * [HHH-13555] - FetchGraphTest, MergeProxyTest and ProxyDeletionTest fail due to ConstraintViolationException + * [HHH-13556] - Tests doing dynamic fetch scrolling a collection fail on DB2 + * [HHH-13557] - LocalTimeTest#writeThenNativeRead and OffsetTimeTest#writeThenNativeRead tests are failing on SQL Server + * [HHH-13558] - InstantTest, LocalDateTimeTest, OffsetDateTimeTest, ZonedDateTimeTest failing on Sybase for year 1600 + * [HHH-13569] - org.hibernate.test.annotations.embedded.EmbeddedTest failures on Sybase + * [HHH-13570] - Test failures due to Sybase not supporting UPDATE statement with WITH(NOWAIT) + * [HHH-13571] - Test failures due to cross joined table out of scope of a subsequent JOIN on Sybase + * [HHH-13573] - Test failure due to Sybase not supporting cascade delete on foreign key definitions + * [HHH-13574] - SybaseASE does not support PARTITION BY + * [HHH-13577] - LockTest.testContendedPessimisticLock and StatementIsClosedAfterALockExceptionTest.testStatementIsClosed tests fail on Sybase + +** New Feature + * [HHH-11147] - Allow enhanced entities to be returned in a completely uninitialized state + +** Task + * [HHH-13026] - Documentation: fixing link to Infinispan documentation section regarding Hibernate 2LC + * [HHH-13416] - Unguarded debug message being rendered in org.hibernate.engine.internal.Collections.processReachableCollection + * [HHH-13513] - Partial revert of string interning introduced by HHH-3924 + * [HHH-13520] - Deprecate mutators on SqlStatementLogger + * [HHH-13525] - Make test SessionDelegatorBaseImplTest more resilient to previously existing alias definition + * [HHH-13526] - Optimise ResourceRegistryStandardImpl#release + * [HHH-13527] - Performance regression in org.hibernate.stat.internal.StatisticsImpl + * [HHH-13528] - Invoke afterStatements only at the end of releasing all statements for a batch + * [HHH-13529] - Performance regression in org.hibernate.engine.spi.SessionFactoryImplementor#getDialect + * [HHH-13531] - Some more opportunities to reuse the constants pool in AliasConstantsHelper + * [HHH-13534] - AbstractLoadPlanBasedLoader never needs a List of AfterLoadAction + +** Improvement + * [HHH-11032] - Improve performance of PersistentBag.equalsSnapshot + * [HHH-13442] - CollectionType#getCollection() method improvements + * [HHH-13444] - Remove ignored EntityMode field from CollectionKey + * [HHH-13447] - Minimize number of EventListenerRegistry lookups within a Session use + * [HHH-13448] - Avoid retrieving PRE_LOAD and POST_LOAD Event listeners within the inner loops of TwoPhaseLoad + * [HHH-13450] - Do not compute the full role name of a collection unless necessary + * [HHH-13451] - Logging typo in CascadingActions causing significant allocations + * [HHH-13452] - Missing log level guard on formatting in DefaultPersistEventListener#entityIsDeleted + * [HHH-13453] - Optimise CascadingActions for the most likely case + * [HHH-13458] - Update Hibernate's custom IdentityMap to better match its use + * [HHH-13462] - Introduce a fastpath for SessionImpl#fireLoad to be used by internal loops + * [HHH-13467] - Make average BatchFetchQueue consume less memory + * [HHH-13471] - Avoid invoking delayedAfterCompletion() multiple times from the same SessionImpl method + * [HHH-13475] - SessionImpl#applyQuerySettingsAndHints should not rely on defensive copies to just read properties + * [HHH-13476] - Micro-optimisations of TwoPhaseLoad#getOverridingEager + * [HHH-13477] - Make heavily invoked method final: EventListenerGroupImpl#listeners() + * [HHH-13478] - Various low hanging fruits identified by CPU flame graphs + * [HHH-13494] - LobTypeMappings should not use a Bounded ConcurrentHashmap + * [HHH-13495] - NationalizedTypeMappings should not use a Bounded ConcurrentHashmap + * [HHH-13508] - Reuse alias names generated by BasicLoader#generateSuffixes + * [HHH-13511] - Remove old org.hibernate.loader.DefaultEntityAliases#intern + * [HHH-13512] - Avoid allocating an array in org.hibernate.internal.util.StringHelper#unquote(String[], Dialect) if there are no changes to be applied + * [HHH-13521] - Avoid excessive validation of enabled filters + * [HHH-13522] - Optimise LoadQueryInfluencers by making maps lazily initialized + * [HHH-13523] - StatementPreparerImpl should not need to retrieve the JDBCService as often + * [HHH-13524] - Remove unused fields xref,unassociatedResultSets from JdbcCoordinatorImpl + + + +Changes in 5.3.10.final (April 19th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31759/tab/release-report-done + +** Bug + * [HHH-12939] - Database name not quoted at schema update + * [HHH-13138] - Work around class loading issues so that bytecode enhanced tests can run as expected + * [HHH-13241] - Constraint violation when deleting entites in bi-directional, lazy OneToMany association with bytecode enhancement + * [HHH-13266] - LocalDateTime values are wrong around 1900 (caused by JDK-8061577) + * [HHH-13277] - HibernateMethodLookupDispatcher - Issue with Security Manager + * [HHH-13300] - query.getSingleResult() throws org.hibernate.NonUniqueResultException instead of javax.persistence.NonUniqueResultException + * [HHH-13326] - Transaction passed to Hibernate Interceptor methods is null when JTA is used + * [HHH-13343] - Bytecode enhancement using ByteBuddy fails when the class is not available from the provided ClassLoader + * [HHH-13364] - Query.getSingleResult and getResultList() throw PessimisticLockException when pessimistic lock fails with timeout + +** Task + * [HHH-13376] - Upgrade Javassist dependency to 3.23.2-GA + + + +Changes in 5.3.9.final (February 25th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31757/tab/release-report-done + +** Bug + * [HHH-13107] - JtaWithStatementsBatchTest fails on Oracle + * [HHH-13112] - Proxies on entity types in the default package lead to MappingException with JDK9+ + * [HHH-13262] - javax.persistence.TransactionRequiredException: Executing an update/delete query + * [HHH-13269] - Embeddable collection regression due to HHH-11544 + * [HHH-13281] - java.lang.ClassCastException: org.hibernate.internal.SessionImpl cannot be cast to org.hibernate.ejb.HibernateEntityManager + * [HHH-13285] - ClassCastException: org.dom4j.DocumentFactory cannot be cast to org.dom4j.DocumentFactory after dom4j update + + + +Changes in 5.3.8.final (February 19th, 2019) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31715/tab/release-report-done + +** Bug + * [HHH-10891] - Exception at bootstrap when @Any is inside an @Embeddable object + * [HHH-11209] - NullPointerException in EntityType.replace() with a PersistentBag + * [HHH-12555] - Merging a blob on an entity results in a class cast exception + * [HHH-13050] - On release of batch it still contained JDBC statements logged; unable to release batch statement + * [HHH-13059] - OneToMany with referencedColumnName returns too many entities + * [HHH-13064] - Documentation of Lock and LockModeType is on two columns instead of 3 + * [HHH-13076] - Hibernate “Transaction already active” behaviour with custom transaction manager + * [HHH-13084] - Querying entity with non-ID property named 'id' fails if entity has an IdClass composite key + * [HHH-13097] - Hibernate enhancer is superslow after upgrade to latest 5.3 or 5.4-SNAPSHOT + * [HHH-13114] - Query "select count(h) from Human h" fails if a subclass has a non-Id property named "id" + * [HHH-13129] - Cascaded merge fails for detached bytecode-enhanced entity with uninitialized ToOne + * [HHH-13164] - Detecting transient state of mandatory toOne relations is broken + * [HHH-13169] - Table alias used instead of exact table name in multitable update query + * [HHH-13172] - Log a warning instead of throwing an Exception when @AttributeOverride is used in conjunction with inheritance + * [HHH-13194] - Some methods returning org.hibernate.query.Query are not defined for StatelessSession + * [HHH-13244] - setting hibernate.jpa.compliance.proxy=true and org.hibernate debug level to DEBUG breaks hibernate + +** Task + * [HHH-13099] - Update to Byte Buddy 1.9.4 + * [HHH-13100] - All custom implementation of Byte Buddy "Implementation" s should have a proper equals and hashcode + +** Improvement + * [HHH-12917] - Interning of strings for Filter definitions + * [HHH-12918] - Interning of strings for Formula and Column exctraction templates + * [HHH-12919] - Interning of strings for EntityReferenceAliases + * [HHH-13005] - Upgrade ByteBuddy to 1.9.0 + * [HHH-13057] - Prevent Byte Buddy's Advice helper to need reloading many resources from the ClassLoader + * [HHH-13220] - In the ByteBuddy enhancer, avoid creating a PersistentAttributeTransformer if the class is not enhanced + + + +Changes in 5.3.7.final (October 16th, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31714/tab/release-report-done + +** Bug + * [HHH-12784] - Javassist support broken by HHH-12760 + * [HHH-12920] - AbstractCachedDomainDataAccess.clearCache() throws MissingFormatArgumentException at DEBUG level + * [HHH-12934] - Exception handling documentation does not apply only to "Session-per-application anti-pattern" + * [HHH-12935] - Constraint and AuxiliaryDatabaseObject export identifiers are not qualified by schema or catalog + * [HHH-12937] - Where clause for collections of basic, embeddable and "any" elements is ignored when mapped using hbm.xml + * [HHH-12964] - Upgrade to dom4j 2.1.1 + * [HHH-13027] - org.hibernate.ejb.HibernatePersistence can no longer be used as a persistence provider name + +** Improvement + * [HHH-12961] - The links in the Javadoc of the SAP HANA dialects don't work + * [HHH-13011] - Add option enabling/disabling use of an entity's mapped where-clause when loading collections of that entity + + + +Changes in 5.3.6.final (August 28th, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31704/tab/release-report-done + +** Bug + * [HHH-12931] - Revert HHH-12542 as it introduces some issues with the security manager + * [HHH-12932] - Add privileged blocks in ByteBuddyState initialization + + + +Changes in 5.3.5.final (August 14th, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31695/tab/release-report-done + +** Bug + * [HHH-12871] - Metamodel contains managed types related to dynamic-map entities that have been excluded. + * [HHH-12875] - Class level where="..." clause in hbm.xml mappings is not enforced on collections of that class + * [HHH-12882] - Where clauses mapped on collections and entities need parentheses when used in conjunction + * [HHH-12890] - Fix link to JPA Metamodel generator documentation + * [HHH-12903] - CommitFlushCollectionTest fails when running on Oracle. + * [HHH-12905] - Passing null as parameter is not allowed even when enablePassingNulls() has been called + * [HHH-12906] - Statistics.getCollectionRoleNames() reports incorrect value + +** Task + * [HHH-10782] - Add a comment about what you can expect from a query plan cache cleanup + * [HHH-12898] - Enable integration tests for Oracle Standard Edition Two 12.1.0.2.v12 on the AWS build slaves + * [HHH-12899] - Enable integration tests for MS SQL Server on the AWS build slaves + * [HHH-12901] - Enable loading of additional JDBC drivers from a local path + * [HHH-12909] - Upgrade ByteBuddy to 1.8.17 + +** Improvement + * [HHH-12196] - Sybase Dialect not supporting max result - paging + * [HHH-12361] - In the User Guide, omit constructors and equals/hashCode for brevity + * [HHH-12608] - Add the ST_DWithin() function in DB2 Spatial Dialect + * [HHH-12892] - Fix spelling issues in the User Guide + * [HHH-12907] - Avoid garbage collection pressure when creating proxies with ByteBuddy + + + +Changes in 5.3.4.final (August 2nd, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31688/tab/release-report-done + +** Bug + * [HHH-10603] - ORA-00932: inconsistent datatypes: expected - got BLOB after HHH-10345 with Oracle12cDialect + * [HHH-12492] - JPA delete query generated has missing table alias and thus incorrect semantics + * [HHH-12834] - org.hibernate.envers.test.integration.collection.StringMapNationalizedLobTest fails with Sybase + * [HHH-12835] - Wrong assertion in BatchFetchQueueHelper + * [HHH-12846] - Merge cascade of collection fails when orphan removal enabled with flush mode commit + * [HHH-12847] - NullPointerException in FetchStyleLoadPlanBuildingAssociationVisitationStrategy::adjustJoinFetchIfNeeded + * [HHH-12848] - UpgradeSkipLockedTest, PessimisticReadSkipLockedTest and OracleFollowOnLockingTest fail with Oracle12c + * [HHH-12849] - QuotedIdentifierTest fails with ORA-04043 on Oracle12c + * [HHH-12851] - ConverterTest fails with SQL Server depending on collation + * [HHH-12861] - SchemaUpdate doesn't work with Sybase + * [HHH-12863] - SchemaUpdateTest should be skipped with Sybase + * [HHH-12868] - Using CacheConcurrencyStrategy.NONE leads to a NPE when trying to load an entity + * [HHH-12869] - SingletonEhcacheRegionFactory initialization fails + * [HHH-12880] - LockModeTest hangs indefinitely with Sybase due to HHH-12847 + +** New Feature + * [HHH-12857] - Support the security manager with ByteBuddy as bytecode provider + +** Task + * [HHH-12730] - User types built using 5.1 are not binary compatible with 5.3 + * [HHH-12792] - Document binary incompatibility of persisters and tuplizers + * [HHH-12877] - Upgrade ByteBuddy to 1.8.15 + + + +Changes in 5.3.3.final (July 23, 2018) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/31687/tab/release-report-done + +** Bug + * [HHH-7686] - org.hibernate.proxy.map.MapProxy loses all important state on serialization + * [HHH-8805] - [SchemaUpdate] javax.persistence.ForeignKey doesn't respect ConstraintMode.NO_CONSTRAINT + * [HHH-12200] - Docs mention outdated APIs + * [HHH-12542] - WildFly integration test, HibernateNativeAPINaturalIdTestCase, fails when security manager is enabled + * [HHH-12666] - Add an option for restoring 5.1 native exception handling + * [HHH-12695] - Incompatibility in return value for org.hibernate.procedure.ParameterRegistration.getType() 5.1 vs 5.3 + * [HHH-12718] - Entity changes in @PreUpdate callback are not persisted when lazy loading is active for more than one field + * [HHH-12720] - LazyInitializationException with hibernate.enable_lazy_load_no_trans + * [HHH-12740] - Subselect fetching doesn't work when multiLoad was used + * [HHH-12753] - org.hibernate.envers.test.integration.collection.StringMapNationalizedLobTest fails with DB2 + * [HHH-12768] - TimeAndTimestampTest fails with SQL Server and MYSQL + * [HHH-12771] - Caused by: java.lang.UnsupportedOperationException: Cache provider [org.hibernate.cache.ehcache.internal.EhcacheRegionFactory@3271ec2a] does not support `transactional` access + * [HHH-12776] - NullPointerException when executing native query on an Audited Entity + * [HHH-12779] - Revert HHH-12670 - Allows native SQL queries that take a given resultClass to map the result set to the required type + * [HHH-12781] - Update Javassist dependency to 3.23.1 + * [HHH-12786] - Deleting an entity leads to NullPointerException in ByteBuddy proxy + * [HHH-12787] - SessionJdbcBatchTest hangs with DB2 + * [HHH-12791] - ComponentTuplizer generates a LOT of proxy classes when using Bytebuddy as bytecode provider + * [HHH-12795] - Setting FlushMode to manual for a @NamedQuery is ignored + * [HHH-12797] - Fix cache modes relationships table layout in the documentation + * [HHH-12798] - Nested spatial functions are not rendered correctly on SAP HANA + * [HHH-12800] - TuplizerInstantiatesByteBuddySubclassTest uses ByteBuddy operation unsafe with JDK 11 + * [HHH-12802] - Hibernate does not throw an exception when more than one entity is loaded with the same ID + * [HHH-12815] - LocalDateCustomSessionLevelTimeZoneTest fails with mysql 5.5 and 5.7 + * [HHH-12822] - Skip "case when" tests requiring casts for DB2 + * [HHH-12823] - CompositeIdTest.testDistinctCountOfEntityWithCompositeId fails on databases that don't support tuple distinct counts because it expects wrong exception + * [HHH-12824] - ASTParserLoadingTest.testComponentNullnessChecks fail with DB2 because it uses legacy-style query parameter + * [HHH-12825] - CriteriaHQLAlignmentTest.testCountReturnValues fails on databases that don't support tuple distinct counts because it expects wrong exception + * [HHH-12826] - Persist cascade of collection fails when orphan removal enabled with flush mode commit. + * [HHH-12827] - NUMERIC column type is not handled correctly on DB2 + * [HHH-12829] - Invalid references to outdated EhCache classes + * [HHH-12832] - SchemaUpdateHaltOnErrorTest and SchemaMigratorHaltOnErrorTest fail with DB2 + * [HHH-12833] - UniqueConstraintDropTest fails with DB2 + * [HHH-12838] - AndNationalizedTests fails with DB2 + * [HHH-12839] - EntityProxySerializationTest fails with oracle + * [HHH-12843] - CreateDeleteTest and FlushIdGenTest fail with ORA-00936 on oracle + * [HHH-12844] - HbmWithIdentityTest fails with ORA-00936 on oracle + +** Task + * [HHH-12742] - Document the removal of JPAIntegrator SPI + * [HHH-12773] - Document org.hibernate.Query.getHibernateFirstResult(), setHibernateFirstResult(), getHibernateMaxResults(), setHibernateMaxResults() in migration guide + * [HHH-12774] - JARs missing from the distribution ZIP + * [HHH-12785] - Test Javassist support + * [HHH-12788] - Enable mockito-inline for the Agroal integration module + * [HHH-12789] - Upgrade to Mockito 2.19.0 + * [HHH-12793] - Upgrade Karaf, pax-exam and reenable the OSGi tests + * [HHH-12799] - Enforce version alignment of Mockito and ByteBuddy dependencies + * [HHH-12801] - Error message in SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest differs with JDK 11 + * [HHH-12803] - Upgrade ByteBuddy to 1.8.13 + * [HHH-12805] - Upgrade Mockito to 2.19.1 + * [HHH-12807] - Disable the hibernate-orm-modules tests for JDK 11 + * [HHH-12808] - Upgrade Gradle to 4.8.1 + * [HHH-12809] - Use an HTTP link for the Javadoc link to our Bean Validation documentation + * [HHH-12813] - Disable Asciidoclet in Javadoc generation + * [HHH-12816] - Enable the experimental features of ByteBuddy when building with JDK 11 + * [HHH-12820] - Merge the migration guides in the code base + * [HHH-12828] - ScannerTests#testGetBytesFromInputStream() is not stable enough + +** Improvement + * [HHH-12349] - User Guide documentation for @Filter is too verbose + * [HHH-12778] - BasicProxyFactoryImpl.getProxy() swallows exception + * [HHH-12804] - No need to mock Map in CollectionBinderTest + * [HHH-12811] - @UpdateTimestamp and @CreationTimestamp missing @Target annotation and breaking in Kotlin + * [HHH-12830] - Improve error output with transaction issues + + + Changes in 5.3.2.final (July 5, 2018) ------------------------------------------------------------------------------------------------------------------------ diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile new file mode 100644 index 0000000000000000000000000000000000000000..9883c59457eb175806770c39493023f2f04a6633 --- /dev/null +++ b/ci/snapshot-publish.Jenkinsfile @@ -0,0 +1,51 @@ +/* + * See https://github.com/hibernate/hibernate-jenkins-pipeline-helpers + */ +@Library('hibernate-jenkins-pipeline-helpers@1.5') _ + +// Avoid running the pipeline on branch indexing +if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { + print "INFO: Build skipped due to trigger being Branch Indexing" + currentBuild.result = 'ABORTED' + return +} + +pipeline { + agent { + label 'Fedora' + } + tools { + jdk 'OpenJDK 8 Latest' + } + options { + rateLimitBuilds(throttle: [count: 1, durationName: 'hour', userBoost: true]) + buildDiscarder(logRotator(numToKeepStr: '3', artifactNumToKeepStr: '3')) + disableConcurrentBuilds(abortPrevious: true) + } + stages { + stage('Checkout') { + steps { + checkout scm + } + } + stage('Publish') { + steps { + configFileProvider([ + configFile(fileId: 'ci-hibernate.deploy.settings.maven', variable: 'MAVEN_SETTINGS_PATH') + ]) { + sh '''./gradlew clean publishPublishedArtifactsPublicationToJboss-snapshots-repositoryRepository \ + -Dmaven.settings=$MAVEN_SETTINGS_PATH \ + --no-scan + ''' + } + } + } + } + post { + always { + configFileProvider([configFile(fileId: 'job-configuration.yaml', variable: 'JOB_CONFIGURATION_FILE')]) { + notifyBuildResult maintainers: (String) readYaml(file: env.JOB_CONFIGURATION_FILE).notification?.email?.recipients + } + } + } +} \ No newline at end of file diff --git a/databases/mariadb/matrix.gradle b/databases/mariadb/matrix.gradle index 7ebdee14585bc221246bd02c5211db3c344e624e..39b03e208f257ee4e94f1d769affbf21e8b9e66b 100644 --- a/databases/mariadb/matrix.gradle +++ b/databases/mariadb/matrix.gradle @@ -4,4 +4,4 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -jdbcDependency 'org.mariadb.jdbc:mariadb-java-client:1.5.7' \ No newline at end of file +jdbcDependency 'org.mariadb.jdbc:mariadb-java-client:2.2.4' \ No newline at end of file diff --git a/databases/mssqlserver/matrix.gradle b/databases/mssqlserver/matrix.gradle new file mode 100644 index 0000000000000000000000000000000000000000..9e763e239a6b405cc0c43baaf1ef176544231348 --- /dev/null +++ b/databases/mssqlserver/matrix.gradle @@ -0,0 +1,14 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +jdbcDependency 'com.microsoft.sqlserver:mssql-jdbc:6.4.0.jre8' \ No newline at end of file diff --git a/databases/mssqlserver/resources/hibernate.properties b/databases/mssqlserver/resources/hibernate.properties new file mode 100644 index 0000000000000000000000000000000000000000..7f582bf0a02b3fc60962965c8b1fd8f631925291 --- /dev/null +++ b/databases/mssqlserver/resources/hibernate.properties @@ -0,0 +1,33 @@ +# +# Hibernate, Relational Persistence for Idiomatic Java +# +# License: GNU Lesser General Public License (LGPL), version 2.1 or later. +# See the lgpl.txt file in the root directory or . +# + +# +# Hibernate, Relational Persistence for Idiomatic Java +# +# License: GNU Lesser General Public License (LGPL), version 2.1 or later. +# See the lgpl.txt file in the root directory or . +# + +hibernate.dialect org.hibernate.dialect.SQLServer2012Dialect +hibernate.connection.driver_class com.microsoft.sqlserver.jdbc.SQLServerDriver +hibernate.connection.url jdbc:sqlserver://hibernate-testing-mssql-express.ccuzkqo3zqzq.us-east-1.rds.amazonaws.com +hibernate.connection.username hibernate_orm_test +hibernate.connection.password hibernate_orm_test + +hibernate.connection.pool_size 5 + +hibernate.show_sql false +hibernate.format_sql true + +hibernate.max_fetch_depth 5 + +hibernate.cache.region_prefix hibernate.test +hibernate.cache.region.factory_class org.hibernate.testing.cache.CachingRegionFactory + +javax.persistence.validation.mode=NONE +hibernate.service.allow_crawling=false +hibernate.session.events.log=true \ No newline at end of file diff --git a/databases/oracle/matrix.gradle b/databases/oracle/matrix.gradle index b4a64b9e80a156d4a8b8e4afba19f17d3995d172..2355701ef41cef0e25d85f0f6ceb0857f78c0bc2 100644 --- a/databases/oracle/matrix.gradle +++ b/databases/oracle/matrix.gradle @@ -4,4 +4,6 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -jdbcDependency 'com.oracle.ojdbc:ojdbc7:12.1.0.2.0' +// Expected to match the jar name: drop a copy of ojdbc8.jar in a local directory, +// then point the environment variable ADDITIONAL_REPO to that directory. +jdbcDependency name : 'ojdbc8' diff --git a/databases/oracle/resources/hibernate.properties b/databases/oracle/resources/hibernate.properties index eeb6db8d9dddb3f3ab4ebbc08e93ae069e099b84..05f554eec3949d479dc80c998c14189215c4e021 100644 --- a/databases/oracle/resources/hibernate.properties +++ b/databases/oracle/resources/hibernate.properties @@ -7,8 +7,9 @@ hibernate.dialect org.hibernate.dialect.Oracle12cDialect hibernate.connection.driver_class oracle.jdbc.driver.OracleDriver -hibernate.connection.url jdbc:oracle:thin:@orm-testing.ccuzkqo3zqzq.us-east-1.rds.amazonaws.com:1521:ORCL -hibernate.connection.username ormmasteruser +hibernate.connection.url jdbc:oracle:thin:@hibernate-testing-oracle-se.ccuzkqo3zqzq.us-east-1.rds.amazonaws.com:1521:ORCL +hibernate.connection.username hibernate_orm_test +hibernate.connection.password hibernate_orm_test hibernate.connection.pool_size 5 diff --git a/documentation/documentation.gradle b/documentation/documentation.gradle index 408b0f0f598fde324a0e7066bbf53cb2cfa6f78e..087e88e70f6e92789eb774eb51c0ae298e236fa4 100644 --- a/documentation/documentation.gradle +++ b/documentation/documentation.gradle @@ -40,7 +40,7 @@ dependencies { compile( libraries.jpa ) compile( project( ':hibernate-core' ) ) - compile( project( ':hibernate-jpamodelgen' ) ) + annotationProcessor( project( ':hibernate-jpamodelgen' ) ) testCompile( 'org.apache.commons:commons-lang3:3.4' ) @@ -124,13 +124,33 @@ task aggregateJavadocs(type: Javadoc) { links = [ 'https://docs.oracle.com/javase/8/docs/api/', - 'https://docs.jboss.org/hibernate/beanvalidation/spec/2.0/api/', + 'http://docs.jboss.org/hibernate/beanvalidation/spec/2.0/api/', 'http://docs.jboss.org/cdi/api/2.0/', 'https://javaee.github.io/javaee-spec/javadocs/' ] + + if ( gradle.ext.javaVersions.main.compiler.asInt() >= 11 ) { + //The need to set `--source 1.8` applies to all JVMs after 11, and also to 11 + // but after excluding the first two builds; see also specific comments on + // https://bugs.openjdk.java.net/browse/JDK-8212233?focusedCommentId=14245762 + // For now, let's be compatible with JDK 11.0.3+. We can improve on it if people + // complain they cannot build with JDK 11.0.0, 11.0.1 and 11.0.2. + logger.lifecycle "Forcing Javadoc in Java 8 compatible mode" + options.source = gradle.ext.baselineJavaVersion + } + + options.addStringOption( 'Xdoclint:none', '-quiet' ) + + if ( gradle.ext.javaToolchainEnabled ) { + options.setJFlags( getProperty( 'toolchain.javadoc.jvmargs' ).toString(). + split( ' ' ).toList().findAll( { !it.isEmpty() } ) ) + } + } - if ( JavaVersion.current().isJava8Compatible() ) { - options.addStringOption( 'Xdoclint:none', '-quiet' ) + if ( gradle.ext.javaToolchainEnabled ) { + // Display version of Java tools + doFirst { + logger.lifecycle "Aggregating javadoc with '${javadocTool.get().metadata.installationPath}'" } } diff --git a/documentation/src/main/asciidoc/integrationguide/chapters/services/Services.adoc b/documentation/src/main/asciidoc/integrationguide/chapters/services/Services.adoc index 9ba671f81ea55c23cca3f3cd7f2bb7af35629dc7..a23200c17388b87533848a17efcd0a47f0ccb108 100644 --- a/documentation/src/main/asciidoc/integrationguide/chapters/services/Services.adoc +++ b/documentation/src/main/asciidoc/integrationguide/chapters/services/Services.adoc @@ -11,7 +11,7 @@ It will also delve into the ways third-party integrators and applications can le === What is a Service? A services provides a certain types of functionality, in a pluggable manner. -Specifically they are interfaces defining certain functionality and then implementations of those `Service` contract interfaces. +Specifically, they are interfaces defining certain functionality and then implementations of those `Service` contract interfaces. The interface is known as the `Service` role; the implementation class is known as the `Service` implementation. The pluggability comes from the fact that the `Service` implementation adheres to contract defined by the interface of the `Service` role and that consumers of the `Service` program to the `Service` role, not the implementation. diff --git a/documentation/src/main/asciidoc/quickstart/guides/obtaining.adoc b/documentation/src/main/asciidoc/quickstart/guides/obtaining.adoc index 8002e2a44d1fc3b1449ca53fc5e5753f0b0e3a4d..605bc13cddd5ff76b97c7354944c901b68cc4ac5 100644 --- a/documentation/src/main/asciidoc/quickstart/guides/obtaining.adoc +++ b/documentation/src/main/asciidoc/quickstart/guides/obtaining.adoc @@ -17,8 +17,6 @@ hibernate-proxool:: Integrates the http://proxool.sourceforge.net/[Proxool] conn hibernate-jcache:: Integrates the https://jcp.org/en/jsr/detail?id=107$$[JCache] caching specification into Hibernate, enabling any compliant implementation to become a second-level cache provider. hibernate-ehcache:: Integrates the http://ehcache.org/[Ehcache] caching library into Hibernate as a second-level cache provider. -hibernate-infinispan:: Integrates the http://infinispan.org/[Infinispan] caching library into Hibernate as a second-level cache provider. - === Release Bundle Downloads diff --git a/documentation/src/main/asciidoc/quickstart/guides/preface.adoc b/documentation/src/main/asciidoc/quickstart/guides/preface.adoc index 77c2417a8a77e3fd95e4c83f9d387d0321104344..ba4111af91baeee0169d85f6910358253e615df5 100644 --- a/documentation/src/main/asciidoc/quickstart/guides/preface.adoc +++ b/documentation/src/main/asciidoc/quickstart/guides/preface.adoc @@ -3,7 +3,7 @@ [preface] == Preface -Working with both Object-Oriented software and Relational Databases can be cumbersome and time consuming. +Working with both Object-Oriented software and Relational Databases can be cumbersome and time-consuming. Development costs are significantly higher due to a paradigm mismatch between how data is represented in objects versus relational databases. Hibernate is an Object/Relational Mapping (ORM) solution for Java environments. The term Object/Relational Mapping refers to the technique of mapping data between an object model representation to diff --git a/documentation/src/main/asciidoc/quickstart/tutorials/osgi/managed-jpa/features.xml b/documentation/src/main/asciidoc/quickstart/tutorials/osgi/managed-jpa/features.xml index c712d512f40679da7cf3ac3fc38069ee2e72b8ab..1df06fe5b90789cb901eed8fd9bc0a6233850561 100644 --- a/documentation/src/main/asciidoc/quickstart/tutorials/osgi/managed-jpa/features.xml +++ b/documentation/src/main/asciidoc/quickstart/tutorials/osgi/managed-jpa/features.xml @@ -59,7 +59,6 @@ mvn:com.fasterxml/classmate/0.8.0 mvn:org.apache.logging.log4j/log4j-api/2.0 - mvn:log4j/log4j/1.2.17 mvn:org.jboss.logging/jboss-logging/3.2.1.Final mvn:org.javassist/javassist/3.18.1-GA diff --git a/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-jpa/features.xml b/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-jpa/features.xml index 3252004918ee8afff94b92c4130fa04ad24dd9b3..f3f240a28223bfd96119df95ac6a6fe912c36a37 100644 --- a/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-jpa/features.xml +++ b/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-jpa/features.xml @@ -32,7 +32,6 @@ mvn:com.fasterxml/classmate/0.8.0 mvn:org.apache.logging.log4j/log4j-api/2.0 - mvn:log4j/log4j/1.2.17 mvn:org.jboss.logging/jboss-logging/3.2.1.Final mvn:org.javassist/javassist/3.18.1-GA diff --git a/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-native/features.xml b/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-native/features.xml index 3adcef2c307a3141b554302a2efaaf752a1eeadd..cd4d665d40cc087778aafdec89482c0b2ef02206 100644 --- a/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-native/features.xml +++ b/documentation/src/main/asciidoc/quickstart/tutorials/osgi/unmanaged-native/features.xml @@ -40,7 +40,6 @@ mvn:com.fasterxml/classmate/0.8.0 mvn:org.apache.logging.log4j/log4j-api/2.0 - mvn:log4j/log4j/1.2.17 mvn:org.jboss.logging/jboss-logging/3.2.1.Final mvn:org.javassist/javassist/3.18.1-GA diff --git a/documentation/src/main/asciidoc/topical/bootstrap/LegacyBootstrapping.adoc b/documentation/src/main/asciidoc/topical/bootstrap/LegacyBootstrapping.adoc index 2f9641b4efea6e462a61cadc1ec65958fdda198d..6f82eda7c65514cf2cd95e2e94d0c946d6019f71 100644 --- a/documentation/src/main/asciidoc/topical/bootstrap/LegacyBootstrapping.adoc +++ b/documentation/src/main/asciidoc/topical/bootstrap/LegacyBootstrapping.adoc @@ -58,7 +58,7 @@ There are other ways to specify configuration properties, including: * Place a file named hibernate.properties in a root directory of the classpath. * Place a file named hibernate.properties in a root directory of the classpath. * Pass an instance of java.util.Properties to `Configuration#setProperties`. -* Set System properties using java `-Dproperty=value`. +* Set System properties using Java `-Dproperty=value`. * Include `` elements in `hibernate.cfg.xml` diff --git a/documentation/src/main/asciidoc/topical/bootstrap/NativeBootstrapping.adoc b/documentation/src/main/asciidoc/topical/bootstrap/NativeBootstrapping.adoc index c6312902b77f03e2e7b86b19c633463ae0c05a43..e596c9ef3dd0148debc4203814a20d98a68f47b1 100644 --- a/documentation/src/main/asciidoc/topical/bootstrap/NativeBootstrapping.adoc +++ b/documentation/src/main/asciidoc/topical/bootstrap/NativeBootstrapping.adoc @@ -4,7 +4,7 @@ This guide discusses the process of bootstrapping a Hibernate `org.hibernate.SessionFactory`. It also discusses the ways in which applications and integrators can hook-in to and affect that process. This bootstrapping process is defined in 2 distinct steps. The first step is the building of a ServiceRegistry -holding the services Hibernate will need at bootstrap- and run-time. The second step is the building of +holding the services Hibernate will need at bootstrap- and runtime. The second step is the building of a Metadata object representing the mapping information for the application's model and its mapping to the database. diff --git a/documentation/src/main/asciidoc/topical/bytecode/BytecodeEnhancement.adoc b/documentation/src/main/asciidoc/topical/bytecode/BytecodeEnhancement.adoc index 70e6026838064ee5c37ef279c5e01046fd2ad9bc..3e5922680f197c9277a04b8526620cc86d283477 100644 --- a/documentation/src/main/asciidoc/topical/bytecode/BytecodeEnhancement.adoc +++ b/documentation/src/main/asciidoc/topical/bytecode/BytecodeEnhancement.adoc @@ -23,11 +23,11 @@ Ultimately all enhancement is handled by the `org.hibernate.bytecode.enhance.spi enhancement can certainly be crafted on top of Enhancer, but that is beyond the scope of this guide. Here we will focus on the means Hibernate already exposes for performing these enhancements. -=== Run-time enhancement +=== Runtime enhancement -Currently run-time enhancement of the domain model is only supported in managed JPA environments following the JPA defined SPI for performing class transformations. +Currently runtime enhancement of the domain model is only supported in managed JPA environments following the JPA defined SPI for performing class transformations. -Even then, this support is disabled by default. To enable run-time enhancement, specify one of the following configuration properties: +Even then, this support is disabled by default. To enable runtime enhancement, specify one of the following configuration properties: `*hibernate.enhancer.enableDirtyTracking*` (e.g. `true` or `false` (default value)):: Enable dirty tracking feature in runtime bytecode enhancement. diff --git a/documentation/src/main/asciidoc/topical/metamodelgen/MetamodelGenerator.adoc b/documentation/src/main/asciidoc/topical/metamodelgen/MetamodelGenerator.adoc index 8b203354ee707d7f4b7d8fe480dfa208044bc032..65dac38984e9c204b3bce90b1b76c29fcd5bfe64 100644 --- a/documentation/src/main/asciidoc/topical/metamodelgen/MetamodelGenerator.adoc +++ b/documentation/src/main/asciidoc/topical/metamodelgen/MetamodelGenerator.adoc @@ -167,7 +167,7 @@ appropriate and all classes X, Y, Z, and K. == Usage The jar file for the annotation processor can be found in the -http://repository.jboss.com/[JBoss Maven repository] under: +https://search.maven.org/[Maven Central repository] under: ==== [source, XML] @@ -182,12 +182,12 @@ http://repository.jboss.com/[JBoss Maven repository] under: ==== Alternatively, it can be found in the ORM distribution bundle on -http://sourceforge.net/projects/hibernate/files/hibernate4[SourceForge]. +https://sourceforge.net/projects/hibernate/files/hibernate-orm/[SourceForge]. In most cases the annotation processor will automatically run provided -the processor jar is added to the build classpath and a JDK >6 is used. +the processor jar is added to the build classpath. This happens due to Java's Service Provider contract and the fact -the the Hibernate Static Metamodel Generator jar files contains the +the Hibernate Static Metamodel Generator jar files contains the file _javax.annotation.processing.Processor_ in the _META-INF/services_ directory. The fully qualified name of the processor itself is: @@ -246,8 +246,8 @@ pass the processor option to the compiler plugin: maven-compiler-plugin - 1.6 - 1.6 + 1.8 + 1.8 org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor @@ -273,14 +273,14 @@ plugin as seen in below. maven-compiler-plugin - 1.6 - 1.6 + 1.8 + 1.8 -proc:none ---- ==== -Once disabled, the http://code.google.com/p/maven-annotation-plugin[maven-processor-plugin] +Once disabled, the https://bsorrentino.github.io/maven-annotation-plugin/[maven-processor-plugin] for annotation processing can be used: [[maven-processor-plugin]] @@ -375,6 +375,9 @@ Just check the "Enable annotation processing" option, configure the directory fo generated sources and finally add the Hibernate Static Metamodel Generator and JPA 2 jar files to the factory path. +If you use JDK 11+, you also need to add the `javax.xml.bind:jaxb-api` and +`org.glassfish.jaxb:jaxb-runtime` jars as JAXB is not included in the JDK anymore. + image:eclipse-annotation-processor-config.png[] === Processor specific options diff --git a/documentation/src/main/asciidoc/topical/registries/ServiceRegistries.adoc b/documentation/src/main/asciidoc/topical/registries/ServiceRegistries.adoc index 462d2d4ec498ae109e2d03909d44492959df8e9b..856dba0fc0d98f599f2a6f8680d394811186d39c 100644 --- a/documentation/src/main/asciidoc/topical/registries/ServiceRegistries.adoc +++ b/documentation/src/main/asciidoc/topical/registries/ServiceRegistries.adoc @@ -12,7 +12,7 @@ applications can leverage and customize Services and Registries. == What is a Service? -Services provide various types of functionality, in a pluggable manner. Specifically they are interfaces defining +Services provide various types of functionality, in a pluggable manner. Specifically, they are interfaces defining certain functionality and then implementations of those service contract interfaces. The interface is known as the service role; the implementation class is known as the service implementation. The pluggability comes from the fact that the service implementation adheres to contract defined by the interface of the service role and that consumers diff --git a/documentation/src/main/asciidoc/topical/wildfly/Wildfly.adoc b/documentation/src/main/asciidoc/topical/wildfly/Wildfly.adoc index 128393652bf2544cabde363c0948abee6cd3e0da..af6ca0fddbf11093c2b7a69ac2b7a5259c67144c 100644 --- a/documentation/src/main/asciidoc/topical/wildfly/Wildfly.adoc +++ b/documentation/src/main/asciidoc/topical/wildfly/Wildfly.adoc @@ -208,8 +208,8 @@ repositories { mavenLocal() mavenCentral() maven { - name 'jboss-nexus' - url "http://repository.jboss.org/nexus/content/groups/public/" + name 'jboss-public' + url 'https://repository.jboss.org/nexus/content/groups/public/' } } @@ -266,9 +266,9 @@ By convention all modules included with WildFly use the "main" slot, while the m will use a slot name which matches the version, and also provide an alias to match its "major.minor" version. Our suggestion is to depend on the module using the "major.minor" representation, as this simplifies rolling out bugfix -releases (micro version updates) of Hibernate ORM without changing application configuration (micro versions are always expected to be backwards compatible and released as bugfix only). +releases (micro version updates) of Hibernate ORM without changing application configuration (micro versions are always expected to be backward compatible and released as bugfix only). -For example if your application wants to use the latest version of Hibernate ORM version {majorMinorVersion}.x it should declare to use the module _org.hibernate:{majorMinorVersion}_. You can of course decide to use the full version instead for more precise control, in case an application requires a very specific version. +For example, if your application wants to use the latest version of Hibernate ORM version {majorMinorVersion}.x it should declare to use the module _org.hibernate:{majorMinorVersion}_. You can of course decide to use the full version instead for more precise control, in case an application requires a very specific version. == Switch to a different Hibernate ORM slot @@ -311,7 +311,7 @@ you might want to check it out as it lists several other useful properties. When using the custom modules provided by the feature packs you're going to give up on some of the integration which the application server normally automates. -For example enabling an Infinispan 2nd level cache is straight forward when using the default Hibernate ORM +For example, enabling an Infinispan 2nd level cache is straight forward when using the default Hibernate ORM module, as WildFly will automatically setup the dependency to the Infinispan and clustering components. When using these custom modules such integration will no longer work automatically: you can still enable all normally available features but these will require explicit configuration, as if you were diff --git a/documentation/src/main/asciidoc/userguide/Preface.adoc b/documentation/src/main/asciidoc/userguide/Preface.adoc index ec196cd5a5143272f8640af7721fa0a55fcd10bd..d21ebf8f564f916a750c059159b3cee68e2867d4 100644 --- a/documentation/src/main/asciidoc/userguide/Preface.adoc +++ b/documentation/src/main/asciidoc/userguide/Preface.adoc @@ -1,10 +1,10 @@ [[preface]] == Preface -Working with both Object-Oriented software and Relational Databases can be cumbersome and time consuming. +Working with both Object-Oriented software and Relational Databases can be cumbersome and time-consuming. Development costs are significantly higher due to a paradigm mismatch between how data is represented in objects versus relational databases. Hibernate is an Object/Relational Mapping solution for Java environments. -The term http://en.wikipedia.org/wiki/Object-relational_mapping[Object/Relational Mapping] refers to the technique of mapping data from an object model representation to a relational data model representation (and visa versa). +The term http://en.wikipedia.org/wiki/Object-relational_mapping[Object/Relational Mapping] refers to the technique of mapping data from an object model representation to a relational data model representation (and vice versa). Hibernate not only takes care of the mapping from Java classes to database tables (and from Java data types to SQL data types), but also provides data query and retrieval facilities. It can significantly reduce development time otherwise spent with manual data handling in SQL and JDBC. diff --git a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc index 6911c1afcf175c4bc74c779418bd13d762ce2abf..214362104ecf7c019c147cc91f612a4e5e1b0393 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc @@ -84,7 +84,7 @@ See the <> section for more info. @@ -159,7 +159,7 @@ See the <> section for more info. [[annotations-jpa-entitylisteners]] ==== `@EntityListeners` -The http://docs.oracle.com/javaee/7/api/javax/persistence/EntityListeners.html[`@EntityListeners`] annotation is used to specify an array of callback listener classes that are used by the current annotated entity. +The http://docs.oracle.com/javaee/7/api/javax/persistence/EntityListeners.html[`@EntityListeners`] annotation is used to specify an array of callback listener classes that are used by the currently annotated entity. See the <> section for more info. @@ -180,14 +180,14 @@ See the <> section for more info. [[annotations-jpa-excludesuperclasslisteners]] ==== `@ExcludeSuperclassListeners` -The http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeSuperclassListeners.html[`@ExcludeSuperclassListeners`] annotation is used to specify that the current annotated entity skips the invocation of listeners declared by its superclass. +The http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeSuperclassListeners.html[`@ExcludeSuperclassListeners`] annotation is used to specify that the currently annotated entity skips the invocation of listeners declared by its superclass. See the <> section for more info. @@ -266,7 +266,7 @@ See the <> section for more info. @@ -303,7 +303,7 @@ See the <> section for an example of `@MapKeyColumn` annotation usage. +See the <> for an example of `@MapKeyColumn` annotation usage. [[annotations-jpa-mapkeyenumerated]] ==== `@MapKeyEnumerated` @@ -335,14 +335,14 @@ See the <> section for more info. [[annotations-jpa-mapsid]] ==== `@MapsId` -The http://docs.oracle.com/javaee/7/api/javax/persistence/MapsId.html[`@MapsId`] annotation is used to specify that the entity identifier is mapped by the current annotated `@ManyToOne` or `@OneToOne` association. +The http://docs.oracle.com/javaee/7/api/javax/persistence/MapsId.html[`@MapsId`] annotation is used to specify that the entity identifier is mapped by the currently annotated `@ManyToOne` or `@OneToOne` association. See the <> section for more info. @@ -427,7 +427,7 @@ See the <> section for more info. @@ -521,7 +521,7 @@ See the <> section for more info. @@ -541,7 +541,7 @@ See the <> section for more info. @@ -553,7 +553,7 @@ The http://docs.oracle.com/javaee/7/api/javax/persistence/SecondaryTables.html[` [[annotations-jpa-sequencegenerator]] ==== `@SequenceGenerator` -The http://docs.oracle.com/javaee/7/api/javax/persistence/SequenceGenerator.html[`@SequenceGenerator`] annotation is used to specify the database sequence used by the identifier generator of the current annotated entity. +The http://docs.oracle.com/javaee/7/api/javax/persistence/SequenceGenerator.html[`@SequenceGenerator`] annotation is used to specify the database sequence used by the identifier generator of the currently annotated entity. See the <> section for more info. @@ -579,21 +579,21 @@ See the <> section for more info. [[annotations-jpa-tablegenerator]] ==== `@TableGenerator` -The http://docs.oracle.com/javaee/7/api/javax/persistence/TableGenerator.html[`@TableGenerator`] annotation is used to specify the database table used by the identity generator of the current annotated entity. +The http://docs.oracle.com/javaee/7/api/javax/persistence/TableGenerator.html[`@TableGenerator`] annotation is used to specify the database table used by the identity generator of the currently annotated entity. See the <> section for more info. [[annotations-jpa-temporal]] ==== `@Temporal` -The http://docs.oracle.com/javaee/7/api/javax/persistence/Temporal.html[`@Temporal`] annotation is used to specify the `TemporalType` of the current annotated `java.util.Date` or `java.util.Calendar` entity attribute. +The http://docs.oracle.com/javaee/7/api/javax/persistence/Temporal.html[`@Temporal`] annotation is used to specify the `TemporalType` of the currently annotated `java.util.Date` or `java.util.Calendar` entity attribute. See the <> chapter for more info. @@ -607,7 +607,7 @@ See the <> chapter for more info. @@ -712,7 +712,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern The same behavior can be achieved using the `definition` attribute of the JPA <> annotation. -See the <> chapter for more info. +See the <> chapter for more info. [[annotations-hibernate-columns]] ==== `@Columns` @@ -736,7 +736,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-creationtimestamp]] ==== `@CreationTimestamp` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/CreationTimestamp.html[`@CreationTimestamp`] annotation is used to specify that the current annotated temporal type must be initialized with the current JVM timestamp value. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/CreationTimestamp.html[`@CreationTimestamp`] annotation is used to specify that the currently annotated temporal type must be initialized with the current JVM timestamp value. See the <> section for more info. @@ -787,7 +787,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-fetch]] ==== `@Fetch` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Fetch.html[`@Fetch`] annotation is used to specify the Hibernate specific https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/FetchMode.html[`FetchMode`] (e.g. `JOIN`, `SELECT`, `SUBSELECT`) used for the current annotated association: +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Fetch.html[`@Fetch`] annotation is used to specify the Hibernate specific https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/FetchMode.html[`FetchMode`] (e.g. `JOIN`, `SELECT`, `SUBSELECT`) used for the currently annotated association: See the <> section for more info. @@ -861,7 +861,7 @@ See the <> section for more info. @@ -869,7 +869,7 @@ See the <> section for more info. @@ -1044,14 +1044,14 @@ See the <> section for more info. [[annotations-hibernate-naturalid]] ==== `@NaturalId` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/NaturalId.html[`@NaturalId`] annotation is used to specify that the current annotated attribute is part of the natural id of the entity. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/NaturalId.html[`@NaturalId`] annotation is used to specify that the currently annotated attribute is part of the natural id of the entity. See the <> section for more info. @@ -1077,7 +1077,7 @@ See the <> section for more info. [[annotations-hibernate-optimisticlocking]] ==== `@OptimisticLocking` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLocking.html[`@OptimisticLocking`] annotation is used to specify the current annotated an entity optimistic locking strategy. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLocking.html[`@OptimisticLocking`] annotation is used to specify the currently annotated an entity optimistic locking strategy. The four possible strategies are defined by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLockType.html[`OptimisticLockType`] enumeration: @@ -1111,7 +1111,7 @@ See the <> annotation because the JPA annotation expects a JPQL order-by fragment, not an SQL directive. @@ -1127,13 +1127,13 @@ See the <>, <>, and <>, <>. [[annotations-hibernate-parent]] ==== `@Parent` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Parent.html[`@Parent`] annotation is used to specify that the current annotated embeddable attribute references back the owning entity. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Parent.html[`@Parent`] annotation is used to specify that the currently annotated embeddable attribute references back the owning entity. See the <> section for more info. @@ -1155,15 +1155,15 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern There are two possible `PolymorphismType` options: -EXPLICIT:: The current annotated entity is retrieved only if explicitly asked. -IMPLICIT:: The current annotated entity is retrieved if any of its super entity are retrieved. This is the default option. +EXPLICIT:: The currently annotated entity is retrieved only if explicitly asked. +IMPLICIT:: The currently annotated entity is retrieved if any of its super entity are retrieved. This is the default option. See the <> section for more info. [[annotations-hibernate-proxy]] ==== `@Proxy` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Proxy.html[`@Proxy`] annotation is used to specify a custom proxy implementation for the current annotated entity. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Proxy.html[`@Proxy`] annotation is used to specify a custom proxy implementation for the currently annotated entity. See the <> section for more info. @@ -1180,7 +1180,7 @@ See the <> [[annotations-hibernate-selectbeforeupdate]] ==== `@SelectBeforeUpdate` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SelectBeforeUpdate.html[`@SelectBeforeUpdate`] annotation is used to specify that the current annotated entity state be selected from the database when determining whether to perform an update when the detached entity is reattached. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SelectBeforeUpdate.html[`@SelectBeforeUpdate`] annotation is used to specify that the currently annotated entity state be selected from the database when determining whether to perform an update when the detached entity is reattached. See the <> section for more info on how `@SelectBeforeUpdate` works. @@ -1219,14 +1219,14 @@ See the <> section for more info. [[annotations-hibernate-sqldeleteall]] ==== `@SQLDeleteAll` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLDeleteAll.html[`@SQLDeleteAll`] annotation is used to specify a custom SQL `DELETE` statement when removing all elements of the current annotated collection. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLDeleteAll.html[`@SQLDeleteAll`] annotation is used to specify a custom SQL `DELETE` statement when removing all elements of the currently annotated collection. See the <> section for more info. @@ -1242,14 +1242,14 @@ See the <> section for more info. [[annotations-hibernate-sqlupdate]] ==== `@SQLUpdate` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLUpdate.html[`@SQLUpdate`] annotation is used to specify a custom SQL `UPDATE` statement for the current annotated entity or collection. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SQLUpdate.html[`@SQLUpdate`] annotation is used to specify a custom SQL `UPDATE` statement for the currently annotated entity or collection. See the <> section for more info. @@ -1285,14 +1285,14 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-target]] ==== `@Target` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Target.html[`@Target`] annotation is used to specify an explicit target implementation when the current annotated association is using an interface type. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Target.html[`@Target`] annotation is used to specify an explicit target implementation when the currently annotated association is using an interface type. See the <> section for more info. [[annotations-hibernate-tuplizer]] ==== `@Tuplizer` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Tuplizer.html[`@Tuplizer`] annotation is used to specify a custom tuplizer for the current annotated entity or embeddable. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Tuplizer.html[`@Tuplizer`] annotation is used to specify a custom tuplizer for the currently annotated entity or embeddable. For entities, the tupelizer must implement the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tuple/entity/EntityTuplizer.html[`EntityTuplizer`] interface. @@ -1308,7 +1308,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-type]] ==== `@Type` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Type.html[`@Type`] annotation is used to specify the Hibernate https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/type/Type.html[`@Type`] used by the current annotated basic attribute. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Type.html[`@Type`] annotation is used to specify the Hibernate https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/type/Type.html[`@Type`] used by the currently annotated basic attribute. See the <> section for more info. @@ -1327,7 +1327,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern [[annotations-hibernate-updatetimestamp]] ==== `@UpdateTimestamp` -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/UpdateTimestamp.html[`@UpdateTimestamp`] annotation is used to specify that the current annotated timestamp attribute should be updated with the current JVM timestamp whenever the owning entity gets modified. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/UpdateTimestamp.html[`@UpdateTimestamp`] annotation is used to specify that the currently annotated timestamp attribute should be updated with the current JVM timestamp whenever the owning entity gets modified. - `java.util.Date` - `java.util.Calendar` diff --git a/documentation/src/main/asciidoc/userguide/appendices/BestPractices.adoc b/documentation/src/main/asciidoc/userguide/appendices/BestPractices.adoc index d48160e721b0770d68ece9c30b32c7b0bec64f43..b59113f9a2329d92d4b39187ffdd24da577839c5 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/BestPractices.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/BestPractices.adoc @@ -215,12 +215,12 @@ If you need to fetch multiple collections, to avoid a Cartesian Product, you sho Hibernate has two caching layers: -- the first-level cache (Persistence Context) which is a application-level repeatable reads. +- the first-level cache (Persistence Context) which provides application-level repeatable reads. - the second-level cache which, unlike application-level caches, it doesn't store entity aggregates but normalized dehydrated entity entries. The first-level cache is not a caching solution "per se", being more useful for ensuring `READ COMMITTED` isolation level. -While the first-level cache is short lived, being cleared when the underlying `EntityManager` is closed, the second-level cache is tied to an `EntityManagerFactory`. +While the first-level cache is short-lived, being cleared when the underlying `EntityManager` is closed, the second-level cache is tied to an `EntityManagerFactory`. Some second-level caching providers offer support for clusters. Therefore, a node needs only to store a subset of the whole cached data. Although the second-level cache can reduce transaction response time since entities are retrieved from the cache rather than from the database, @@ -233,8 +233,8 @@ and you should consider these alternatives prior to jumping to a second-level ca After properly tuning the database, to further reduce the average response time and increase the system throughput, application-level caching becomes inevitable. -Topically, a key-value application-level cache like https://memcached.org/[Memcached] or http://redis.io/[Redis] is a common choice to store data aggregates. -If you can duplicate all data in the key-value store, you have the option of taking down the database system for maintenance without completely loosing availability since read-only traffic can still be served from the cache. +Typically, a key-value application-level cache like https://memcached.org/[Memcached] or http://redis.io/[Redis] is a common choice to store data aggregates. +If you can duplicate all data in the key-value store, you have the option of taking down the database system for maintenance without completely losing availability since read-only traffic can still be served from the cache. One of the main challenges of using an application-level cache is ensuring data consistency across entity aggregates. That's where the second-level cache comes to the rescue. diff --git a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc index 7dcd85451eafdedc4a7d954072cddd4c8676d830..e3f1312132658929d94185a971e1031bf5c989d8 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc @@ -5,8 +5,8 @@ === Strategy configurations Many configuration settings define pluggable strategies that Hibernate uses for various purposes. -The configuration of many of these strategy type settings accept definition in various forms. -The documentation of such configuration settings refer here. +The configurations of many of these strategy type settings accept definition in various forms. +The documentation of such configuration settings refers here. The types of forms available in such cases include: short name (if defined):: @@ -22,9 +22,9 @@ strategy Class name:: === General configuration `*hibernate.dialect*` (e.g. `org.hibernate.dialect.PostgreSQL94Dialect`):: -The classname of a Hibernate https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`] from which Hibernate can generate SQL optimized for a particular relational database. +The class name of a Hibernate https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`] from which Hibernate can generate SQL optimized for a particular relational database. + -In most cases Hibernate can choose the correct https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`] implementation based on the JDBC metadata returned by the JDBC driver. +In most cases, Hibernate can choose the correct https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`] implementation based on the JDBC metadata returned by the JDBC driver. + `*hibernate.current_session_context_class*` (e.g. `jta`, `thread`, `managed`, or a custom class implementing `org.hibernate.context.spi.CurrentSessionContext`):: + @@ -32,7 +32,7 @@ Supply a custom strategy for the scoping of the _current_ `Session`. + The definition of what exactly _current_ means is controlled by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/spi/CurrentSessionContext.html[`CurrentSessionContext`] implementation in use. + -Note that for backwards compatibility, if a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/spi/CurrentSessionContext.html[`CurrentSessionContext`] is not configured but JTA is configured this will default to the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/internal/JTASessionContext.html[`JTASessionContext`]. +Note that for backward compatibility, if a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/spi/CurrentSessionContext.html[`CurrentSessionContext`] is not configured but JTA is configured this will default to the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/context/internal/JTASessionContext.html[`JTASessionContext`]. [[configurations-jpa-compliance]] === JPA compliance @@ -45,7 +45,7 @@ since it extends the JPA one. Controls whether Hibernate's handling of `javax.persistence.Query` (JPQL, Criteria and native-query) should strictly follow the JPA spec. + This includes both in terms of parsing or translating a query as well as calls to the `javax.persistence.Query` methods throwing spec -defined exceptions where as Hibernate might not. +defined exceptions whereas Hibernate might not. `*hibernate.jpa.compliance.list*` (e.g. `true` or `false` (default value)):: Controls whether Hibernate should recognize what it considers a "bag" (`org.hibernate.collection.internal.PersistentBag`) @@ -58,7 +58,7 @@ is just missing (and its defaults will apply). JPA defines specific exceptions upon calling specific methods on `javax.persistence.EntityManager` and `javax.persistence.EntityManagerFactory` objects which have been closed previously. + -This setting controls whether the JPA spec defined behavior or the Hibernate behavior will be used. +This setting controls whether the JPA spec-defined behavior or the Hibernate behavior will be used. + If enabled, Hibernate will operate in the JPA specified way, throwing exceptions when the spec says it should. @@ -105,13 +105,13 @@ See discussion of `hibernate.connection.provider_disables_autocommit` as well. `*hibernate.connection.provider_disables_autocommit*` (e.g. `true` or `false` (default value)):: Indicates a promise by the user that Connections that Hibernate obtains from the configured ConnectionProvider have auto-commit disabled when they are obtained from that provider, whether that provider is backed by -a DataSource or some other Connection pooling mechanism. Generally this occurs when: +a DataSource or some other Connection pooling mechanism. Generally, this occurs when: * Hibernate is configured to get Connections from an underlying DataSource, and that DataSource is already configured to disable auto-commit on its managed Connections * Hibernate is configured to get Connections from a non-DataSource connection pool and that connection pool is already configured to disable auto-commit. For the Hibernate provided implementation this will depend on the value of `hibernate.connection.autocommit` setting. + Hibernate uses this assurance as an opportunity to opt-out of certain operations that may have a performance -impact (although this impact is general negligible). Specifically, when a transaction is started via the +impact (although this impact is generally negligible). Specifically, when a transaction is started via the Hibernate or JPA transaction APIs Hibernate will generally immediately acquire a Connection from the provider and: * check whether the Connection is initially in auto-commit mode via a call to `Connection#getAutocommit` to know how to clean up the Connection when released. @@ -146,7 +146,7 @@ Can reference: ** a fully qualified name of a class implementing `ConnectionProvider` + -The term `class` appears in the setting name due to legacy reasons; however it can accept instances. +The term `class` appears in the setting name due to legacy reasons. However, it can accept instances. `*hibernate.jndi.class*`:: Names the JNDI `javax.naming.InitialContext` class. @@ -196,7 +196,7 @@ The number of seconds between two consecutive pool validations. During validatio Maximum size of C3P0 statement cache. Refers to http://www.mchange.com/projects/c3p0/#maxStatements[c3p0 `maxStatements` setting]. `*hibernate.c3p0.acquire_increment*` (e.g. 2):: - Number of connections acquired at a time when there's no connection available in the pool. Refers to http://www.mchange.com/projects/c3p0/#acquireIncrement[c3p0 `acquireIncrement` setting]. + The number of connections acquired at a time when there's no connection available in the pool. Refers to http://www.mchange.com/projects/c3p0/#acquireIncrement[c3p0 `acquireIncrement` setting]. `*hibernate.c3p0.idle_test_period*` (e.g. 5):: Idle time before a C3P0 pooled connection is validated. Refers to http://www.mchange.com/projects/c3p0/#idleConnectionTestPeriod[c3p0 `idleConnectionTestPeriod` setting]. @@ -289,7 +289,7 @@ The following short names are defined for this setting: `legacy-hbm`::: Uses the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/model/naming/ImplicitNamingStrategyLegacyHbmImpl.html[`ImplicitNamingStrategyLegacyHbmImpl`] `component-path`::: Uses the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/model/naming/ImplicitNamingStrategyComponentPathImpl.html[`ImplicitNamingStrategyComponentPathImpl`] + -If this property happens to be empty, the fallback is to use `default` strategy. +If this property happens to be empty, the fallback is to use the `default` strategy. `*hibernate.physical_naming_strategy*` (e.g. `org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl` (default value)):: Used to specify the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/model/naming/PhysicalNamingStrategy.html[`PhysicalNamingStrategy`] class to use. @@ -336,7 +336,7 @@ Therefore, when setting `exclude-unlisted-classes` to true, only the classes tha Used to specify the order in which metadata sources should be processed. Value is a delimited-list whose elements are defined by https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/cfg/MetadataSourceType.html[`MetadataSourceType`]. + -Default is `hbm,class"`, therefore `hbm.xml` files are processed first, followed by annotations (combined with `orm.xml` mappings). +The default is `hbm,class"`, therefore `hbm.xml` files are processed first, followed by annotations (combined with `orm.xml` mappings). + When using JPA, the XML mapping overrides a conflicting annotation mapping that targets the same entity attribute. @@ -460,7 +460,7 @@ Can reference a `StatementInspector` implementation class name (fully-qualified class name). `*hibernate.query.validate_parameters*` (e.g. `true` (default value) or `false`):: -This configuration property can be used to disable parameters validation performed by `org.hibernate.query.Query#setParameter` when the the Session is bootstrapped via JPA +This configuration property can be used to disable parameters validation performed by `org.hibernate.query.Query#setParameter` when the Session is bootstrapped via JPA `javax.persistence.EntityManagerFactory` `*hibernate.criteria.literal_handling_mode*` (e.g. `AUTO` (default value), `BIND` or `INLINE`):: @@ -491,19 +491,19 @@ Provide a custom https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javado `*hibernate.hql.bulk_id_strategy.persistent.drop_tables*` (e.g. `true` or `false` (default value)):: This configuration property is used by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/hql/spi/id/persistent/PersistentTableBulkIdStrategy.html[`PersistentTableBulkIdStrategy`], that mimics temporary tables for databases which do not support temporary tables. -It follows a pattern similar to the ANSI SQL definition of global temporary table using a "session id" column to segment rows from the various sessions. +It follows a pattern similar to the ANSI SQL definition of the global temporary table using a "session id" column to segment rows from the various sessions. + This configuration property allows you to DROP the tables used for multi-table bulk HQL operations when the `SessionFactory` or the `EntityManagerFactory` is closed. `*hibernate.hql.bulk_id_strategy.persistent.schema*` (e.g. Database schema name. By default, the `hibernate.default_schema` is used.):: This configuration property is used by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/hql/spi/id/persistent/PersistentTableBulkIdStrategy.html[`PersistentTableBulkIdStrategy`], that mimics temporary tables for databases which do not support temporary tables. -It follows a pattern similar to the ANSI SQL definition of global temporary table using a "session id" column to segment rows from the various sessions. +It follows a pattern similar to the ANSI SQL definition of the global temporary table using a "session id" column to segment rows from the various sessions. + This configuration property defines the database schema used for storing the temporary tables used for bulk HQL operations. `*hibernate.hql.bulk_id_strategy.persistent.catalog*` (e.g. Database catalog name. By default, the `hibernate.default_catalog` is used.):: This configuration property is used by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/hql/spi/id/persistent/PersistentTableBulkIdStrategy.html[`PersistentTableBulkIdStrategy`], that mimics temporary tables for databases which do not support temporary tables. -It follows a pattern similar to the ANSI SQL definition of global temporary table using a "session id" column to segment rows from the various sessions. +It follows a pattern similar to the ANSI SQL definition of the global temporary table using a "session id" column to segment rows from the various sessions. + This configuration property defines the database catalog used for storing the temporary tables used for bulk HQL operations. @@ -515,9 +515,9 @@ Legacy 4.x behavior favored performing pagination in-memory by avoiding the use In 5.x, the limit handler behavior favors performance, thus, if the dialect doesn't support offsets, an exception is thrown instead. `*hibernate.query.conventional_java_constants*` (e.g. `true` (default value) or `false`):: -Setting which indicates whether or not Java constant follow the https://docs.oracle.com/javase/tutorial/java/nutsandbolts/variables.html[Java Naming conventions]. +Setting which indicates whether or not Java constants follow the https://docs.oracle.com/javase/tutorial/java/nutsandbolts/variables.html[Java Naming conventions]. + -Default is `true`. +The default is `true`. Existing applications may want to disable this (set it `false`) if non-conventional Java constants are used. However, there is a significant performance overhead for using non-conventional Java constants since Hibernate cannot determine if aliases should be treated as Java constants or not. @@ -546,17 +546,17 @@ Names the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/ + Can specify either the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/loader/BatchFetchStyle.html[`BatchFetchStyle`] name (insensitively), or a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/loader/BatchFetchStyle.html[`BatchFetchStyle`] instance. `LEGACY}` is the default value. -`*hibernate.jdbc.batch.builder*` (e.g. The fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/jdbc/batch/spi/BatchBuilder.html[`BatchBuilder`] implementation class type or an actual object instance):: +`*hibernate.jdbc.batch.builder*` (e.g. The fully qualified name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/jdbc/batch/spi/BatchBuilder.html[`BatchBuilder`] implementation class type or an actual object instance):: Names the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/engine/jdbc/batch/spi/BatchBuilder.html[`BatchBuilder`] implementation to use. [[configurations-database-fetch]] ==== Fetching properties `*hibernate.max_fetch_depth*` (e.g. A value between `0` and `3`):: -Sets a maximum depth for the outer join fetch tree for single-ended associations. A single-ended association is a one-to-one or many-to-one assocation. A value of `0` disables default outer join fetching. +Sets a maximum depth for the outer join fetch tree for single-ended associations. A single-ended association is a one-to-one or many-to-one association. A value of `0` disables default outer join fetching. `*hibernate.default_batch_fetch_size*` (e.g. `4`,`8`, or `16`):: -Default size for Hibernate Batch fetching of associations (lazily fetched associations can be fetched in batches to prevent N+1 query problems). +The default size for Hibernate Batch fetching of associations (lazily fetched associations can be fetched in batches to prevent N+1 query problems). `*hibernate.jdbc.fetch_size*` (e.g. `0` or an integer):: A non-zero value determines the JDBC fetch size, by calling `Statement.setFetchSize()`. @@ -576,7 +576,7 @@ Enable wrapping of JDBC result sets in order to speed up column name lookups for `*hibernate.enable_lazy_load_no_trans*` (e.g. `true` or `false` (default value)):: Initialize Lazy Proxies or Collections outside a given Transactional Persistence Context. + -Although enabling this configuration can make `LazyInitializationException` go away, it's better to use a fetch plan that guarantees that all properties are properly initialised before the Session is closed. +Although enabling this configuration can make `LazyInitializationException` go away, it's better to use a fetch plan that guarantees that all properties are properly initialized before the Session is closed. + In reality, you shouldn't probably enable this setting anyway. @@ -599,7 +599,7 @@ If true, Hibernate generates comments inside the SQL, for easier debugging. `*hibernate.generate_statistics*` (e.g. `true` or `false`):: Causes Hibernate to collect statistics for performance tuning. -`*hibernate.stats.factory*` (e.g. the fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/stat/spi/StatisticsFactory.html[`StatisticsFactory`] implementation or an actual instance):: +`*hibernate.stats.factory*` (e.g. the fully qualified name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/stat/spi/StatisticsFactory.html[`StatisticsFactory`] implementation or an actual instance):: The `StatisticsFactory` allow you to customize how the Hibernate Statistics are being collected. `*hibernate.session.events.log*` (e.g. `true` or `false`):: @@ -610,12 +610,12 @@ The default value of this setting is determined by the value for `hibernate.gene [[configurations-cache]] === Cache Properties -`*hibernate.cache.region.factory_class*` (e.g. `org.hibernate.cache.infinispan.InfinispanRegionFactory`):: -The fully-qualified name of the `RegionFactory` implementation class. +`*hibernate.cache.region.factory_class*` (e.g. `jcache`):: +Either a shortcut name (e.g. `jcache`, `ehcache`) or the fully-qualified name of the `RegionFactory` implementation class. `*hibernate.cache.default_cache_concurrency_strategy*`:: Setting used to give the name of the default https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/CacheConcurrencyStrategy.html[`CacheConcurrencyStrategy`] to use -when either `@javax.persistence.Cacheable` or `@org.hibernate.annotations.Cache`. `@org.hibernate.annotations.Cache` is used to override the global setting. +when either `@javax.persistence.Cacheable` or `@org.hibernate.annotations.Cache`. `@org.hibernate.annotations.Cache` is used to override the global setting. `*hibernate.cache.use_minimal_puts*` (e.g. `true` (default value) or `false`):: Optimizes second-level cache operation to minimize writes, at the cost of more frequent reads. This is most useful for clustered caches and is enabled by default for clustered cache implementations. @@ -626,7 +626,7 @@ Enables the query cache. You still need to set individual queries to be cachable `*hibernate.cache.use_second_level_cache*` (e.g. `true` (default value) or `false`):: Enable/disable the second level cache, which is enabled by default, although the default `RegionFactor` is `NoCachingRegionFactory` (meaning there is no actual caching implementation). -`*hibernate.cache.query_cache_factory*` (e.g. Fully-qualified classname):: +`*hibernate.cache.query_cache_factory*` (e.g. Fully-qualified class name):: A custom https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/cache/spi/QueryCacheFactory.html[`QueryCacheFactory`] interface. The default is the built-in `StandardQueryCacheFactory`. `*hibernate.cache.region_prefix*` (e.g. A string):: @@ -639,7 +639,7 @@ Forces Hibernate to store data in the second-level cache in a more human-readabl Enables the automatic eviction of a bi-directional association's collection cache when an element in the `ManyToOne` collection is added/updated/removed without properly managing the change on the `OneToMany` side. `*hibernate.cache.use_reference_entries*` (e.g. `true` or `false`):: -Optimizes second-level cache operation to store immutable entities (aka "reference") which do not have associations into cache directly, this case, lots of disasseble and deep copy operations can be avoid. Default value of this property is `false`. +Optimizes second-level cache operation to store immutable entities (aka "reference") which do not have associations into cache directly, this case, disassembling and deep copy operations can be avoided. The default value of this property is `false`. `*hibernate.ejb.classcache*` (e.g. `hibernate.ejb.classcache.org.hibernate.ejb.test.Item` = `read-write`):: Sets the associated entity class cache concurrency strategy for the designated region. Caching configuration should follow the following pattern `hibernate.ejb.classcache.` usage[, region] where usage is the cache strategy used and region the cache region name. @@ -650,17 +650,8 @@ Sets the associated collection cache concurrency strategy for the designated reg [[configurations-infinispan]] === Infinispan properties -`*hibernate.cache.infinispan.cfg*` (e.g. `org/hibernate/cache/infinispan/builder/infinispan-configs.xml`):: -Classpath or filesystem resource containing the Infinispan configuration settings. - -`*hibernate.cache.infinispan.statistics*`:: -Property name that controls whether Infinispan statistics are enabled. The property value is expected to be a boolean true or false, and it overrides statistic configuration in base Infinispan configuration, if provided. - -`*hibernate.cache.infinispan.use_synchronization*`:: -Deprecated setting because Infinispan is designed to always register a `Synchronization` for `TRANSACTIONAL` caches. - -`*hibernate.cache.infinispan.cachemanager*` (e.g. There is no default value, the user must specify the property.):: -Specifies the JNDI name under which the `EmbeddedCacheManager` is bound. +For more details about how to customize the Infinispan second-level cache provider, check out the +https://infinispan.org/docs/stable/titles/integrating/integrating.html#configuration_properties[Infinispan User Guide]. [[configurations-transactions]] === Transactions properties @@ -797,21 +788,21 @@ Specifies the minor version of the underlying database, as would be returned by This value is used to help more precisely determine how to perform schema generation tasks for the underlying database in cases where `javax.persistence.database-product-name` and `javax.persistence.database-major-version` does not provide enough distinction. `*javax.persistence.schema-generation.create-source*`:: -Specifies whether schema generation commands for schema creation are to be determine based on object/relational mapping metadata, DDL scripts, or a combination of the two. +Specifies whether schema generation commands for schema creation are to be determined based on object/relational mapping metadata, DDL scripts, or a combination of the two. See https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/SourceType.html[`SourceType`] for valid set of values. + If no value is specified, a default is assumed as follows: + -* if source scripts are specified (per `javax.persistence.schema-generation.create-script-source`), then `scripts` is assumed +* if source scripts are specified (per `javax.persistence.schema-generation.create-script-source`), then `script` is assumed * otherwise, `metadata` is assumed `*javax.persistence.schema-generation.drop-source*`:: -Specifies whether schema generation commands for schema dropping are to be determine based on object/relational mapping metadata, DDL scripts, or a combination of the two. +Specifies whether schema generation commands for schema dropping are to be determined based on object/relational mapping metadata, DDL scripts, or a combination of the two. See https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tool/schema/SourceType.html[`SourceType`] for valid set of values. + If no value is specified, a default is assumed as follows: + -* if source scripts are specified (per `javax.persistence.schema-generation.create-script-source`), then `scripts` is assumed +* if source scripts are specified (per `javax.persistence.schema-generation.create-script-source`), then the `script` option is assumed * otherwise, `metadata` is assumed `*javax.persistence.schema-generation.create-script-source*`:: @@ -830,7 +821,7 @@ For cases where the `javax.persistence.schema-generation.scripts.action` value i `*javax.persistence.hibernate.hbm2ddl.import_files*` (e.g. `import.sql` (default value)):: Comma-separated names of the optional files containing SQL DML statements executed during the `SessionFactory` creation. -File order matters, the statements of a give file are executed before the statements of the following one. +File order matters, the statements of a given file are executed before the statements of the following one. + These statements are only executed if the schema is created, meaning that `hibernate.hbm2ddl.auto` is set to `create`, `create-drop`, or `update`. `javax.persistence.schema-generation.create-script-source` / `javax.persistence.schema-generation.drop-script-source` should be preferred. @@ -906,6 +897,12 @@ Whether the schema migration tool should halt on error, therefore terminating th `*hibernate.jdbc.sql_exception_converter*` (e.g. Fully-qualified name of class implementing `SQLExceptionConverter`):: The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/exception/spi/SQLExceptionConverter.html[`SQLExceptionConverter`] to use for converting `SQLExceptions` to Hibernate's `JDBCException` hierarchy. The default is to use the configured https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html[`Dialect`]'s preferred `SQLExceptionConverter`. +`*hibernate.native_exception_handling_51_compliance*` (e.g. `true` or `false` (default value)):: +Indicates if exception handling for a `SessionFactory` built via Hibernate's native bootstrapping +should behave the same as native exception handling in Hibernate ORM 5.1. When set to `true`, +`HibernateException` will be not wrapped or converted according to the JPA specification. This +setting will be ignored for a `SessionFactory` built via JPA bootstrapping. + [[configurations-session-events]] === Session events @@ -1002,7 +999,7 @@ Names the `ClassLoader` used to load user application classes. Names the `ClassLoader` Hibernate should use to perform resource loading. `*hibernate.classLoader.hibernate*`:: -Names the `ClassLoader` responsible for loading Hibernate classes. By default this is the `ClassLoader` that loaded this class. +Names the `ClassLoader` responsible for loading Hibernate classes. By default, this is the `ClassLoader` that loaded this class. `*hibernate.classLoader.environment*`:: Names the `ClassLoader` used when Hibernate is unable to locates classes on the `hibernate.classLoader.application` or `hibernate.classLoader.hibernate`. @@ -1011,13 +1008,13 @@ Names the `ClassLoader` used when Hibernate is unable to locates classes on the === Bootstrap properties `*hibernate.integrator_provider*` (e.g. The fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/IntegratorProvider.html[`IntegratorProvider`]):: -Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/integrator/spi/Integrator.html[`Integrator`] which are used during bootstrap process to integrate various services. +Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/integrator/spi/Integrator.html[`Integrator`] which is used during the bootstrap process to integrate various services. `*hibernate.strategy_registration_provider*` (e.g. The fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/StrategyRegistrationProviderList.html[`StrategyRegistrationProviderList`]):: -Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/registry/selector/StrategyRegistrationProvider.html[`StrategyRegistrationProvider`] which are used during bootstrap process to provide registrations of strategy selector(s). +Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/registry/selector/StrategyRegistrationProvider.html[`StrategyRegistrationProvider`] which is used during the bootstrap process to provide registrations of strategy selector(s). `*hibernate.type_contributors*` (e.g. The fully qualified name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/TypeContributorList.html[`TypeContributorList`]):: -Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/model/TypeContributor.html[`TypeContributor`] which are used during bootstrap process to contribute types. +Used to define a list of https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/model/TypeContributor.html[`TypeContributor`] which is used during the bootstrap process to contribute types. `*hibernate.persister.resolver*` (e.g. The fully qualified name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/persister/spi/PersisterClassResolver.html[`PersisterClassResolver`] or a `PersisterClassResolver` instance):: Used to define an implementation of the `PersisterClassResolver` interface which can be used to customize how an entity or a collection is being persisted. @@ -1028,8 +1025,8 @@ Like a `PersisterClassResolver`, the `PersisterFactory` can be used to customize `*hibernate.service.allow_crawling*` (e.g. `true` (default value) or `false`):: Crawl all available service bindings for an alternate registration of a given Hibernate `Service`. -`*hibernate.metadata_builder_contributor*` (e.g. The instance, the class or the fully qualified class name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/MetadataBuilderContributor.html[`MetadataBuilderContributor`]):: -Used to define a instance, the class or the fully qualified class name of an https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/MetadataBuilderContributor.html[`MetadataBuilderContributor`] which can be used to configure the `MetadataBuilder` when bootstrapping via the JPA `EntityManagerFactory`. +`*hibernate.metadata_builder_contributor*` (e.g. The instance, the class or the fully qualified class name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/MetadataBuilderContributor.html[`MetadataBuilderContributor`]):: +Used to define an instance, the class or the fully qualified class name of a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/jpa/boot/spi/MetadataBuilderContributor.html[`MetadataBuilderContributor`] which can be used to configure the `MetadataBuilder` when bootstrapping via the JPA `EntityManagerFactory`. [[configurations-misc]] === Miscellaneous properties @@ -1046,7 +1043,7 @@ If `hibernate.session_factory_name_is_jndi` is set to `true`, this is also the n `*hibernate.session_factory_name_is_jndi*` (e.g. `true` (default value) or `false`):: Does the value defined by `hibernate.session_factory_name` represent a JNDI namespace into which the `org.hibernate.SessionFactory` should be bound and made accessible? + -Defaults to `true` for backwards compatibility. Set this to `false` if naming a SessionFactory is needed for serialization purposes, but no writable JNDI context exists in the runtime environment or if the user simply does not want JNDI to be used. +Defaults to `true` for backward compatibility. Set this to `false` if naming a SessionFactory is needed for serialization purposes, but no writable JNDI context exists in the runtime environment or if the user simply does not want JNDI to be used. `*hibernate.ejb.entitymanager_factory_name*` (e.g. By default, the persistence unit name is used, otherwise a randomly generated UUID):: Internally, Hibernate keeps track of all `EntityManagerFactory` instances using the `EntityManagerFactoryRegistry`. The name is used as a key to identify a given `EntityManagerFactory` reference. @@ -1101,6 +1098,9 @@ Setting which indicates whether or not the new JOINS over collection tables shou `*hibernate.allow_refresh_detached_entity*` (e.g. `true` (default value when using Hibernate native bootstrapping) or `false` (default value when using JPA bootstrapping)):: Setting that allows to call `javax.persistence.EntityManager#refresh(entity)` or `Session#refresh(entity)` on a detached instance even when the `org.hibernate.Session` is obtained from a JPA `javax.persistence.EntityManager`. +`*hibernate.use_entity_where_clause_for_collections*` (e.g., `true` (default) or `false`):: +Setting controls whether an entity's "where" clause, mapped using `@Where(clause="...")` or `, is taken into account when loading one-to-many or many-to-many collections of that type of entity. + `*hibernate.event.merge.entity_copy_observer*` (e.g. `disallow` (default value), `allow`, `log` (testing purpose only) or fully-qualified class name):: Setting that specifies how Hibernate will respond when multiple representations of the same persistent entity ("entity copy") is detected while merging. + diff --git a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Bootstrap.adoc b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Bootstrap.adoc index 58a5edcfc3088110f78525ac9d90b4fa9beb9f3d..350d36062c6340d7a22b6ae398ebb4f05073b139 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Bootstrap.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Bootstrap.adoc @@ -41,7 +41,7 @@ There are other ways to specify Configuration information, including: * Place a file named hibernate.properties in a root directory of the classpath * Pass an instance of java.util.Properties to `Configuration#setProperties` * Via a `hibernate.cfg.xml` file -* System properties using java `-Dproperty=value` +* System properties using Java `-Dproperty=value` == Migration diff --git a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Criteria.adoc b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Criteria.adoc index 76b13178e6f5ea0f383bb949fd677232c871b4e6..95e12ea910e6999bdcecc92c314ad4bb440805da 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Criteria.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Criteria.adoc @@ -26,7 +26,7 @@ List cats = crit.list(); ---- [[criteria-entity-name]] -=== JPA vs Hibernate entity name +=== JPA vs. Hibernate entity name When using the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/SharedSessionContract.html#createCriteria-java.lang.String-[`Session#createCriteria(String entityName)` or `StatelessSession#createCriteria(String entityName)`], the *entityName* means the fully-qualified name of the underlying entity and not the name denoted by the `name` attribute of the JPA `@Entity` annotation. @@ -228,7 +228,7 @@ List cats = session.createCriteria( Cat.class ) This will return all of the `Cat`s with a mate whose name starts with "good" ordered by their mate's age, and all cats who do not have a mate. This is useful when there is a need to order or limit in the database prior to returning complex/large result sets, -and removes many instances where multiple queries would have to be performed and the results unioned by java in memory. +and removes many instances where multiple queries would have to be performed and the results unioned by Java in memory. Without this feature, first all of the cats without a mate would need to be loaded in one query. @@ -275,7 +275,7 @@ When using criteria against collections, there are two distinct cases. One is if the collection contains entities (eg. `` or ``) or components (`` ), and the second is if the collection contains scalar values (``). In the first case, the syntax is as given above in the section <> where we restrict the `kittens` collection. -Essentially we create a `Criteria` object against the collection property and restrict the entity or component properties using that instance. +Essentially, we create a `Criteria` object against the collection property and restrict the entity or component properties using that instance. For querying a collection of basic values, we still create the `Criteria` object against the collection, but to reference the value, we use the special property "elements". diff --git a/documentation/src/main/asciidoc/userguide/appendices/Legacy_DomainModel.adoc b/documentation/src/main/asciidoc/userguide/appendices/Legacy_DomainModel.adoc index 300b44a5c33c8a2379252e3dcb467be1502e943f..43f92dc654d55b932e2c82419256eacc6a02e0b7 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Legacy_DomainModel.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Legacy_DomainModel.adoc @@ -37,7 +37,7 @@ include::{sourcedir}/timestamp_version.xml[] |column |The name of the column which holds the timestamp. Optional, defaults to the property name |name |The name of a JavaBeans style property of Java type `Date` or `Timestamp` of the persistent class. |access |The strategy Hibernate uses to access the property value. Optional, defaults to `property`. -|unsaved-value |A version property which indicates than instance is newly instantiated, and unsaved. +|unsaved-value |A version property which indicates that the instance is newly instantiated and unsaved. This distinguishes it from detached instances that were saved or loaded in a previous session. The default value of `undefined` indicates that Hibernate uses the identifier property value. |source |Whether Hibernate retrieves the timestamp from the database or the current JVM. diff --git a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Native_Queries.adoc b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Native_Queries.adoc index 6d6c3c885e7c92d66b64535a2fb6c894ad44083a..955911a3ae6f20845b8e7b56bca6b697b09cf3bf 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Legacy_Native_Queries.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Legacy_Native_Queries.adoc @@ -102,7 +102,7 @@ You can externalize the resultset mapping information in a `` element ---- ==== -You can, alternatively, use the resultset mapping information in your hbm files directly in java code. +You can, alternatively, use the resultset mapping information in your hbm files directly in Java code. .Programmatically specifying the result mapping information ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/architecture/Architecture.adoc b/documentation/src/main/asciidoc/userguide/chapters/architecture/Architecture.adoc index dbe41e9a0fa6fb88094cf47758c1bee74ff8bae4..58123d50b8e6b2753d53863f2f4115f1f4ac4115 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/architecture/Architecture.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/architecture/Architecture.adoc @@ -16,7 +16,7 @@ As a JPA provider, Hibernate implements the Java Persistence API specifications image:images/architecture/JPA_Hibernate.svg[image] SessionFactory (`org.hibernate.SessionFactory`):: A thread-safe (and immutable) representation of the mapping of the application domain model to a database. -Acts as a factory for `org.hibernate.Session` instances. The `EntityManagerFactory` is the JPA equivalent of a `SessionFactory` and basically those two converge into the same `SessionFactory` implementation. +Acts as a factory for `org.hibernate.Session` instances. The `EntityManagerFactory` is the JPA equivalent of a `SessionFactory` and basically, those two converge into the same `SessionFactory` implementation. + A `SessionFactory` is very expensive to create, so, for any given database, the application should have only one associated `SessionFactory`. The `SessionFactory` maintains services that Hibernate uses across all `Session(s)` such as second level caches, connection pools, transaction system integrations, etc. diff --git a/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc b/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc index 443e3fa70e04515c7b2e65cc0914f6d9022675a7..13404f0d69723ae2193a0fa69bdc91ac2c4495da 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc @@ -67,7 +67,7 @@ include::{sourcedir}/BatchTest.java[tags=batch-session-batch-example] There are several problems associated with this example: . Hibernate caches all the newly inserted `Customer` instances in the session-level c1ache, so, when the transaction ends, 100 000 entities are managed by the persistence context. - If the maximum memory allocated to the JVM is rather low, this example could fails with an `OutOfMemoryException`. + If the maximum memory allocated to the JVM is rather low, this example could fail with an `OutOfMemoryException`. The Java 1.8 JVM allocated either 1/4 of available RAM or 1Gb, which can easily accommodate 100 000 objects on the heap. . long-running transactions can deplete a connection pool so other transactions don't get a chance to proceed. . JDBC batching is not enabled by default, so every insert statement requires a database roundtrip. @@ -118,7 +118,7 @@ However, it is good practice to close the `ScrollableResults` explicitly. `StatelessSession` is a command-oriented API provided by Hibernate. Use it to stream data to and from the database in the form of detached objects. -A `StatelessSession` has no persistence context associated with it and does not provide many of the higher-level life cycle semantics. +A `StatelessSession` has no persistence context associated with it and does not provide many of the higher-level lifecycle semantics. Some of the things not provided by a `StatelessSession` include: @@ -243,8 +243,8 @@ include::{sourcedir}/BatchTest.java[tags=batch-bulk-hql-delete-example] ---- ==== -Method `Query.executeUpdate()` returns an `int` value, which indicates the number of entities effected by the operation. -This may or may not correlate to the number of rows effected in the database. +Method `Query.executeUpdate()` returns an `int` value, which indicates the number of entities affected by the operation. +This may or may not correlate to the number of rows affected in the database. A JPQL/HQL bulk operation might result in multiple SQL statements being executed, such as for joined-subclass. In the example of joined-subclass, a `DELETE` against one of the subclasses may actually result in deletes in the tables underlying the join, or further down the inheritance hierarchy. @@ -280,10 +280,9 @@ Hibernate generates a value automatically. Automatic generation is only available if you use ID generators which operate on the database. Otherwise, Hibernate throws an exception during parsing. Available in-database generators are `org.hibernate.id.SequenceGenerator` and its subclasses, and objects which implement `org.hibernate.id.PostInsertIdentifierGenerator`. -The most notable exception is `org.hibernate.id.TableHiLoGenerator`, which does not expose a selectable way to get its values. For properties mapped as either version or timestamp, the insert statement gives you two options. -You can either specify the property in the properties_list, in which case its value is taken from the corresponding select expressions, or omit it from the properties_list, +You can either specify the property in the properties_list, in which case its value is taken from the corresponding select expressions or omit it from the properties_list, in which case the seed value defined by the org.hibernate.type.VersionType is used. [[batch-bulk-hql-insert-example]] diff --git a/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc b/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc index cd042975c972bf58706f77e001f01bc4fc59900b..ce7f360f4f45a54b0e015208d860b36e8082958e 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc @@ -26,7 +26,7 @@ During the bootstrap process, you might want to customize Hibernate behavior so === Native Bootstrapping This section discusses the process of bootstrapping a Hibernate `SessionFactory`. -Specifically it discusses the bootstrapping APIs as redesigned in 5.0. +Specifically, it addresses the bootstrapping APIs as redesigned in 5.0. For a discussion of the legacy bootstrapping API, see <> [[bootstrap-native-registry]] @@ -110,7 +110,7 @@ include::{sourcedir}/BootstrapTest.java[tags=bootstrap-event-listener-registrati [[bootstrap-native-metadata]] ==== Building the Metadata -The second step in native bootstrapping is the building of a `org.hibernate.boot.Metadata` object containing the parsed representations of an application domain model and its mapping to a database. +The second step in native bootstrapping is the building of an `org.hibernate.boot.Metadata` object containing the parsed representations of an application domain model and its mapping to a database. The first thing we obviously need to build a parsed representation is the source information to be parsed (annotated classes, `hbm.xml` files, `orm.xml` files). This is the purpose of `org.hibernate.boot.MetadataSources`: @@ -133,7 +133,7 @@ If you are ok with the default behavior in building the Metadata then you can si ==== Notice that a `ServiceRegistry` can be passed at a number of points in this bootstrapping process. The suggested approach is to build a `StandardServiceRegistry` yourself and pass that along to the `MetadataSources` constructor. -From there, `MetadataBuilder`, `Metadata`, `SessionFactoryBuilder` and `SessionFactory` will all pick up that same `StandardServiceRegistry`. +From there, `MetadataBuilder`, `Metadata`, `SessionFactoryBuilder`, and `SessionFactory` will all pick up that same `StandardServiceRegistry`. ==== However, if you wish to adjust the process of building `Metadata` from `MetadataSources`, @@ -156,7 +156,7 @@ include::{sourcedir}/BootstrapTest.java[tags=bootstrap-native-metadata-builder-e The final step in native bootstrapping is to build the `SessionFactory` itself. Much like discussed above, if you are ok with the default behavior of building a `SessionFactory` from a `Metadata` reference, you can simply call the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/Metadata.html#buildSessionFactory--[`buildSessionFactory`] method on the `Metadata` object. -However, if you would like to adjust that building process you will need to use `SessionFactoryBuilder` as obtained via [`Metadata#getSessionFactoryBuilder`. Again, see its https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/Metadata.html#getSessionFactoryBuilder--[Javadocs] for more details. +However, if you would like to adjust that building process, you will need to use `SessionFactoryBuilder` as obtained via [`Metadata#getSessionFactoryBuilder`. Again, see its https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/boot/Metadata.html#getSessionFactoryBuilder--[Javadocs] for more details. [[bootstrap-native-SessionFactory-example]] .Native Bootstrapping - Putting it all together @@ -291,22 +291,22 @@ JPA offers two mapping options: - annotations - XML mappings -Although annotations are much more common, there are projects were XML mappings are preferred. +Although annotations are much more common, there are projects where XML mappings are preferred. You can even mix annotations and XML mappings so that you can override annotation mappings with XML configurations that can be easily changed without recompiling the project source code. -This is possible because if there are two conflicting mappings, the XML mappings takes precedence over its annotation counterpart. +This is possible because if there are two conflicting mappings, the XML mappings take precedence over its annotation counterpart. -The JPA specifications requires the XML mappings to be located on the class path: +The JPA specification requires the XML mappings to be located on the classpath: [quote, Section 8.2.1.6.2 of the JPA 2.1 Specification] ____ An object/relational mapping XML file named `orm.xml` may be specified in the `META-INF` directory in the root of the persistence unit or in the `META-INF` directory of any jar file referenced by the `persistence.xml`. -Alternatively, or in addition, one or more mapping files may be referenced by the mapping-file elements of the persistence-unit element. These mapping files may be present anywhere on the class path. +Alternatively, or in addition, one or more mapping files may be referenced by the mapping-file elements of the persistence-unit element. These mapping files may be present anywhere on the classpath. ____ -Therefore, the mapping files can reside in the application jar artifacts, or they can be stored in an external folder location with the cogitation that that location be included in the class path. +Therefore, the mapping files can reside in the application jar artifacts, or they can be stored in an external folder location with the cogitation that that location be included in the classpath. -Hibernate is more lenient in this regard so you can use any external location even outside of the application configured class path. +Hibernate is more lenient in this regard so you can use any external location even outside of the application configured classpath. [[bootstrap-jpa-compliant-persistence-xml-external-mappings-example]] .META-INF/persistence.xml configuration file for external XML mappings diff --git a/documentation/src/main/asciidoc/userguide/chapters/bytecode/BytecodeEnhancement.adoc b/documentation/src/main/asciidoc/userguide/chapters/bytecode/BytecodeEnhancement.adoc new file mode 100644 index 0000000000000000000000000000000000000000..f79afe18da845999de31cac516fcf4ad57642f18 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/bytecode/BytecodeEnhancement.adoc @@ -0,0 +1,23 @@ +[[enhancement]] +== Enhancement +:sourcedir: ../../../../../test/java/org/hibernate/userguide/bytecode +:extrasdir: extras + +Hibernate offers a number of services that can be added into an application's domain model classes +through bytecode enhancement... + + +[[enhancement-laziness]] +=== Laziness + + +[[enhancement-bidir]] +=== Bi-directionality + + +[[enhancement-dirty]] +=== In-line dirty checking + + +[[enhancement-extended]] +=== Extended enhancement \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc index c12af7803fb89ba8a7adf72f6b7cfa330fce4c71..b66d8fafc62b72ec14d10e795419b0a1e2ea6553 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc @@ -35,9 +35,9 @@ Detailed information is provided later in this chapter. Besides specific provider configuration, there are a number of configurations options on the Hibernate side of the integration that control various caching behaviors: `hibernate.cache.use_second_level_cache`:: - Enable or disable second level caching overall. Default is true, although the default region factory is `NoCachingRegionFactory`. + Enable or disable second level caching overall. The default is true, although the default region factory is `NoCachingRegionFactory`. `hibernate.cache.use_query_cache`:: - Enable or disable second level caching of query results. Default is false. + Enable or disable second level caching of query results. The default is false. `hibernate.cache.query_cache_factory`:: Query result caching is handled by a special contract that deals with staleness-based invalidation of the results. The default implementation does not allow stale results at all. Use this for applications that would like to relax that. @@ -48,7 +48,7 @@ Besides specific provider configuration, there are a number of configurations op Defines a name to be used as a prefix to all second-level cache region names. `hibernate.cache.default_cache_concurrency_strategy`:: In Hibernate second-level caching, all regions can be configured differently including the concurrency strategy to use when accessing that particular region. - This setting allows to define a default strategy to be used. + This setting allows defining a default strategy to be used. This setting is very rarely required as the pluggable providers do specify the default strategy to use. Valid values include: * read-only, @@ -61,12 +61,12 @@ Besides specific provider configuration, there are a number of configurations op `hibernate.cache.auto_evict_collection_cache`:: Enables or disables the automatic eviction of a bidirectional association's collection cache entry when the association is changed just from the owning side. This is disabled by default, as it has a performance impact to track this state. - However if your application does not manage both sides of bidirectional association where the collection side is cached, + However, if your application does not manage both sides of bidirectional association where the collection side is cached, the alternative is to have stale data in that collection cache. `hibernate.cache.use_reference_entries`:: Enable direct storage of entity references into the second level cache for read-only or immutable entities. `hibernate.cache.keys_factory`:: - When storing entries into second-level cache as key-value pair, the identifiers can be wrapped into tuples + When storing entries into the second-level cache as a key-value pair, the identifiers can be wrapped into tuples to guarantee uniqueness in case that second-level cache stores all entities in single space. These tuples are then used as keys in the cache. When the second-level cache implementation (incl. its configuration) guarantees that different entity types are stored separately and multi-tenancy is not @@ -380,7 +380,7 @@ When using http://docs.oracle.com/javaee/7/api/javax/persistence/CacheStoreMode. Hibernate will selectively force the results cached in that particular region to be refreshed. This is particularly useful in cases where underlying data may have been updated via a separate process -and is a far more efficient alternative to bulk eviction of the region via `SessionFactory` eviction which looks as follows: +and is a far more efficient alternative to the bulk eviction of the region via `SessionFactory` eviction which looks as follows: [source, JAVA, indent=0] ---- @@ -399,14 +399,14 @@ and retrieval (http://docs.oracle.com/javaee/7/api/javax/persistence/CacheRetrie The relationship between Hibernate and JPA cache modes can be seen in the following table: .Cache modes relationships -[cols=",,,",options="header",] +[cols=",,",options="header",] |====================================== |Hibernate | JPA | Description -|`CacheMode.NORMAL` |`CacheStoreMode.USE` and `CacheRetrieveMode.USE` | Default. Reads/writes data from/into cache +|`CacheMode.NORMAL` |`CacheStoreMode.USE` and `CacheRetrieveMode.USE` | Default. Reads/writes data from/into the cache |`CacheMode.REFRESH` |`CacheStoreMode.REFRESH` and `CacheRetrieveMode.BYPASS` | Doesn't read from cache, but writes to the cache upon loading from the database |`CacheMode.PUT` |`CacheStoreMode.USE` and `CacheRetrieveMode.BYPASS` | Doesn't read from cache, but writes to the cache as it reads from the database |`CacheMode.GET` |`CacheStoreMode.BYPASS` and `CacheRetrieveMode.USE` | Read from the cache, but doesn't write to cache -|`CacheMode.IGNORE` |`CacheStoreMode.BYPASS` and `CacheRetrieveMode.BYPASS` | Doesn't read/write data from/into cache +|`CacheMode.IGNORE` |`CacheStoreMode.BYPASS` and `CacheRetrieveMode.BYPASS` | Doesn't read/write data from/into the cache |====================================== Setting the cache mode can be done either when loading entities directly or when executing a query. @@ -507,7 +507,7 @@ include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-statistics-example] [NOTE] ==== -Use of the build-in integration for https://jcp.org/en/jsr/detail?id=107[JCache] requires that the `hibernate-jcache` module jar (and all of its dependencies) are on the classpath. +Use of the built-in integration for https://jcp.org/en/jsr/detail?id=107[JCache] requires that the `hibernate-jcache` module jar (and all of its dependencies) are on the classpath. In addition a JCache implementation needs to be added as well. A list of compatible implementations can be found https://jcp.org/aboutJava/communityprocess/implementations/jsr107/index.html[on the JCP website]. An alternative source of compatible implementations can be found through https://github.com/cruftex/jsr107-test-zoo[the JSR-107 test zoo]. @@ -585,7 +585,7 @@ and also log a warning about the missing cache. Note that caches created this way may be very badly configured (unlimited size and no eviction in particular) unless the cache provider was explicitly configured to use a specific configuration for default caches. -Ehcache in particular allows to set such default configuration using cache templates, +Ehcache, in particular, allows to set such default configuration using cache templates, see http://www.ehcache.org/documentation/3.0/107.html#supplement-jsr-107-configurations ==== @@ -596,7 +596,7 @@ This integration covers Ehcache 2.x, in order to use Ehcache 3.x as second level [NOTE] ==== -Use of the build-in integration for http://www.ehcache.org/[Ehcache] requires that the `hibernate-ehcache` module jar (and all of its dependencies) are on the classpath. +Use of the built-in integration for http://www.ehcache.org/[Ehcache] requires that the `hibernate-ehcache` module jar (and all of its dependencies) are on the classpath. ==== [[caching-provider-ehcache-region-factory]] @@ -616,7 +616,7 @@ To use the `EhCacheRegionFactory`, you need to specify the following configurati ---- + value="ehcache"/> ---- ==== @@ -635,7 +635,7 @@ To use the `SingletonEhCacheRegionFactory`, you need to specify the following co ---- + value="ehcache-singleton"/> ---- ==== @@ -677,401 +677,9 @@ unless an appropriate `` entry is added to the Ehcache configurati [[caching-provider-infinispan]] === Infinispan -[NOTE] -==== -Use of the build-in integration for http://infinispan.org/[Infinispan] requires that the `hibernate-infinispan module` jar (and all of its dependencies) are on the classpath. -==== - -How to configure Infinispan to be the second level cache provider varies slightly depending on the deployment scenario: - -==== Single Node Local - -In standalone library mode, a JPA/Hibernate application runs inside a Java SE application or inside containers that don't offer Infinispan integration. - -Enabling Infinispan second level cache provider inside a JPA/Hibernate application that runs in single node is very straightforward. -First, make sure the Hibernate Infinispan cache provider (and its dependencies) are available in the classpath, then modify the persistence.xml to include these properties: - -==== -[source, XML, indent=0] ----- - - - - - ----- -==== - -Plugging in Infinispan as second-level cache provider requires at the bare minimum that `hibernate.cache.region.factory_class` is set to an Infinispan region factory implementation. -Normally, this is `org.hibernate.cache.infinispan.InfinispanRegionFactory` but other region factories are possible in alternative scenarios (see <> section for more info). - -By default, the Infinispan second-level cache provider uses an Infinispan configuration that's designed for clustered environments. -However, since this section is focused on running Infinispan second-level cache provider in a single node, an Infinispan configuration designed for local environments is recommended. -To enable that configuration, set `hibernate.cache.infinispan.cfg` to `org/hibernate/cache/infinispan/builder/infinispan-configs-local.xml` value. - -The next section focuses on analysing how the default local configuration works. -Changing Infinispan configuration options can be done following the instructions in <> section. - -===== Default Local Configuration - -Infinispan second-level cache provider comes with a configuration designed for local, single node, environments. -These are the characteristics of such configuration: - -* Entities, collections, queries and timestamps are stored in non-transactional local caches. - -* Entities and collections query caches are configured with the following eviction settings. -You can change these settings on a per entity or collection basis or per individual entity or collection type. -More information in the <> section below. - - Eviction wake up interval is 5 seconds. - - Max number of entries are 10,000 - - Max idle time before expiration is 100 seconds - - Default eviction algorithm is LRU, least recently used. - -* _No eviction/expiration is configured for timestamp caches_, nor it's allowed. - -===== Local Cache Strategies - -Before version 5.0, Infinispan only supported `transactional` and `read-only` strategies. - -Starting with version 5.0, all cache strategies are supported: `transactional`, `read-write`, `nonstrict-read-write` and `read-only`. - -==== Multi Node Cluster - -When running a JPA/Hibernate in a multi-node environment and enabling Infinispan second-level cache, it is necessary to cluster the second-level cache so that cache consistency can be guaranteed. -Clustering the Infinispan second-level cache provider is as simple as adding the following properties: - -==== -[source, XML, indent=0] ----- - - ----- -==== - -As with the standalone local mode, at the bare minimum the region factory has to be configured to point to an Infinispan region factory implementation. - -However, the default Infinispan configuration used by the second-level cache provider is already configured to work in a cluster environment, so no need to add any extra properties. - -The next section focuses on analysing how the default cluster configuration works. -Changing Infinispan configuration options can be done following the instructions in <> section. - -===== Default Cluster Configuration [[integrations:infinispan-2lc-cluster-configuration]] - -Infinispan second-level cache provider default configuration is designed for multi-node clustered environments. -The aim of this section is to explain the default settings for each of the different global data type caches (entity, collection, query and timestamps), why these were chosen and what are the available alternatives. -These are the characteristics of such configuration: - -* For all entities and collections, whenever a new _entity or collection is read from database_ and needs to be cached, _it's only cached locally_ in order to reduce intra-cluster traffic. -This option can be changed so that entities/collections are cached cluster wide, by switching the entity/collection cache to be replicated or distributed. -How to change this option is explained in the <> section. - -* All _entities and collections are configured to use a synchronous invalidation_ as clustering mode. -This means that when an entity is updated, the updated cache will send a message to the other members of the cluster telling them that the entity has been modified. -Upon receipt of this message, the other nodes will remove this data from their local cache, if it was stored there. -This option can be changed so that both local and remote nodes contain the updates by configuring entities or collections to use a replicated or distributed cache. -With replicated caches all nodes would contain the update, whereas with distributed caches only a subset of the nodes. -How to change this option is explained in the <> section. - -* All _entities and collections have initial state transfer disabled_ since there's no need for it. - -* Entities and collections are configured with the following eviction settings. -You can change these settings on a per entity or collection basis or per individual entity or collection type. -More information in the <> section below. - - Eviction wake up interval is 5 seconds. - - Max number of entries are 10,000 - - Max idle time before expiration is 100 seconds - - Default eviction algorithm is LRU, least recently used. - -* Assuming that query caching has been enabled for the persistence unit (see <>), the query cache is configured so that _queries are only cached locally_. -Alternatively, you can configure query caching to use replication by selecting the `replicated-query` as query cache name. -However, replication for query cache only makes sense if, and only if, all of this conditions are true: - - Performing the query is quite expensive. - - The same query is very likely to be repeatedly executed on different cluster nodes. - - The query is unlikely to be invalidated out of the cache (Note: Hibernate must aggressively invalidate query results from the cache any time any instance of one of the entity types targeted by the query. All such query results are invalidated, even if the change made to the specific entity instance would not have affected the query result) - -* _query cache_ uses the _same eviction/expiration settings as for entities/collections_. - -* _query cache has initial state transfer disabled_ . It is not recommended that this is enabled. - -* The _timestamps cache is configured with asynchronous replication_ as clustering mode. -Local or invalidated cluster modes are not allowed, since all cluster nodes must store all timestamps. -As a result, _no eviction/expiration is allowed for timestamp caches either_. - -[IMPORTANT] -==== -Asynchronous replication was selected as default for timestamps cache for performance reasons. -A side effect of this choice is that when an entity/collection is updated, for a very brief period of time stale queries might be returned. -It's important to note that due to how Infinispan deals with asynchronous replication, stale queries might be found even query is done right after an entity/collection update on same node. -The reason why asynchronous replication works this way is because there's a single node that's owner for a given key, and that enables changes to be applied in the same order in all nodes. -Without it, it could happen that an older value could replace a newer value in certain nodes. -==== - -[NOTE] -==== -Hibernate must aggressively invalidate query results from the cache any time any instance of one of the entity types is modified. All cached query results referencing given entity type are invalidated, even if the change made to the specific entity instance would not have affected the query result. -The timestamps cache plays here an important role - it contains last modification timestamp for each entity type. After a cached query results is loaded, its timestamp is compared to all timestamps of the entity types that are referenced in the query and if any of these is higher, the cached query result is discarded and the query is executed against DB. -==== - -===== Cluster Cache Strategies - -Before version 5.0, Infinispan only supported `transactional` and `read-only` strategies on top of _transactional invalidation_ caches. - -Since version 5.0, Infinispan currently supports all cache concurrency modes in cluster mode, although not all combinations of configurations are compatible: - -* _non-transactional invalidation_ caches are supported as well with `read-write` strategy. The actual setting of cache concurrency mode (`read-write` vs. `transactional`) is not honored, the appropriate strategy is selected based on the cache configuration (_non-transactional_ vs. _transactional_). -* `read-write` mode is supported on _non-transactional distributed/replicated_ caches, however, eviction should not be used in this configuration. Use of eviction can lead to consistency issues. Expiration (with reasonably long max-idle times) can be used. -* `nonstrict-read-write` mode is supported on _non-transactional distributed/replicated_ caches, but the eviction should be turned off as well. In addition to that, the entities must use versioning. This mode mildly relaxes the consistency - between DB commit and end of transaction commit a stale read (see <>) may occur in another transaction. However this strategy uses less RPCs and can be more performant than the other ones. -* `read-only` mode is supported on both _transactional_ and _non-transactional_ _invalidation_ caches and _non-transactional distributed/replicated_ caches, but use of this mode currently does not bring any performance gains. - -The available combinations are summarized in table below: - -[[caching-provider-infinispan-compatibility-table]] -.Cache concurrency strategy/cache mode compatibility table -[options="header"] -|=== -|Concurrency strategy|Cache transactions|Cache mode |Eviction -|transactional |transactional |invalidation |yes -|read-write |non-transactional |invalidation |yes -|read-write |non-transactional |distributed/replicated |no -|nonstrict-read-write|non-transactional |distributed/replicated |no -|=== - -Changing caches to behave different to the default behaviour explained in previous section is explained in <> section. - -[[caching-provider-infinispan-stale-read-example]] -.Stale read with `nonstrict-read-write` strategy -==== -[source, indent=0] ----- -A=0 (non-cached), B=0 (cached in 2LC) -TX1: write A = 1, write B = 1 -TX1: start commit -TX1: commit A, B in DB -TX2: read A = 1 (from DB), read B = 0 (from 2LC) // breaks transactional atomicity -TX1: update A, B in 2LC -TX1: end commit -Tx3: read A = 1, B = 1 // reads after TX1 commit completes are consistent again ----- -==== - -[[caching-provider-infinispan-region-factory]] -==== Alternative RegionFactory - -In standalone environments or managed environments with no Infinispan integration, `org.hibernate.cache.infinispan.InfinispanRegionFactory` should be the choice for region factory implementation. -However, it might be sometimes desirable for the Infinispan cache manager to be shared between different JPA/Hibernate applications, for example to share intra-cluster communications channels. -In this case, the Infinispan cache manager could be bound into JNDI and the JPA/Hibernate applications could use an alternative region factory implementation: - -[[caching-provider-infinispan-region-factory-jndi-example]] -.`JndiInfinispanRegionFactory` configuration -==== -[source, XML, indent=0] ----- - - - ----- -==== - -==== Inside Wildfly - -In WildFly, Infinispan is the default second level cache provider for JPA/Hibernate. -When using JPA in WildFly, region factory is automatically set upon configuring `hibernate.cache.use_second_level_cache=true` (by default second-level cache is not used). - -You can find details about its configuration in link:{wildflydocroot}/JPA%20Reference%20Guide[the JPA reference guide], in particular, in the link:{wildflydocroot}/JPA%20Reference%20Guide#JPAReferenceGuide-UsingtheInfinispansecondlevelcache[second level cache] section. - -The default second-level cache configurations used by Wildfly match the configurations explained above both for local and clustered environments. -So, an Infinispan based second-level cache should behave exactly the same standalone and within containers that provide Infinispan second-level cache as default for JPA/Hibernate. - -[IMPORTANT] -==== -Remember that if deploying to Wildfly or Application Server, the way some Infinispan second level cache provider configuration is defined changes slightly because the properties must include deployment and persistence information. -Check the <> section for more details. -==== - -[[caching-provider-infinispan-config]] -==== Configuration properties - -As explained above, Infinispan second-level cache provider comes with default configuration in `infinispan-config.xml` that is suited for clustered use. -If there's only single JVM accessing the DB, you can use more performant `infinispan-config-local.xml` by setting the `hibernate.cache.infinispan.cfg` property. -If you require further tuning of the cache, you can provide your own configuration. -Caches that are not specified in the provided configuration will default to `infinispan-config.xml` (if the provided configuration uses clustering) or `infinispan-config-local.xml`. - -[WARNING] -==== -It is not possible to specify the configuration this way in WildFly. -Cache configuration changes in Wildfly should be done either modifying the cache configurations inside the application server configuration, or creating new caches with the desired tweaks and plugging them accordingly. -See examples below on how entity/collection specific configurations can be applied. -==== - -[[caching-provider-infinispan-config-example]] -.Use custom Infinispan configuration -==== -[source, XML, indent=0] ----- - ----- -==== - -[NOTE] -==== -If the cache is configured as transactional, InfinispanRegionFactory automatically sets transaction manager so that the TM used by Infinispan is the same as TM used by Hibernate. -==== - -Cache configuration can differ for each type of data stored in the cache. In order to override the cache configuration template, use property `hibernate.cache.infinispan._data-type_.cfg` where `_data-type_` can be one of: - -`entity`:: Entities indexed by `@Id` or `@EmbeddedId` attribute. -`immutable-entity`:: Entities tagged with `@Immutable` annotation or set as `mutable=false` in mapping file. -`naturalid`:: Entities indexed by their `@NaturalId` attribute. -`collection`:: All collections. -`timestamps`:: Mapping _entity type_ -> _last modification timestamp_. Used for query caching. -`query`:: Mapping _query_ -> _query result_. -`pending-puts`:: Auxiliary caches for regions using invalidation mode caches. - -For specifying cache template for specific region, use region name instead of the `_data-type_`: - -[[caching-provider-infinispan-config-cache-example]] -.Use custom cache template -==== -[source, XML, indent=0] ----- - - - - ----- -==== - -.Use custom cache template in Wildfly -When applying entity/collection level changes inside JPA applications deployed in Wildfly, it is necessary to specify deployment name and persistence unit name: - -==== -[source, XML, indent=0] ----- - - ----- -==== - -[IMPORTANT] -==== -Cache configurations are used only as a template for the cache created for given region (usually each entity hierarchy or collection has its own region). It is not possible to use the same cache for different regions. -==== - -Some options in the cache configuration can also be overridden directly through properties. These are: - -`hibernate.cache.infinispan._something_.eviction.strategy`:: Available options are `NONE`, `LRU` and `LIRS`. -`hibernate.cache.infinispan._something_.eviction.max_entries`:: Maximum number of entries in the cache. -`hibernate.cache.infinispan._something_.expiration.lifespan`:: Lifespan of entry from insert into cache (in milliseconds) -`hibernate.cache.infinispan._something_.expiration.max_idle`:: Lifespan of entry from last read/modification (in milliseconds) -`hibernate.cache.infinispan._something_.expiration.wake_up_interval`:: Period of thread checking expired entries. -`hibernate.cache.infinispan.statistics`:: Globally enables/disable Infinispan statistics collection, and their exposure via JMX. - -Example: -==== -[source, XML, indent=0] ----- - - - - - ----- -==== - -With the above configuration, you're overriding whatever eviction/expiration settings were defined for the default entity cache name in the Infinispan cache configuration used, regardless of whether it's the default one or user defined. -More specifically, we're defining the following: - -* All entities to use LRU eviction strategy -* The eviction thread to wake up every 2 seconds (2000 milliseconds) -* The maximum number of entities for each entity type to be 5000 entries -* The lifespan of each entity instance to be 1 minute (600000 milliseconds). -* The maximum idle time for each entity instance to be 30 seconds (30000 milliseconds). - -You can also override eviction/expiration settings on a per entity/collection type basis in such way that the overriden settings only afftect that particular entity (i.e. `com.acme.Person`) or collection type (i.e. `com.acme.Person.addresses`). -Example: - -[source,xml] ----- - ----- -==== - -Inside of Wildfly, same as with the entity/collection configuration override, eviction/expiration settings would also require deployment name and persistence unit information: - -[source,xml] ----- - - ----- -==== - -[NOTE] -==== -In versions prior to 5.1, `hibernate.cache.infinispan._something_.expiration.wake_up_interval` was called `hibernate.cache.infinispan._something_.eviction.wake_up_interval`. -Eviction settings are checked upon each cache insert, it is expiration that needs to be triggered periodically. -The old property still works, but its use is deprecated. -==== - -[NOTE] -==== -Property `hibernate.cache.infinispan.use_synchronization` that allowed to register Infinispan as XA resource in the transaction has been deprecated in 5.0 and is not honored anymore. Infinispan 2LC must register as synchronizations on transactional caches. Also, non-transactional cache modes hook into the current JTA/JDBC transaction as synchronizations. -==== - -[[caching-provider-infinispan-remote]] -==== Remote Infinispan Caching - -Lately, several questions ( link:http://community.jboss.org/message/575814#575814[here] and link:http://community.jboss.org/message/585841#585841[here] ) have appeared in the Infinispan user forums asking whether it'd be possible to have an Infinispan second level cache that instead of living in the same JVM as the Hibernate code, it resides in a remote server, i.e. an Infinispan Hot Rod server. -It's important to understand that trying to set up second level cache in this way is generally not a good idea for the following reasons: - -* The purpose of a JPA/Hibernate second level cache is to store entities/collections recently retrieved from database or to maintain results of recent queries. -So, part of the aim of the second level cache is to have data accessible locally rather than having to go to the database to retrieve it everytime this is needed. -Hence, if you decide to set the second level cache to be remote as well, you're losing one of the key advantages of the second level cache: the fact that the cache is local to the code that requires it. -* Setting a remote second level cache can have a negative impact in the overall performance of your application because it means that cache misses require accessing a remote location to verify whether a particular entity/collection/query is cached. -With a local second level cache however, these misses are resolved locally and so they are much faster to execute than with a remote second level cache. - -There are however some edge cases where it might make sense to have a remote second level cache, for example: +Infinispan is a distributed in-memory key/value data store, available as a cache or data grid, which can be used as a Hibernate 2nd-level cache provider as well. -* You are having memory issues in the JVM where JPA/Hibernate code and the second level cache is running. -Off loading the second level cache to remote Hot Rod servers could be an interesting way to separate systems and allow you find the culprit of the memory issues more easily. -* Your application layer cannot be clustered but you still want to run multiple application layer nodes. -In this case, you can't have multiple local second level cache instances running because they won't be able to invalidate each other for example when data in the second level cache is updated. -In this case, having a remote second level cache could be a way out to make sure your second level cache is always in a consistent state, will all nodes in the application layer pointing to it. -* Rather than having the second level cache in a remote server, you want to simply keep the cache in a separate VM still within the same machine. -In this case you would still have the additional overhead of talking across to another JVM, but it wouldn't have the latency of across a network. -+ -The benefit of doing this is that: -+ -** Size the cache separate from the application, since the cache and the application server have very different memory profiles. -One has lots of short lived objects, and the other could have lots of long lived objects. -** To pin the cache and the application server onto different CPU cores (using _numactl_ ), and even pin them to different physically memory based on the NUMA nodes. +It supports advanced functionality such as transactions, events, querying, distributed processing, off-heap and geographical failover. +For more details, check out the +http://infinispan.org/docs/stable/titles/integrating/integrating.html#integrating_jpa_hibernate[Infinispan User Guide]. \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc index a15aa7ceb082fc33d1af111e8ff1fa30d809656c..40476ea8cb11c7adfeaeebefaebf0839ff447210 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc @@ -123,7 +123,7 @@ include::{extrasdir}/associations-one-to-many-bidirectional-example.sql[] [IMPORTANT] ==== Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times. -The `addPhone()` and `removePhone()` are utilities methods that synchronize both ends whenever a child element is added or removed. +The `addPhone()` and `removePhone()` are utility methods that synchronize both ends whenever a child element is added or removed. ==== Because the `Phone` class has a `@NaturalId` column (the phone number being unique), @@ -146,7 +146,7 @@ include::{extrasdir}/associations-one-to-many-bidirectional-lifecycle-example.sq Unlike the unidirectional `@OneToMany`, the bidirectional association is much more efficient when managing the collection persistence state. Every element removal only requires a single update (in which the foreign key column is set to `NULL`), and, if the child entity lifecycle is bound to its owning parent so that the child cannot exist without its parent, -then we can annotate the association with the `orphan-removal` attribute and disassociating the child will trigger a delete statement on the actual child table row as well. +then we can annotate the association with the `orphan-removal` attribute and dissociate the child will trigger a delete statement on the actual child table row as well. [[associations-one-to-one]] ==== `@OneToOne` @@ -176,7 +176,7 @@ From a relational database point of view, the underlying schema is identical to as the client-side controls the relationship based on the foreign key column. But then, it's unusual to consider the `Phone` as a client-side and the `PhoneDetails` as the parent-side because the details cannot exist without an actual phone. -A much more natural mapping would be if the `Phone` were the parent-side, therefore pushing the foreign key into the `PhoneDetails` table. +A much more natural mapping would be the `Phone` were the parent-side, therefore pushing the foreign key into the `PhoneDetails` table. This mapping requires a bidirectional `@OneToOne` association as you can see in the following example: [[associations-one-to-one-bidirectional]] @@ -254,7 +254,7 @@ see the <> section. +The aforementioned example uses a Hibernate-specific mapping for the link entity since JPA doesn't allow building a composite identifier out of multiple `@ManyToOne` associations. + +For more details, see the <> section. ==== The entity state transitions are better managed than in the previous bidirectional `@ManyToMany` case. @@ -415,15 +416,38 @@ include::{extrasdir}/associations-many-to-many-bidirectional-with-link-entity-li There is only one delete statement executed because, this time, the association is controlled by the `@ManyToOne` side which only has to monitor the state of the underlying foreign key relationship to trigger the right DML statement. [[associations-not-found]] -==== `@NotFound` association mapping +==== `@NotFound` + +When dealing with associations which are not enforced by a physical foreign-key, it is possible +for a non-null foreign-key value to point to a non-existent value on the associated entity's table. + +[WARNING] +==== +Not enforcing physical foreign-keys at the database level is highly discouraged. +==== -When dealing with associations which are not enforced by a Foreign Key, -it's possible to bump into inconsistencies if the child record cannot reference a parent entity. +Hibernate provides support for such models using the `@NotFound` annotation, which accepts a +`NotFoundAction` value which indicates how Hibernate should behave when such broken foreign-keys +are encountered - -By default, Hibernate will complain whenever a child association references a non-existing parent record. -However, you can configure this behavior so that Hibernate can ignore such an Exception and simply assign `null` as a parent object referenced. +EXCEPTION:: (default) Hibernate will throw an exception (`FetchNotFoundException`) +IGNORE:: the association will be treated as `null` + +Both `@NotFound(IGNORE)` and `@NotFound(EXCEPTION)` cause Hibernate to assume that there is +no physical foreign-key. + +`@ManyToOne` and `@OneToOne` associations annotated with `@NotFound` are always fetched eagerly even +if the `fetch` strategy is set to `FetchType.LAZY`. + + +[TIP] +==== +If the application itself manages the referential integrity and can guarantee that there are no +broken foreign-keys, `jakarta.persistence.ForeignKey(NO_CONSTRAINT)` can be used instead. +This will force Hibernate to not export physical foreign-keys, but still behave as if there is +in terms of avoiding the downsides to `@NotFound`. +==== -To ignore non-existing parent entity references, even though not really recommended, it's possible to use the annotation `org.hibernate.annotation.NotFound` annotation with a value of `org.hibernate.annotations.NotFoundAction.IGNORE`. Considering the following `City` and `Person` entity mappings: @@ -439,7 +463,7 @@ include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-domain-model- If we have the following entities in our database: [[associations-not-found-persist-example]] -.`@NotFound` mapping example +.`@NotFound` persist example ==== [source,java] ---- @@ -450,32 +474,87 @@ include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-persist-examp When loading the `Person` entity, Hibernate is able to locate the associated `City` parent entity: [[associations-not-found-find-example]] -.`@NotFound` find existing entity example +.`@NotFound` - find existing entity example ==== [source,java] ---- -include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-find-example,indent=0] +include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-find-baseline,indent=0] ---- ==== -However, if we change the `cityName` attribute to a non-existing city: +However, if we break the foreign-key: [[associations-not-found-non-existing-persist-example]] -.`@NotFound` change to non-existing City example +.Break the foreign-key ==== [source,java] ---- -include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-non-existing-persist-example,indent=0] +include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-break-fk,indent=0] ---- ==== Hibernate is not going to throw any exception, and it will assign a value of `null` for the non-existing `City` entity reference: [[associations-not-found-non-existing-find-example]] -.`@NotFound` find non-existing City example +.`@NotFound` - find non-existing City example ==== [source,java] ---- include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-non-existing-find-example,indent=0] ---- ==== + +`@NotFound` also affects how the association is treated as "implicit joins" in HQL and Criteria. +When there is a physical foreign-key, Hibernate can safely assume that the value in the foreign-key's +key-column(s) will match the value in the target-column(s) because the database makes sure that +is the case. However, `@NotFound` forces Hibernate to perform a physical join for implicit joins +when it might not be needed otherwise. + +Using the `Person` / `City` model, consider the query `from Person p where p.city.id is null`. + +Normally Hibernate would not need the join between the `Person` table and the `City` table because +a physical foreign-key would ensure that any non-null value in the `Person.cityName` column +has a matching non-null value in the `City.name` column. + +However, with `@NotFound` mappings it is possible to have a broken association because there is no +physical foreign-key enforcing the relation. As seen in <>, +the `Person.cityName` column for John Doe has been changed from "New York" to "Atlantis" even though +there is no `City` in the database named "Atlantis". Hibernate is not able to trust the referring +foreign-key value ("Atlantis") has a matching target value, so it must join to the `City` table to +resolve the `city.id` value. + + +[[associations-not-found-implicit-join-example]] +.Implicit join example +==== +[source,java] +---- +include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-implicit-join-example,indent=0] +---- +==== + +Neither result includes a match for "John Doe" because the inner-join filters out that row. + +Hibernate does support a means to refer specifically to the key column (`Person.cityName`) in a query +using the special `fk(..)` function. E.g. + +[[associations-not-found-implicit-join-example]] +.Implicit join example +==== +[source,java] +---- +include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-fk-function-example,indent=0] +---- +==== + +With Hibernate Criteria it is possible to use `Projections.fk(...)` to select the foreign key value of an association +and `Restrictions.fkEq(...)`, `Restrictions.fkNe(...)`, `Restrictions.fkIsNotNull(...)` and ``Restrictions.fkIsNull(...)`, E.g. + +[[associations-not-found-implicit-join-example]] +.Implicit join example +==== +[source,java] +---- +include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-fk-criteria-example,indent=0] +---- +==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc index 972fab0c665692d8549a71c434515741cf3adcd2..97d021bab825e8c2a02735736a4a1889e9bb82b1 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc @@ -84,8 +84,8 @@ Internally Hibernate uses a registry of basic types when it needs to resolve a s [cols=",,,",options="header",] |================================================================================================= |Hibernate type (org.hibernate.spatial package) |JDBC type |Java type |BasicTypeRegistry key(s) -|JTSGeometryType |depends on the dialect | com.vividsolutions.jts.geom.Geometry |jts_geometry, or the classname of Geometry or any of its subclasses -|GeolatteGeometryType |depends on the dialect | org.geolatte.geom.Geometry |geolatte_geometry, or the classname of Geometry or any of its subclasses +|JTSGeometryType |depends on the dialect | com.vividsolutions.jts.geom.Geometry |jts_geometry, or the class name of Geometry or any of its subclasses +|GeolatteGeometryType |depends on the dialect | org.geolatte.geom.Geometry |geolatte_geometry, or the class name of Geometry or any of its subclasses |================================================================================================= [NOTE] @@ -151,7 +151,7 @@ The `@Basic` annotation defines 2 attributes. JPA defines this as "a hint", which essentially means that it effect is specifically required. As long as the type is not primitive, Hibernate takes this to mean that the underlying column should be `NULLABLE`. `fetch` - FetchType (defaults to EAGER):: Defines whether this attribute should be fetched eagerly or lazily. -JPA says that EAGER is a requirement to the provider (Hibernate) that the value should be fetched when the owner is fetched, while LAZY is merely a hint that the value be fetched when the attribute is accessed. +JPA says that EAGER is a requirement to the provider (Hibernate) that the value should be fetched when the owner is fetched, while LAZY is merely a hint that the value is fetched when the attribute is accessed. Hibernate ignores this setting for basic types unless you are using bytecode enhancement. See the <> for additional information on fetching and on bytecode enhancement. @@ -188,7 +188,7 @@ or its `org.hibernate.type.IntegerType` for mapping `java.lang.Integer` attribut The answer lies in a service inside Hibernate called the `org.hibernate.type.BasicTypeRegistry`, which essentially maintains a map of `org.hibernate.type.BasicType` (a `org.hibernate.type.Type` specialization) instances keyed by a name. We will see later, in the <> section, that we can explicitly tell Hibernate which BasicType to use for a particular attribute. -But first let's explore how implicit resolution works and how applications can adjust implicit resolution. +But first, let's explore how implicit resolution works and how applications can adjust the implicit resolution. [NOTE] ==== @@ -214,7 +214,7 @@ For more details, see <> section. Sometimes you want a particular attribute to be handled differently. Occasionally Hibernate will implicitly pick a `BasicType` that you do not want (and for some reason you do not want to adjust the `BasicTypeRegistry`). -In these cases you must explicitly tell Hibernate the `BasicType` to use, via the `org.hibernate.annotations.Type` annotation. +In these cases, you must explicitly tell Hibernate the `BasicType` to use, via the `org.hibernate.annotations.Type` annotation. [[basic-type-annotation-example]] .Using `@org.hibernate.annotations.Type` @@ -315,7 +315,7 @@ include::{sourcedir}/basic/BitSetTypeTest.java[tags=basic-custom-type-BitSetType ---- ==== -Alternatively, use can use a `@TypeDef` ans skip the registration phase: +Alternatively, you can use the `@TypeDef` and skip the registration phase: [[basic-custom-type-BitSetTypeDef-mapping-example]] .Using `@TypeDef` to register a custom Type @@ -424,7 +424,7 @@ Hibernate supports the mapping of Java enums as basic value types in a number of [[basic-enums-Enumerated]] ===== `@Enumerated` -The original JPA-compliant way to map enums was via the `@Enumerated` and `@MapKeyEnumerated` for map keys annotations which works on the principle that the enum values are stored according to one of 2 strategies indicated by `javax.persistence.EnumType`: +The original JPA-compliant way to map enums was via the `@Enumerated` or `@MapKeyEnumerated` for map keys annotations, working on the principle that the enum values are stored according to one of 2 strategies indicated by `javax.persistence.EnumType`: `ORDINAL`:: stored according to the enum value's ordinal position within the enum class, as indicated by `java.lang.Enum#ordinal` @@ -487,7 +487,7 @@ include::{sourcedir}/basic/PhoneTypeEnumeratedStringTest.java[tags=basic-enums-E ---- ==== -Persisting the same entity like in the `@Enumerated(ORDINAL)` example, Hibernate generates the following SQL statement: +Persisting the same entity as in the `@Enumerated(ORDINAL)` example, Hibernate generates the following SQL statement: [[basic-enums-Enumerated-string-persistence-example]] .Persisting an entity with an `@Enumerated(STRING)` mapping @@ -504,7 +504,7 @@ include::{extrasdir}/basic/basic-enums-Enumerated-string-persistence-example.sql Let's consider the following `Gender` enum which stores its values using the `'M'` and `'F'` codes. [[basic-enums-converter-example]] -.Enum with custom constructor +.Enum with a custom constructor ==== [source, JAVA, indent=0] ---- @@ -684,7 +684,7 @@ Mapping LOBs (database Large Objects) come in 2 forms, those using the JDBC loca JDBC LOB locators exist to allow efficient access to the LOB data. They allow the JDBC driver to stream parts of the LOB data as needed, potentially freeing up memory space. -However they can be unnatural to deal with and have certain limitations. +However, they can be unnatural to deal with and have certain limitations. For example, a LOB locator is only portably valid during the duration of the transaction in which it was obtained. The idea of materialized LOBs is to trade-off the potential efficiency (not all drivers handle LOB data efficiently) for a more natural programming paradigm using familiar Java types such as `String` or `byte[]`, etc for these LOBs. @@ -698,7 +698,7 @@ The JDBC LOB locator types include: * `java.sql.NClob` Mapping materialized forms of these LOB values would use more familiar Java types such as `String`, `char[]`, `byte[]`, etc. -The trade off for _more familiar_ is usually performance. +The trade-off for _more familiar_ is usually performance. [[basic-clob]] ===== Mapping CLOB @@ -843,7 +843,7 @@ include::{sourcedir}/basic/BlobByteArrayTest.java[tags=basic-blob-byte-array-exa ==== Mapping Nationalized Character Data JDBC 4 added the ability to explicitly handle nationalized character data. -To this end it added specific nationalized character data types. +To this end, it added specific nationalized character data types: * `NCHAR` * `NVARCHAR` @@ -894,7 +894,7 @@ include::{sourcedir}/basic/NClobTest.java[tags=basic-nclob-example] ---- ==== -To persist such an entity, you have to create a `NClob` using the `NClobProxy` Hibernate utility: +To persist such an entity, you have to create an `NClob` using the `NClobProxy` Hibernate utility: [[basic-nclob-persist-example]] .Persisting a `java.sql.NClob` entity @@ -952,7 +952,7 @@ Hibernate also allows you to map UUID values, again in a number of ways. [NOTE] ==== The default UUID mapping is as binary because it represents more efficient storage. -However many applications prefer the readability of character storage. +However, many applications prefer the readability of character storage. To switch the default mapping, simply call `MetadataBuilder.applyBasicType( UUIDCharType.INSTANCE, UUID.class.getName() )`. ==== @@ -961,7 +961,7 @@ To switch the default mapping, simply call `MetadataBuilder.applyBasicType( UUID As mentioned, the default mapping for UUID attributes. Maps the UUID to a `byte[]` using `java.util.UUID#getMostSignificantBits` and `java.util.UUID#getLeastSignificantBits` and stores that as `BINARY` data. -Chosen as the default simply because it is generally more efficient from storage perspective. +Chosen as the default simply because it is generally more efficient from a storage perspective. ==== UUID as (var)char @@ -980,7 +980,7 @@ Note that this can cause difficulty as the driver chooses to map many different ==== UUID as identifier -Hibernate supports using UUID values as identifiers, and they can even be generated on user's behalf. +Hibernate supports using UUID values as identifiers, and they can even be generated on the user's behalf. For details, see the discussion of generators in <>. [[basic-datetime]] @@ -1127,7 +1127,7 @@ Programmatically:: TimeZone.setDefault( TimeZone.getTimeZone( "UTC" ) ); ---- -However, as explained in http://in.relation.to/2016/09/12/jdbc-time-zone-configuration-property/[this article], this is not always practical especially for front-end nodes. +However, as explained in http://in.relation.to/2016/09/12/jdbc-time-zone-configuration-property/[this article], this is not always practical, especially for front-end nodes. For this reason, Hibernate offers the `hibernate.jdbc.time_zone` configuration property which can be configured: Declaratively, at the `SessionFactory` level:: @@ -1200,7 +1200,7 @@ include::{extrasdir}/basic/basic-jpa-convert-period-string-converter-sql-example In cases when the Java type specified for the "database side" of the conversion (the second `AttributeConverter` bind parameter) is not known, Hibernate will fallback to a `java.io.Serializable` type. -If the Java type is not know to Hibernate, you will encounter the following message: +If the Java type is not known to Hibernate, you will encounter the following message: > HHH000481: Encountered Java type for which we could not locate a JavaTypeDescriptor and which does not appear to implement equals and/or hashCode. > This can lead to significant performance problems when performing equality/dirty checking involving this Java type. @@ -1291,7 +1291,7 @@ include::{sourcedir}/basic/JpaQuotingTest.java[tags=basic-jpa-quoting-example] ---- ==== -Because `name` and `number` are reserved words, the `Product` entity mapping uses backtricks to quote these column names. +Because `name` and `number` are reserved words, the `Product` entity mapping uses backticks to quote these column names. When saving the following `Product entity`, Hibernate generates the following SQL insert statement: @@ -1360,8 +1360,8 @@ Properties marked as generated must additionally be _non-insertable_ and _non-up Only `@Version` and `@Basic` types can be marked as generated. `NEVER` (the default):: the given property value is not generated within the database. -`INSERT`:: the given property value is generated on insert, but is not regenerated on subsequent updates. Properties like _creationTimestamp_ fall into this category. -`ALWAYS`:: the property value is generated both on insert and on update. +`INSERT`:: the given property value is generated on insert but is not regenerated on subsequent updates. Properties like _creationTimestamp_ fall into this category. +`ALWAYS`:: the property value is generated both on insert and update. To mark a property as generated, use The Hibernate specific `@Generated` annotation. @@ -1682,7 +1682,7 @@ include::{extrasdir}/basic/mapping-column-read-and-write-composite-type-persiste ==== `@Formula` Sometimes, you want the Database to do some computation for you rather than in the JVM, you might also create some kind of virtual column. -You can use a SQL fragment (aka formula) instead of mapping a property into a column. This kind of property is read only (its value is calculated by your formula fragment) +You can use a SQL fragment (aka formula) instead of mapping a property into a column. This kind of property is read-only (its value is calculated by your formula fragment) [NOTE] ==== @@ -1829,19 +1829,40 @@ include::{sourcedir}/basic/WhereJoinTableTest.java[tags=mapping-where-join-table [[mapping-column-filter]] ==== `@Filter` -The `@Filter` annotation is another way to filter out entities or collections using custom SQL criteria, for both entities and collections. +The `@Filter` annotation is another way to filter out entities or collections using custom SQL criteria. Unlike the `@Where` annotation, `@Filter` allows you to parameterize the filter clause at runtime. -[[mapping-filter-example]] -.`@Filter` mapping usage +Now, considering we have the following `Account` entity: + +[[mapping-filter-account-example]] +.`@Filter` mapping entity-level usage ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/FilterTest.java[tags=mapping-filter-example] +include::{sourcedir}/basic/FilterTest.java[tags=mapping-filter-Account-example] ---- ==== -If the database contains the following entities: +[NOTE] +==== +Notice that the `active` property is mapped to the `active_status` column. + +This mapping was done to show you that the `@Filter` condition uses a SQL condition and not a JPQL filtering predicate. +==== + +As already explained, we can also apply the `@Filter` annotation for collections as illustrated by the `Client` entity: + +[[mapping-filter-client-example]] +.`@Filter` mapping collection-level usage +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterTest.java[tags=mapping-filter-Client-example] +---- +==== + +If we persist a `Client` with three associated `Account` entities, +Hibernate will execute the following SQL statements: [[mapping-filter-persistence-example]] .Persisting and fetching entities with a `@Filter` mapping @@ -1858,6 +1879,21 @@ include::{extrasdir}/basic/mapping-filter-persistence-example.sql[] ==== By default, without explicitly enabling the filter, Hibernate is going to fetch all `Account` entities. + +[[mapping-no-filter-entity-query-example]] +.Query entities mapped without activating the `@Filter` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterTest.java[tags=mapping-no-filter-entity-query-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-no-filter-entity-query-example.sql[] +---- +==== + If the filter is enabled and the filter parameter value is provided, then Hibernate is going to apply the filtering criteria to the associated `Account` entities. @@ -1878,6 +1914,7 @@ include::{extrasdir}/basic/mapping-filter-entity-query-example.sql[] [IMPORTANT] ==== Filters apply to entity queries, but not to direct fetching. + Therefore, in the following example, the filter is not taken into consideration when fetching an entity from the Persistence Context. [[mapping-filter-entity-example]] @@ -1896,7 +1933,22 @@ As you can see from the example above, contrary to an entity query, the filter d ==== Just like with entity queries, collections can be filtered as well, but only if the filter is explicitly enabled on the currently running Hibernate `Session`. -This way, when fetching the `accounts` collections, Hibernate is going to apply the `@Filter` clause filtering criteria to the associated collection entries. + +[[mapping-no-filter-collection-query-example]] +.Traversing collections without activating the `@Filter` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterTest.java[tags=mapping-no-filter-collection-query-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-no-filter-collection-query-example.sql[] +---- +==== + +When activating the `@Filter` and fetching the `accounts` collections, Hibernate is going to apply the filter condition to the associated collection entries. [[mapping-filter-collection-query-example]] .Traversing collections mapped with `@Filter` @@ -1922,7 +1974,7 @@ The main advantage of `@Filter` over the `@Where` clause is that the filtering c It's not possible to combine the `@Filter` and `@Cache` collection annotations. This limitation is due to ensuring consistency and because the filtering information is not stored in the second-level cache. -If caching was allowed for a currently filtered collection, then the second-level cache would store only a subset of the whole collection. +If caching were allowed for a currently filtered collection, then the second-level cache would store only a subset of the whole collection. Afterward, every other Session will get the filtered collection from the cache, even if the Session-level filters have not been explicitly activated. For this reason, the second-level collection cache is limited to storing whole collections, and not subsets. @@ -1934,7 +1986,7 @@ For this reason, the second-level collection cache is limited to storing whole c When using the `@Filter` annotation with collections, the filtering is done against the child entries (entities or embeddables). However, if you have a link table between the parent entity and the child table, then you need to use the `@FilterJoinTable` to filter child entries according to some column contained in the join table. -The `@FilterJoinTable` annotation can be, therefore, applied to a unidirectional `@OneToMany` collection as illustrate din the following mapping: +The `@FilterJoinTable` annotation can be, therefore, applied to a unidirectional `@OneToMany` collection as illustrated in the following mapping: [[mapping-filter-join-table-example]] .`@FilterJoinTable` mapping usage @@ -1945,7 +1997,11 @@ include::{sourcedir}/basic/FilterJoinTableTest.java[tags=mapping-filter-join-tab ---- ==== -If the database contains the following entities: +The `firstAccounts` filter will allow us to get only the `Account` entities that have the `order_id` +(which tells the position of every entry inside the `accounts` collection) +less than a given number (e.g. `maxOrderId`). + +Let's assume our database contains the following entities: [[mapping-filter-join-table-persistence-example]] .Persisting and fetching entities with a `@FilterJoinTable` mapping @@ -1961,8 +2017,24 @@ include::{extrasdir}/basic/mapping-filter-join-table-persistence-example.sql[] ---- ==== -The collections can be filtered if the associated filter is enabled on the currently running Hibernate `Session`. -This way, when fetching the `accounts` collections, Hibernate is going to apply the `@FilterJoinTable` clause filtering criteria to the associated collection entries. +The collections can be filtered only if the associated filter is enabled on the currently running Hibernate `Session`. + +[[mapping-no-filter-join-table-collection-query-example]] +.Traversing collections mapped with `@FilterJoinTable` without enabling the filter +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterJoinTableTest.java[tags=mapping-no-filter-join-table-collection-query-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-no-filter-join-table-collection-query-example.sql[] +---- +==== + +If we enable the filter and set the `maxOrderId` to `1` when fetching the `accounts` collections, Hibernate is going to apply the `@FilterJoinTable` clause filtering criteria, and we will get just +`2` `Account` entities, with the `order_id` values of `0` and `1`. [[mapping-filter-join-table-collection-query-example]] .Traversing collections mapped with `@FilterJoinTable` @@ -2294,7 +2366,7 @@ http://docs.oracle.com/javaee/7/api/javax/persistence/ManyToOne.html[`@ManyToOne http://docs.oracle.com/javaee/7/api/javax/persistence/OneToOne.html[`@OneToOne`], http://docs.oracle.com/javaee/7/api/javax/persistence/OneToMany.html[`@OneToMany`], and http://docs.oracle.com/javaee/7/api/javax/persistence/ManyToMany.html[`@ManyToMany`] -feature a http://docs.oracle.com/javaee/7/api/javax/persistence/ManyToOne.html#targetEntity--[`targetEntity`] attribute to specify the actual class of the entiity association when an interface is used for the mapping. +feature a http://docs.oracle.com/javaee/7/api/javax/persistence/ManyToOne.html#targetEntity--[`targetEntity`] attribute to specify the actual class of the entity association when an interface is used for the mapping. The http://docs.oracle.com/javaee/7/api/javax/persistence/ElementCollection.html[`@ElementCollection`] association has a http://docs.oracle.com/javaee/7/api/javax/persistence/ElementCollection.html#targetClass--[`targetClass`] attribute for the same purpose. diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc index 6680b9e63bbd8d63bec023f53b590543c0475d9d..ae6e9663c76cebc33985537f87316ef79ad29609 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc @@ -3,11 +3,11 @@ :sourcedir: ../../../../../test/java/org/hibernate/userguide/collections :extrasdir: extras/collections -Naturally Hibernate also allows to persist collections. -These persistent collections can contain almost any other Hibernate type, including: basic types, custom types, embeddables and references to other entities. +Naturally Hibernate also allows persisting collections. +These persistent collections can contain almost any other Hibernate type, including basic types, custom types, embeddables, and references to other entities. In this context, the distinction between value and reference semantics is very important. -An object in a collection might be handled with _value_ semantics (its life cycle being fully depends on the collection owner), -or it might be a reference to another entity with its own life cycle. +An object in a collection might be handled with _value_ semantics (its lifecycle being fully dependant on the collection owner), +or it might be a reference to another entity with its own lifecycle. In the latter case, only the _link_ between the two objects is considered to be a state held by the collection. The owner of the collection is always an entity, even if the collection is defined by an embeddable type. @@ -46,7 +46,7 @@ The persistent collections injected by Hibernate behave like `ArrayList`, `HashS [[collections-synopsis]] ==== Collections as a value type -Value and embeddable type collections have a similar behavior as simple value types because they are automatically persisted when referenced by a persistent object and automatically deleted when unreferenced. +Value and embeddable type collections have a similar behavior to basic types since they are automatically persisted when referenced by a persistent object and automatically deleted when unreferenced. If a collection is passed from one persistent object to another, its elements might be moved from one table to another. [IMPORTANT] @@ -170,7 +170,7 @@ In the following sections, we will go through all these collection types and dis [[collections-bag]] ==== Bags -Bags are unordered lists and we can have unidirectional bags or bidirectional ones. +Bags are unordered lists, and we can have unidirectional bags or bidirectional ones. [[collections-unidirectional-bag]] ===== Unidirectional bags @@ -270,7 +270,7 @@ include::{extrasdir}/collections-bidirectional-bag-orphan-removal-example.sql[] ---- ==== -When rerunning the previous example, the child will get removed because the parent-side propagates the removal upon disassociating the child entity reference. +When rerunning the previous example, the child will get removed because the parent-side propagates the removal upon dissociating the child entity reference. [[collections-list]] ==== Ordered Lists @@ -418,7 +418,7 @@ http://docs.oracle.com/javaee/7/api/javax/persistence/OrderBy.html[`@OrderBy`] a when fetching the current annotated collection, the Hibernate specific https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OrderBy.html[`@OrderBy`] annotation is used to specify a *SQL* clause instead. -In the following example, the `@OrderBy` annotations uses the `CHAR_LENGTH` SQL function to order the `Article` entities +In the following example, the `@OrderBy` annotation uses the `CHAR_LENGTH` SQL function to order the `Article` entities by the number of characters of the `name` attribute. [[collections-customizing-ordered-by-sql-clause-mapping-example]] @@ -541,7 +541,7 @@ include::{sourcedir}/UnidirectionalComparatorSortedSetTest.java[lines=75..77,ind [[collections-map]] ==== Maps -A `java.util.Map` is a ternary association because it requires a parent entity, a map key and a value. +A `java.util.Map` is a ternary association because it requires a parent entity, a map key, and a value. An entity can either be a map key or a map value, depending on the mapping. Hibernate allows using the following map keys: @@ -601,7 +601,7 @@ include::{extrasdir}/collections-map-custom-key-type-sql-example.sql[] ---- The `call_register` records the call history for every `person`. -The `call_timestamp_epoch` column stores the phone call timestamp as a Unix timestamp since epoch. +The `call_timestamp_epoch` column stores the phone call timestamp as a Unix timestamp since the Unix epoch. [NOTE] ==== @@ -700,7 +700,7 @@ include::{extrasdir}/collections-map-key-class-fetch-example.sql[] A unidirectional map exposes a parent-child association from the parent-side only. The following example shows a unidirectional map which also uses a `@MapKeyTemporal` annotation. -The map key is a timestamp and it's taken from the child entity table. +The map key is a timestamp, and it's taken from the child entity table. [NOTE] ==== @@ -851,7 +851,7 @@ The reason why the `Queue` interface is not used for the entity attribute is bec - `java.util.SortedSet` - `java.util.SortedMap` -However, the custom collection type can still be customized as long as the base type is one of the aformentioned persistent types. +However, the custom collection type can still be customized as long as the base type is one of the aforementioned persistent types. ==== This way, the `Phone` collection can be used as a `java.util.Queue`: diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc index 20a3e2497ddd7a39142a06a277eadc2ef5c63ab8..b7517cb8ddebfd2457fe6126ff9a93056ac90eb8 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc @@ -19,7 +19,7 @@ With this approach, you do not write persistent classes, only mapping files. A given entity has just one entity mode within a given SessionFactory. This is a change from previous versions which allowed to define multiple entity modes for an entity and to select which to load. -Entity modes can now be mixed within a domain model; a dynamic entity might reference a POJO entity, and vice versa. +Entity modes can now be mixed within a domain model; a dynamic entity might reference a POJO entity and vice versa. [[mapping-model-dynamic-example]] .Dynamic domain model Hibernate mapping @@ -60,8 +60,8 @@ include::{extrasdir}/dynamic/mapping-model-dynamic-example.sql[indent=0] [NOTE] ==== -The main advantage of dynamic models is quick turnaround time for prototyping without the need for entity class implementation. -The main down-fall is that you lose compile-time type checking and will likely deal with many exceptions at runtime. +The main advantage of dynamic models is the quick turnaround time for prototyping without the need for entity class implementation. +The main downfall is that you lose compile-time type checking and will likely deal with many exceptions at runtime. However, as a result of the Hibernate mapping, the database schema can easily be normalized and sound, allowing to add a proper domain model implementation on top later on. It is also interesting to note that dynamic models are great for certain integration use cases as well. diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc index c00e72f05025a3ea990d9362dbcb0cc229599617..b9e50148ce6c07e4e39bd774baf39a20bae88918 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc @@ -5,17 +5,17 @@ Historically Hibernate called these components. JPA calls them embeddables. -Either way the concept is the same: a composition of values. +Either way, the concept is the same: a composition of values. -For example we might have a `Publisher` class that is a composition of `name` and `country`, +For example, we might have a `Publisher` class that is a composition of `name` and `country`, or a `Location` class that is a composition of `country` and `city`. .Usage of the word _embeddable_ [NOTE] ==== -To avoid any confusion with the annotation that marks a given embeddable type, the annotation will be further referred as `@Embeddable`. +To avoid any confusion with the annotation that marks a given embeddable type, the annotation will be further referred to as `@Embeddable`. -Throughout this chapter and thereafter, for brevity sake, embeddable types may also be referred as _embeddable_. +Throughout this chapter and thereafter, for brevity sake, embeddable types may also be referred to as _embeddable_. ==== [[embeddable-type-mapping-example]] @@ -27,7 +27,7 @@ include::{sourcedir}/NestedEmbeddableTest.java[tag=embeddable-type-mapping-examp ---- ==== -An embeddable type is another form of value type, and its lifecycle is bound to a parent entity type, therefore inheriting the attribute access from its parent (for details on attribute access, see <>). +An embeddable type is another form of a value type, and its lifecycle is bound to a parent entity type, therefore inheriting the attribute access from its parent (for details on attribute access, see <>). Embeddable types can be made up of basic values as well as associations, with the caveat that, when used as collection elements, they cannot define collections themselves. @@ -36,7 +36,7 @@ Embeddable types can be made up of basic values as well as associations, with th Most often, embeddable types are used to group multiple basic type mappings and reuse them across several entities. [[simple-embeddable-type-mapping-example]] -.Simple Embeddedable +.Simple Embeddable ==== [source,java] ---- @@ -62,7 +62,7 @@ So, the embeddable type is represented by the `Publisher` class and the parent entity makes use of it through the `book#publisher` object composition. The composed values are mapped to the same table as the parent table. -Composition is part of good Object-oriented data modeling (idiomatic Java). +Composition is part of good object-oriented data modeling (idiomatic Java). In fact, that table could also be mapped by the following entity type instead. [[alternative-to-embeddable-type-mapping-example]] @@ -74,13 +74,13 @@ include::{sourcedir}/SimpleEmbeddableEquivalentTest.java[tag=embeddable-type-map ---- ==== -The composition form is certainly more Object-oriented, and that becomes more evident as we work with multiple embeddable types. +The composition form is certainly more object-oriented, and that becomes more evident as we work with multiple embeddable types. [[embeddable-multiple]] ==== Multiple embeddable types Although from an object-oriented perspective, it's much more convenient to work with embeddable types, this example doesn't work as-is. -When the same embeddable type is included multiple times in the same parent entity type, the JPA specification demands setting the associated column names explicitly. +When the same embeddable type is included multiple times in the same parent entity type, the JPA specification demands to set the associated column names explicitly. This requirement is due to how object properties are mapped to database columns. By default, JPA expects a database column having the same name with its associated object property. @@ -94,10 +94,10 @@ We have a few options to handle this issue. JPA defines the `@AttributeOverride` annotation to handle this scenario. This way, the mapping conflict is resolved by setting up explicit name-based property-column type mappings. -If an Embeddabe type is used multiple times in some entity, you need to use the +If an Embeddable type is used multiple times in some entity, you need to use the http://docs.oracle.com/javaee/7/api/javax/persistence/AttributeOverride.html[`@AttributeOverride`] and http://docs.oracle.com/javaee/7/api/javax/persistence/AssociationOverride.html[`@AssociationOverride`] annotations -to override the default column names definied by the Embeddable. +to override the default column names defined by the Embeddable. Considering you have the following `Publisher` embeddable type which defines a `@ManyToOne` association with the `Country` entity: @@ -179,17 +179,17 @@ You could even develop your own naming strategy to do other types of implicit na [[embeddable-collections]] ==== Collections of embeddable types -Collections of embeddable types are specifically value collections (as embeddable types are a value type). +Collections of embeddable types are specifically valued collections (as embeddable types are a value type). Value collections are covered in detail in <>. [[embeddable-mapkey]] -==== Embeddable types as Map key +==== Embeddable type as a Map key Embeddable types can also be used as `Map` keys. This topic is converted in detail in <>. [[embeddable-identifier]] -==== Embeddable types as identifiers +==== Embeddable type as identifier Embeddable types can also be used as entity type identifiers. This usage is covered in detail in <>. diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc index e0c54be2af17d8f07ab48bd8da7ea56e96d82de5..5a0ff41dd556cd9d5f406cb30dfee00b24dedd02 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc @@ -10,9 +10,9 @@ [NOTE] ==== The entity type describes the mapping between the actual persistable domain model object and a database table row. -To avoid any confusion with the annotation that marks a given entity type, the annotation will be further referred as `@Entity`. +To avoid any confusion with the annotation that marks a given entity type, the annotation will be further referred to as `@Entity`. -Throughout this chapter and thereafter, entity types will be simply referred as _entity_. +Throughout this chapter and thereafter, entity types will be simply referred to as _entity_. ==== [[entity-pojo]] @@ -71,17 +71,17 @@ That said, the constructor should be defined with at least package visibility if [[entity-pojo-accessors]] ==== Declare getters and setters for persistent attributes -The JPA specification requires this, otherwise the model would prevent accessing the entity persistent state fields directly from outside the entity itself. +The JPA specification requires this, otherwise, the model would prevent accessing the entity persistent state fields directly from outside the entity itself. Although Hibernate does not require it, it is recommended to follow the JavaBean conventions and define getters and setters for entity persistent attributes. Nevertheless, you can still tell Hibernate to directly access the entity fields. Attributes (whether fields or getters/setters) need not be declared public. -Hibernate can deal with attributes declared with public, protected, package or private visibility. +Hibernate can deal with attributes declared with the public, protected, package or private visibility. Again, if wanting to use runtime proxy generation for lazy loading, the getter/setter should grant access to at least package visibility. [[entity-pojo-identifier]] -==== Provide identifier attribute(s) +==== Providing identifier attribute(s) [IMPORTANT] ==== @@ -210,11 +210,11 @@ include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-mu ---- ==== -Specifically the outcome in this last example will depend on whether the `Book` class +Specifically, the outcome in this last example will depend on whether the `Book` class implemented equals/hashCode, and, if so, how. If the `Book` class did not override the default equals/hashCode, -then the two `Book` object reference are not going to be equal since their references are different. +then the two `Book` object references are not going to be equal since their references are different. Consider yet another case: @@ -253,7 +253,7 @@ include::{sourcedir-mapping}/identifier/NaiveEqualsHashCodeEntityTest.java[tag=e ---- ==== -The issue here is a conflict between the use of generated identifier, the contract of `Set` and the equals/hashCode implementations. +The issue here is a conflict between the use of the generated identifier, the contract of `Set`, and the equals/hashCode implementations. `Set` says that the equals/hashCode value for an object should not change while the object is part of the `Set`. But that is exactly what happened here because the equals/hasCode are based on the (generated) id, which was not set until the JPA transaction is committed. @@ -328,7 +328,7 @@ To find the `Account` balance, we need to query the `AccountSummary` which share However, the `AccountSummary` is not mapped to a physical table, but to an SQL query. -So, if we have the following `AccountTransaction` record, the `AccountSummary` balance will mach the proper amount of money in this `Account`. +So, if we have the following `AccountTransaction` record, the `AccountSummary` balance will match the proper amount of money in this `Account`. [[mapping-Subselect-entity-find-example]] .Finding a `@Subselect` entity @@ -356,7 +356,7 @@ The goal of the `@Synchronize` annotation in the `AccountSummary` entity mapping underlying `@Subselect` SQL query. This is because, unlike JPQL and HQL queries, Hibernate cannot parse the underlying native SQL query. With the `@Synchronize` annotation in place, -when executing a HQL or JPQL which selects from the `AccountSummary` entity, +when executing an HQL or JPQL which selects from the `AccountSummary` entity, Hibernate will trigger a Persistence Context flush if there are pending `Account`, `Client` or `AccountTransaction` entity state transitions. ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql index d3d216ce8d2c320b0b99f4a9629ad41aa4be54b7..5282632d416f698f4d5194b4b9ab29f73c5852d6 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql @@ -1,25 +1,3 @@ -SELECT - c.id as id1_1_0_, - c.name as name2_1_0_ -FROM - Client c -WHERE - c.id = 1 - -SELECT - a.id as id1_0_, - a.active as active2_0_, - a.amount as amount3_0_, - a.client_id as client_i6_0_, - a.rate as rate4_0_, - a.account_type as account_5_0_ -FROM - Account a -WHERE - a.client_id = 1 - --- Activate filter [activeAccount] - SELECT c.id as id1_1_0_, c.name as name2_1_0_ @@ -30,7 +8,7 @@ WHERE SELECT a.id as id1_0_, - a.active as active2_0_, + a.active_status as active2_0_, a.amount as amount3_0_, a.client_id as client_i6_0_, a.rate as rate4_0_, @@ -38,5 +16,5 @@ SELECT FROM Account a WHERE - accounts0_.active = true + accounts0_.active_status = true and a.client_id = 1 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-example.sql index f3d4d61627f5bb53ff16e7f473b1bfa05d23272e..aeb9bb001ee7a937b1367bc15880fa345b86f50c 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-example.sql @@ -1,6 +1,6 @@ SELECT a.id as id1_0_0_, - a.active as active2_0_0_, + a.active_status as active2_0_0_, a.amount as amount3_0_0_, a.client_id as client_i6_0_0_, a.rate as rate4_0_0_, @@ -8,9 +8,6 @@ SELECT c.id as id1_1_1_, c.name as name2_1_1_ FROM - Account a -LEFT OUTER JOIN - Client c - ON a.client_id=c.id + Account a WHERE a.id = 2 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql index 88c17423ebd07dbbd70f34f8bca1ceda829857c9..5bcf7e0c354abaa894ed203d7519562d908cf7cd 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql @@ -1,18 +1,6 @@ SELECT a.id as id1_0_, - a.active as active2_0_, - a.amount as amount3_0_, - a.client_id as client_i6_0_, - a.rate as rate4_0_, - a.account_type as account_5_0_ -FROM - Account a - --- Activate filter [activeAccount] - -SELECT - a.id as id1_0_, - a.active as active2_0_, + a.active_status as active2_0_, a.amount as amount3_0_, a.client_id as client_i6_0_, a.rate as rate4_0_, @@ -20,4 +8,4 @@ SELECT FROM Account a WHERE - a.active = true \ No newline at end of file + a.active_status = true \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-collection-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-collection-query-example.sql index f6abfd3ec97a5873784025f4f10fca16882d4f72..80226c2f508e28d1b28045e62b050016f45c32a5 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-collection-query-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-collection-query-example.sql @@ -3,7 +3,6 @@ SELECT ca.accounts_id as accounts2_2_0_, ca.order_id as order_id3_0_, a.id as id1_0_1_, - a.active as active2_0_1_, a.amount as amount3_0_1_, a.rate as rate4_0_1_, a.account_type as account_5_0_1_ @@ -12,27 +11,6 @@ FROM INNER JOIN Account a ON ca.accounts_id=a.id -WHERE - ca.Client_id = ? - --- binding parameter [1] as [BIGINT] - [1] - --- Activate filter [firstAccounts] - -SELECT - ca.Client_id as Client_i1_2_0_, - ca.accounts_id as accounts2_2_0_, - ca.order_id as order_id3_0_, - a.id as id1_0_1_, - a.active as active2_0_1_, - a.amount as amount3_0_1_, - a.rate as rate4_0_1_, - a.account_type as account_5_0_1_ -FROM - Client_Account ca -INNER JOIN - Account a -ON ca.accounts_id=a.id WHERE ca.order_id <= ? AND ca.Client_id = ? diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-persistence-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-persistence-example.sql index e9a785d308ddc9a57ef6e146325fe7034d18837d..6bf698e00b3b0528b046f488b71e9f8ae38fce3b 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-persistence-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-join-table-persistence-example.sql @@ -1,14 +1,14 @@ INSERT INTO Client (name, id) VALUES ('John Doe', 1) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) -VALUES (true, 5000.0, 1, 0.0125, 'CREDIT', 1) +INSERT INTO Account (amount, client_id, rate, account_type, id) +VALUES (5000.0, 1, 0.0125, 'CREDIT', 1) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) -VALUES (false, 0.0, 1, 0.0105, 'DEBIT', 2) +INSERT INTO Account (amount, client_id, rate, account_type, id) +VALUES (0.0, 1, 0.0105, 'DEBIT', 2) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) -VALUES (true, 250.0, 1, 0.0105, 'DEBIT', 3) +INSERT INTO Account (amount, client_id, rate, account_type, id) +VALUES (250.0, 1, 0.0105, 'DEBIT', 3) INSERT INTO Client_Account (Client_id, order_id, accounts_id) VALUES (1, 0, 1) diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql index 2f4abf9d663804b466cf300792313093b3ad8758..750c9f4f70f43bc2af4ace29ab7a82ce9bb4a642 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql @@ -1,11 +1,11 @@ INSERT INTO Client (name, id) VALUES ('John Doe', 1) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) +INSERT INTO Account (active_status, amount, client_id, rate, account_type, id) VALUES (true, 5000.0, 1, 0.0125, 'CREDIT', 1) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) +INSERT INTO Account (active_status, amount, client_id, rate, account_type, id) VALUES (false, 0.0, 1, 0.0105, 'DEBIT', 2) -INSERT INTO Account (active, amount, client_id, rate, account_type, id) +INSERT INTO Account (active_status, amount, client_id, rate, account_type, id) VALUES (true, 250.0, 1, 0.0105, 'DEBIT', 3) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-collection-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-collection-query-example.sql new file mode 100644 index 0000000000000000000000000000000000000000..d80b60f607ab4471e4c5521644625cb53b9d5670 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-collection-query-example.sql @@ -0,0 +1,19 @@ +SELECT + c.id as id1_1_0_, + c.name as name2_1_0_ +FROM + Client c +WHERE + c.id = 1 + +SELECT + a.id as id1_0_, + a.active_status as active2_0_, + a.amount as amount3_0_, + a.client_id as client_i6_0_, + a.rate as rate4_0_, + a.account_type as account_5_0_ +FROM + Account a +WHERE + a.client_id = 1 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-entity-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-entity-query-example.sql new file mode 100644 index 0000000000000000000000000000000000000000..4d6dbc7655a77dbb7f3bb93684e618e900c9ac45 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-entity-query-example.sql @@ -0,0 +1,9 @@ +SELECT + a.id as id1_0_, + a.active_status as active2_0_, + a.amount as amount3_0_, + a.client_id as client_i6_0_, + a.rate as rate4_0_, + a.account_type as account_5_0_ +FROM + Account a \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-join-table-collection-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-join-table-collection-query-example.sql new file mode 100644 index 0000000000000000000000000000000000000000..ab44fcf581022c5c187110bac060fd4b10525f42 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-no-filter-join-table-collection-query-example.sql @@ -0,0 +1,17 @@ +SELECT + ca.Client_id as Client_i1_2_0_, + ca.accounts_id as accounts2_2_0_, + ca.order_id as order_id3_0_, + a.id as id1_0_1_, + a.amount as amount3_0_1_, + a.rate as rate4_0_1_, + a.account_type as account_5_0_1_ +FROM + Client_Account ca +INNER JOIN + Account a +ON ca.accounts_id=a.id +WHERE + ca.Client_id = ? + +-- binding parameter [1] as [BIGINT] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc index 9b0eb74eb21e2516c7e80f52ced82a8d157f8902..79d66216f99207ec5514594110335ae0fd6c6504 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc @@ -21,7 +21,7 @@ See <>. ==== Technically the identifier does not have to map to the column(s) physically defined as the table primary key. They just need to map to column(s) that uniquely identify each row. -However this documentation will continue to use the terms identifier and primary key interchangeably. +However, this documentation will continue to use the terms identifier and primary key interchangeably. ==== Every entity must define an identifier. For entity inheritance hierarchies, the identifier must be defined just on the entity that is the root of the hierarchy. @@ -219,7 +219,7 @@ For discussion of generated values for non-identifier attributes, see <>. @@ -249,7 +249,7 @@ If the identifier type is numerical (e.g. `Long`, `Integer`), then Hibernate is The `IdGeneratorStrategyInterpreter` has two implementations: `FallbackInterpreter`:: - This is the default strategy since Hibernate 5.0. For older versions, this strategy is enabled through the <> configuration property . + This is the default strategy since Hibernate 5.0. For older versions, this strategy is enabled through the <> configuration property. When using this strategy, `AUTO` always resolves to `SequenceStyleGenerator`. If the underlying database supports sequences, then a SEQUENCE generator is used. Otherwise, a TABLE generator is going to be used instead. `LegacyFallbackInterpreter`:: @@ -288,7 +288,7 @@ include::{sourcedir}/SequenceGeneratorNamedTest.java[tag=identifiers-generators- ---- ==== -The `javax.persistence.SequenceGenerator` annotataion allows you to specify additional configurations as well. +The `javax.persistence.SequenceGenerator` annotation allows you to specify additional configurations as well. [[identifiers-generators-sequence-configured]] .Configured sequence @@ -303,7 +303,7 @@ include::{sourcedir}/SequenceGeneratorConfiguredTest.java[tag=identifiers-genera ==== Using IDENTITY columns For implementing identifier value generation based on IDENTITY columns, -Hibernate makes use of its `org.hibernate.id.IdentityGenerator` id generator which expects the identifier to generated by INSERT into the table. +Hibernate makes use of its `org.hibernate.id.IdentityGenerator` id generator which expects the identifier to be generated by INSERT into the table. IdentityGenerator understands 3 different ways that the INSERT-generated value might be retrieved: * If Hibernate believes the JDBC environment supports `java.sql.Statement#getGeneratedKeys`, then that approach will be used for extracting the IDENTITY generated keys. @@ -314,18 +314,18 @@ IdentityGenerator understands 3 different ways that the INSERT-generated value m ==== It is important to realize that this imposes a runtime behavior where the entity row *must* be physically inserted prior to the identifier value being known. This can mess up extended persistence contexts (conversations). -Because of the runtime imposition/inconsistency Hibernate suggest other forms of identifier value generation be used. +Because of the runtime imposition/inconsistency, Hibernate suggests other forms of identifier value generation be used. ==== [NOTE] ==== There is yet another important runtime impact of choosing IDENTITY generation: Hibernate will not be able to JDBC batching for inserts of the entities that use IDENTITY generation. -The importance of this depends on the application specific use cases. +The importance of this depends on the application-specific use cases. If the application is not usually creating many new instances of a given type of entity that uses IDENTITY generation, then this is not an important impact since batching would not have been helpful anyway. ==== [[identifiers-generators-table]] -==== Using table identifier generator +==== Using the table identifier generator Hibernate achieves table-based identifier generation based on its `org.hibernate.id.enhanced.TableGenerator` which defines a table capable of holding multiple named value segments for any number of entities. @@ -392,7 +392,7 @@ This is supported through its `org.hibernate.id.UUIDGenerator` id generator. `UUIDGenerator` supports pluggable strategies for exactly how the UUID is generated. These strategies are defined by the `org.hibernate.id.UUIDGenerationStrategy` contract. The default strategy is a version 4 (random) strategy according to IETF RFC 4122. -Hibernate does ship with an alternative strategy which is a RFC 4122 version 1 (time-based) strategy (using ip address rather than mac address). +Hibernate does ship with an alternative strategy which is a RFC 4122 version 1 (time-based) strategy (using IP address rather than mac address). [[identifiers-generators-uuid-mapping-example]] .Implicitly using the random UUID strategy @@ -427,7 +427,7 @@ Which is, in fact, the role of these optimizers. none:: No optimization is performed. We communicate with the database each and every time an identifier value is needed from the generator. pooled-lo:: The pooled-lo optimizer works on the principle that the increment-value is encoded into the database table/sequence structure. -In sequence-terms this means that the sequence is defined with a greater-that-1 increment size. +In sequence-terms, this means that the sequence is defined with a greater-than-1 increment size. + For example, consider a brand new sequence defined as `create sequence m_sequence start with 1 increment by 20`. This sequence essentially defines a "pool" of 20 usable id values each and every time we ask it for its next-value. @@ -483,7 +483,7 @@ include::{extrasdir}/id/identifiers-generators-pooled-lo-optimizer-persist-examp ---- ==== -As you can see from the list of generated SQL statements, you can insert 3 entities for one database sequence call. +As you can see from the list of generated SQL statements, you can insert 3 entities with just one database sequence call. This way, the pooled and the pooled-lo optimizers allow you to reduce the number of database roundtrips, therefore reducing the overall transaction response time. [[identifiers-derived]] diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc index 3a4d5cd954da90e67fa56611d566955b83eef099..9e7de6c950d763627f93f585a8bf2c7a57cfb2ae 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/inheritance.adoc @@ -5,7 +5,7 @@ Although relational database systems don't provide support for inheritance, Hibernate provides several strategies to leverage this object-oriented trait onto domain model entities: -MappedSuperclass:: Inheritance is implemented in domain model only without reflecting it in the database schema. See <>. +MappedSuperclass:: Inheritance is implemented in the domain model only without reflecting it in the database schema. See <>. Single table:: The domain model class hierarchy is materialized into a single table which contains entities belonging to different class types. See <>. Joined table:: The base class and all the subclasses have their own database tables and fetching a subclass entity requires a join with the parent table as well. See <>. Table per class:: Each subclass has its own table containing both the subclass and the base class properties. See <>. @@ -13,11 +13,11 @@ Table per class:: Each subclass has its own table containing both the subclass a [[entity-inheritance-mapped-superclass]] ==== MappedSuperclass -In the following domain model class hierarchy, a 'DebitAccount' and a 'CreditAccount' share the same 'Account' base class. +In the following domain model class hierarchy, a `DebitAccount` and a `CreditAccount` share the same `Account` base class. image:images/domain/inheritance/inheritance_class_diagram.svg[Inheritance class diagram] -When using `MappedSuperclass`, the inheritance is visible in the domain model only and each database table contains both the base class and the subclass properties. +When using `MappedSuperclass`, the inheritance is visible in the domain model only, and each database table contains both the base class and the subclass properties. [[entity-inheritance-mapped-superclass-example]] .`@MappedSuperclass` inheritance @@ -35,7 +35,7 @@ include::{extrasdir}/entity-inheritance-mapped-superclass-example.sql[] [NOTE] ==== -Because the `@MappedSuperclass` inheritance model is not mirrored at database level, +Because the `@MappedSuperclass` inheritance model is not mirrored at the database level, it's not possible to use polymorphic queries (fetching subclasses by their base class). ==== @@ -123,7 +123,7 @@ Both `@DiscriminatorColumn` and `@DiscriminatorFormula` are to be set on the roo The available options are `force` and `insert`. The `force` attribute is useful if the table contains rows with _extra_ discriminator values that are not mapped to a persistent class. -This could for example occur when working with a legacy database. +This could, for example, occur when working with a legacy database. If `force` is set to true Hibernate will specify the allowed discriminator values in the SELECT query, even when retrieving all instances of the root class. The second option, `insert`, tells Hibernate whether or not to include the discriminator column in SQL INSERTs. diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/naming.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/naming.adoc index ff67cb51c37c958ed4525a965717667d742045b9..7826a95c7444b18b254ccd50856fb459c9df800c 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/naming.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/naming.adoc @@ -83,7 +83,7 @@ to specify the ImplicitNamingStrategy to use. See [[PhysicalNamingStrategy]] ==== PhysicalNamingStrategy -Many organizations define rules around the naming of database objects (tables, columns, foreign-keys, etc). +Many organizations define rules around the naming of database objects (tables, columns, foreign keys, etc). The idea of a PhysicalNamingStrategy is to help implement such naming rules without having to hard-code them into the mapping via explicit names. @@ -94,8 +94,8 @@ would be, for example, to say that the physical column name should instead be ab [NOTE] ==== It is true that the resolution to `acct_num` could have been handled in an ImplicitNamingStrategy in this case. -But the point is separation of concerns. The PhysicalNamingStrategy will be applied regardless of whether -the attribute explicitly specified the column name or whether we determined that implicitly. The +But the point is separation of concerns. The PhysicalNamingStrategy will be applied regardless of whether +the attribute explicitly specified the column name or whether we determined that implicitly. The ImplicitNamingStrategy would only be applied if an explicit name was not given. So it depends on needs and intent. ==== 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 35bc3600a91cf4d1c6d0284b3b4db0150ebd129c..9e01dc994298b3373157db9929f13abe2b49bc22 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/natural_id.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/natural_id.adoc @@ -5,13 +5,20 @@ Natural ids represent domain model unique identifiers 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 its identifier (PK). +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. +==== [[naturalid-mapping]] ==== Natural Id Mapping -Natural ids are defined in terms of on -e or more persistent attributes. +Natural ids are defined in terms of one or more persistent attributes. [[naturalid-simple-basic-attribute-mapping-example]] .Natural id using single basic attribute diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/types.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/types.adoc index 8c0a3f6ee4ca77c99756f4cb6c36cf2b4b688910..1d58ca3ccda39569b3ede711800858ebc255c23f 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/types.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/types.adoc @@ -6,7 +6,7 @@ Hibernate understands both the Java and JDBC representations of application data. The ability to read/write this data from/to the database is the function of a Hibernate _type_. A type, in this usage, is an implementation of the `org.hibernate.type.Type` interface. -This Hibernate type also describes various aspects of behavior of the Java type such as how to check for equality, how to clone values, etc. +This Hibernate type also describes various behavioral aspects of the Java type such as how to check for equality, how to clone values, etc. .Usage of the word _type_ [NOTE] @@ -20,7 +20,7 @@ When you encounter the term type in discussions of Hibernate, it may refer to th To help understand the type categorizations, let's look at a simple table and domain model that we wish to map. [[mapping-types-basic-example]] -.Simple table and domain model +.A simple table and domain model ==== [source, SQL, indent=0] ---- diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc index aa83e0a162b4461a20a5641ad29ec898474002a1..a381bfd11145cbf65d46b3f45e8be53c7c67cfc7 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc @@ -113,7 +113,7 @@ The `REVTYPE` column value is taken from the https://docs.jboss.org/hibernate/or |2 | `DEL` |A database table row was deleted. |================================= -The audit (history) of an entity can be accessed using the `AuditReader` interface, which can be obtained having an open `EntityManager` or `Session` via the `AuditReaderFactory`. +The audit (history) of an entity can be accessed using the `AuditReader` interface, which can be obtained by having an open `EntityManager` or `Session` via the `AuditReaderFactory`. [[envers-audited-revisions-example]] .Getting a list of revisions for the `Customer` entity @@ -148,11 +148,11 @@ include::{extrasdir}/envers-audited-rev1-example.sql[] When executing the aforementioned SQL query, there are two parameters: revision_number:: -The first parameter marks the revision number we are interested in or the latest one that exist up to this particular revision. +The first parameter marks the revision number we are interested in or the latest one that exists up to this particular revision. revision_type:: The second parameter specifies that we are not interested in `DEL` `RevisionType` so that deleted entries are filtered out. -The same goes for the second revision associated to the `UPDATE` statement. +The same goes for the second revision associated with the `UPDATE` statement. [[envers-audited-rev2-example]] .Getting the second revision for the `Customer` entity @@ -210,7 +210,7 @@ Name of a field in the audit entity that will hold the revision number. Name of a field in the audit entity that will hold the type of the revision (currently, this can be: `add`, `mod`, `del`). `*org.hibernate.envers.revision_on_collection_change*` (default: `true` ):: -Should a revision be generated when a not-owned relation field changes (this can be either a collection in a one-to-many relation, or the field using `mappedBy` attribute in a one-to-one relation). +Should a revision be generated when a not-owned relation field changes (this can be either a collection in a one-to-many relation or the field using `mappedBy` attribute in a one-to-one relation). `*org.hibernate.envers.do_not_audit_optimistic_locking_field*` (default: `true` ):: When true, properties to be used for optimistic locking, annotated with `@Version`, will not be automatically audited (their history won't be stored; it normally doesn't make sense to store it). @@ -221,14 +221,14 @@ Should the entity data be stored in the revision when the entity is deleted (ins This is not normally needed, as the data is present in the last-but-one revision. Sometimes, however, it is easier and more efficient to access it in the last revision (then the data that the entity contained before deletion is stored twice). -`*org.hibernate.envers.default_schema*` (default: `null` - same schema as table being audited):: +`*org.hibernate.envers.default_schema*` (default: `null` - same schema as the table being audited):: The default schema name that should be used for audit tables. + Can be overridden using the `@AuditTable( schema="..." )` annotation. + If not present, the schema will be the same as the schema of the table being audited. -`*org.hibernate.envers.default_catalog*` (default: `null` - same catalog as table being audited):: +`*org.hibernate.envers.default_catalog*` (default: `null` - same catalog as the table being audited):: The default catalog name that should be used for audit tables. + Can be overridden using the `@AuditTable( catalog="..." )` annotation. @@ -261,7 +261,7 @@ Only used if the `ValidityAuditStrategy` is used, and `org.hibernate.envers.audi Boolean flag that determines the strategy of revision number generation. Default implementation of revision entity uses native identifier generator. + -If current database engine does not support identity columns, users are advised to set this property to false. +If the current database engine does not support identity columns, users are advised to set this property to false. + In this case revision numbers are created by preconfigured `org.hibernate.id.enhanced.SequenceStyleGenerator`. See: `org.hibernate.envers.DefaultRevisionEntity` and `org.hibernate.envers.enhanced.SequenceIdRevisionEntity`. @@ -284,7 +284,7 @@ For more information, refer to <> and <>. -Users are also allowed to implement custom mechanism of tracking modified entity types. +Users are also allowed to implement custom mechanisms of tracking modified entity types. In this case, they shall pass their own implementation of `org.hibernate.envers.EntityTrackingRevisionListener` interface as the value of `@org.hibernate.envers.RevisionEntity` annotation. @@ -657,10 +657,10 @@ include::{sourcedir}/EntityTypeChangeAuditTrackingRevisionListenerTest.java[tags ==== [[envers-tracking-properties-changes]] -=== Tracking entity changes at property level +=== Tracking entity changes at the property level By default, the only information stored by Envers are revisions of modified entities. -This approach lets user create audit queries based on historical values of entity properties. +This approach lets users create audit queries based on historical values of entity properties. Sometimes it is useful to store additional metadata for each revision, when you are interested also in the type of changes, not only about the resulting values. The feature described in <> makes it possible to tell which entities were modified in a given revision. @@ -668,7 +668,7 @@ The feature described in <> makes The feature described here takes it one step further. _Modification Flags_ enable Envers to track which properties of audited entities were modified in a given revision. -Tracking entity changes at property level can be enabled by: +Tracking entity changes at the property level can be enabled by: . setting `org.hibernate.envers.global_with_modified_flag` configuration property to `true`. This global switch will cause adding modification flags to be stored for all audited properties of all audited entities. @@ -677,11 +677,11 @@ Tracking entity changes at property level can be enabled by: The trade-off coming with this functionality is an increased size of audit tables and a very little, almost negligible, performance drop during audit writes. This is due to the fact that every tracked property has to have an accompanying boolean column in the schema that stores information about the property modifications. -Of course it is Envers job to fill these columns accordingly - no additional work by the developer is required. +Of course, it is Enver's job to fill these columns accordingly - no additional work by the developer is required. Because of costs mentioned, it is recommended to enable the feature selectively, when needed with use of the granular configuration means described above. [[envers-tracking-properties-changes-mapping-example]] -.Mapping for tracking entity changes at property level +.Mapping for tracking entity changes at the property level ==== [source, JAVA, indent=0] ---- @@ -697,7 +697,7 @@ include::{extrasdir}/envers-tracking-properties-changes-mapping-example.sql[] As you can see, every property features a `_MOD` column (e.g. `createdOn_MOD`) in the audit log. [[envers-tracking-properties-changes-example]] -.Tracking entity changes at property level example +.Tracking entity changes at the property level example ==== [source, JAVA, indent=0] ---- @@ -724,14 +724,14 @@ The queries in Envers are similar to Hibernate Criteria queries, so if you are c The main limitation of the current queries implementation is that you cannot traverse relations. You can only specify constraints on the ids of the related entities, and only on the "owning" side of the relation. -This however will be changed in future releases. +This, however, will be changed in future releases. [NOTE] ==== The queries on the audited data will be in many cases much slower than corresponding queries on "live" data, as, especially for the default audit strategy, they involve correlated subselects. -Queries are improved both in terms of speed and possibilities, when using the validity audit strategy, +Queries are improved both in terms of speed and possibilities when using the validity audit strategy, which stores both start and end revisions for entities. See <>. ==== @@ -907,7 +907,7 @@ In other words, the result set would contain a list of `Customer` instances, one hold the audited property data at the _maximum_ revision number for each `Customer` primary key. [[envers-tracking-properties-changes-queries]] -=== Querying for revisions of entity that modified a given property +=== Querying for entity revisions that modified a given property For the two types of queries described above it's possible to use special `Audit` criteria called `hasChanged()` and `hasNotChanged()` that makes use of the functionality described in <>. @@ -946,7 +946,7 @@ Using this query we won't get all other revisions in which `lastName` wasn't tou From the SQL query you can see that the `lastName_MOD` column is being used in the WHERE clause, hence the aforementioned requirement for tracking modification flags. -Of course, nothing prevents user from combining `hasChanged` condition with some additional criteria. +Of course, nothing prevents users from combining `hasChanged` condition with some additional criteria. [[envers-tracking-properties-changes-queries-hasChanged-and-hasNotChanged-example]] .Getting all `Customer` revisions for which the `lastName` attribute has changed and the `firstName` attribute has not changed @@ -1196,7 +1196,7 @@ include::{extrasdir}/envers-querying-entity-relation-nested-join-multiple-restri [[envers-querying-revision-entities]] === Querying for revision information without loading entities -It may sometimes be useful to load information about revisions to find out who performed specific revisions or +Sometimes, it may be useful to load information about revisions to find out who performed specific revisions or to know what entity names were modified but the change log about the related audited entities isn't needed. This API allows an efficient way to get the revision information entity log without instantiating the actual entities themselves. @@ -1213,7 +1213,7 @@ AuditQuery query = getAuditReader().createQuery() This query will return all revision information entities for revisions between 1 and 25 including those which are related to deletions. If deletions are not of interest, you would pass `false` as the second argument. -Note this this query uses the `DefaultRevisionEntity` class type. The class provided will vary depending on the +Note that this query uses the `DefaultRevisionEntity` class type. The class provided will vary depending on the configuration properties used to configure Envers or if you supply your own revision entity. Typically users who will use this API will likely be providing a custom revision entity implementation to obtain custom information being maintained per revision. @@ -1257,24 +1257,24 @@ The audit table contains the following columns: id:: `id` of the original entity (this can be more then one column in the case of composite primary keys) revision number:: an integer, which matches to the revision number in the revision entity table. -revision type:: The `org.hibernate.envers.RevisionType` enumeration ordinal stating if the change represent an INSERT, UPDATE or DELETE. +revision type:: The `org.hibernate.envers.RevisionType` enumeration ordinal stating if the change represents an INSERT, UPDATE or DELETE. audited fields:: properties from the original entity being audited The primary key of the audit table is the combination of the original id of the entity and the revision number, so there can be at most one historic entry for a given entity instance at a given revision. The current entity data is stored in the original table and in the audit table. -This is a duplication of data, however as this solution makes the query system much more powerful, and as memory is cheap, hopefully this won't be a major drawback for the users. +This is a duplication of data, however as this solution makes the query system much more powerful, and as memory is cheap, hopefully, this won't be a major drawback for the users. -A row in the audit table with entity id `ID`, revision `N` and data `D` means: entity with id `ID` has data `D` from revision `N` upwards. +A row in the audit table with entity id `ID`, revision `N`, and data `D` means: entity with id `ID` has data `D` from revision `N` upwards. Hence, if we want to find an entity at revision `M`, we have to search for a row in the audit table, which has the revision number smaller or equal to `M`, but as large as possible. If no such row is found, or a row with a "deleted" marker is found, it means that the entity didn't exist at that revision. -The "revision type" field can currently have three values: `0`, `1` and `2`, which means `ADD`, `MOD` and `DEL`, respectively. +The "revision type" field can currently have three values: `0`, `1` and `2`, which means `ADD`, `MOD`, and `DEL`, respectively. A row with a revision of type `DEL` will only contain the id of the entity and no data (all fields `NULL`), as it only serves as a marker saying "this entity was deleted at that revision". Additionally, there is a revision entity table which contains the information about the global revision. -By default the generated table is named `REVINFO` and contains just two columns: `ID` and `TIMESTAMP`. +By default, the generated table is named `REVINFO` and contains just two columns: `ID` and `TIMESTAMP`. A row is inserted into this table on each new revision, that is, on each commit of a transaction, which changes audited data. The name of this table can be configured, the name of its columns as well as adding additional columns can be achieved as discussed in <>. @@ -1283,7 +1283,7 @@ The name of this table can be configured, the name of its columns as well as add While global revisions are a good way to provide correct auditing of relations, some people have pointed out that this may be a bottleneck in systems, where data is very often modified. One viable solution is to introduce an option to have an entity "locally revisioned", that is revisions would be created for it independently. -This woulld not enable correct versioning of relations, but it would work without the `REVINFO` table. +This would not enable correct versioning of relations, but it would work without the `REVINFO` table. Another possibility is to introduce a notion of "revisioning groups", which would group entities sharing the same revision numbering. Each such group would have to consist of one or more strongly connected components belonging to the entity graph induced by relations between entities. @@ -1326,7 +1326,7 @@ Bags are not supported because they can contain non-unique elements. Persisting, a bag of `String`s violates the relational database principle that each table is a set of tuples. In case of bags, however (which require a join table), if there is a duplicate element, the two tuples corresponding to the elements will be the same. -Hibernate allows this, however Envers (or more precisely: the database connector) will throw an exception when trying to persist two identical elements because of a unique constraint violation. +Although Hibernate allows this, Envers (or more precisely: the database connector) will throw an exception when trying to persist two identical elements because of a unique constraint violation. There are at least two ways out if you need bag semantics: @@ -1344,7 +1344,7 @@ Envers, however, has to do this so that when you read the revisions in which the To be able to name the additional join table, there is a special annotation: `@AuditJoinTable`, which has similar semantics to JPA `@JoinTable`. -One special case are relations mapped with `@OneToMany` with `@JoinColumn` on the one side, and `@ManyToOne` and `@JoinColumn( insertable=false, updatable=false`) on the many side. +One special case is to have relations mapped with `@OneToMany` with `@JoinColumn` on the one side, and `@ManyToOne` and `@JoinColumn( insertable=false, updatable=false`) on the many side. Such relations are, in fact, bidirectional, but the owning side is the collection. To properly audit such relations with Envers, you can use the `@AuditMappedBy` annotation. @@ -1370,7 +1370,7 @@ SQL table partitioning offers a lot of advantages including, but certainly not l === Suitable columns for audit table partitioning Generally, SQL tables must be partitioned on a column that exists within the table. -As a rule it makes sense to use either the _end revision_ or the _end revision timestamp_ column for partitioning of audit tables. +As a rule, it makes sense to use either the _end revision_ or the _end revision timestamp_ column for partitioning of audit tables. [NOTE] ==== @@ -1442,14 +1442,14 @@ The following audit information is available, sorted on in order of occurrence: To partition this data, the _level of relevancy_ must be defined. Consider the following: -. For fiscal year 2006 there is only one revision. +. For the fiscal year 2006, there is only one revision. It has the oldest _revision timestamp_ of all audit rows, but should still be regarded as relevant because it's the latest modification for this fiscal year in the salary table (its _end revision timestamp_ is null). + -Also, note that it would be very unfortunate if in 2011 there would be an update of the salary for fiscal year 2006 (which is possible in until at least 10 years after the fiscal year), +Also, note that it would be very unfortunate if in 2011 there would be an update of the salary for the fiscal year 2006 (which is possible until at least 10 years after the fiscal year), and the audit information would have been moved to a slow disk (based on the age of the __revision timestamp__). Remember that, in this case, Envers will have to update the _end revision timestamp_ of the most recent audit row. -. There are two revisions in the salary of fiscal year 2007 which both have nearly the same _revision timestamp_ and a different __end revision timestamp__. +. There are two revisions in the salary of the fiscal year 2007 which both have nearly the same _revision timestamp_ and a different __end revision timestamp__. On first sight, it is evident that the first revision was a mistake and probably not relevant. The only relevant revision for 2007 is the one with _end revision timestamp_ null. diff --git a/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc b/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc index 6a94ea8b4cfc88c9d7379f7d562139b8f046964e..75333e08f718e8c8c9a49136f7eda9fe82be6e0b 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/events/Events.adoc @@ -42,7 +42,7 @@ include::{sourcedir}/InterceptorTest.java[tags=events-interceptors-session-scope A `SessionFactory`-scoped interceptor is registered with the `Configuration` object prior to building the `SessionFactory`. Unless a session is opened explicitly specifying the interceptor to use, the `SessionFactory`-scoped interceptor will be applied to all sessions opened from that `SessionFactory`. -`SessionFactory`-scoped interceptors must be thread safe. +`SessionFactory`-scoped interceptors must be thread-safe. Ensure that you do not store session-specific states since multiple sessions will use this interceptor potentially concurrently. [[events-interceptors-session-factory-scope-example]] @@ -63,8 +63,8 @@ Many methods of the `Session` interface correlate to an event type. The full range of defined event types is declared as enum values on `org.hibernate.event.spi.EventType`. When a request is made of one of these methods, the Session generates an appropriate event and passes it to the configured event listener(s) for that type. -Applications are free to implement a customization of one of the listener interfaces (i.e., the `LoadEvent` is processed by the registered implementation of the `LoadEventListener` interface), in which case their implementation would -be responsible for processing any `load()` requests made of the `Session`. +Applications can customize the listener interfaces (i.e., the `LoadEvent` is processed by the registered implementation of the `LoadEventListener` interface), in which case their implementations would +be responsible for processing the `load()` requests made of the `Session`. [NOTE] ==== @@ -94,7 +94,7 @@ When you want to customize the entity state transition behavior, you have to opt For example, the `Interceptor#onSave()` method is invoked by Hibernate `AbstractSaveEventListener`. Or, the `Interceptor#onLoad()` is called by the `DefaultPreLoadEventListener`. . you can replace any given default event listener with your own implementation. -When doing this, you should probably extend the default listeners because otherwise you'd have to take care of all the low-level entity state transition logic. +When doing this, you should probably extend the default listeners because otherwise, you'd have to take care of all the low-level entity state transition logic. For example, if you replace the `DefaultPreLoadEventListener` with your own implementation, then, only if you call the `Interceptor#onLoad()` method explicitly, you can mix the custom load event listener with a custom Hibernate interceptor. [[events-declarative-security]] @@ -140,7 +140,7 @@ JPA also defines a more limited set of callbacks through annotations. There are two available approaches defined for specifying callback handling: -* The first approach is to annotate methods on the entity itself to receive notification of particular entity life cycle event(s). +* The first approach is to annotate methods on the entity itself to receive notifications of a particular entity lifecycle event(s). * The second is to use a separate entity listener class. An entity listener is a stateless class with a no-arg constructor. The callback annotations are placed on a method of this class instead of the entity class. diff --git a/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc b/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc index 04a2d8e67bd92e25b9d3744dfc59548741981f60..c6a423f1c60a80e01fcb7b78a4a8d72f8a91960c 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc @@ -8,7 +8,7 @@ Tuning how an application does fetching is one of the biggest factors in determi Fetching too much data, in terms of width (values/columns) and/or depth (results/rows), adds unnecessary overhead in terms of both JDBC communication and ResultSet processing. Fetching too little data might cause additional fetching to be needed. -Tuning how an application fetches data presents a great opportunity to influence the application overall performance. +Tuning how an application fetches data presents a great opportunity to influence the overall application performance. [[fetching-basics]] === The basics @@ -27,7 +27,7 @@ There are a number of scopes for defining fetching: _static_:: Static definition of fetching strategies is done in the mappings. - The statically-defined fetch strategies is used in the absence of any dynamically defined strategies + The statically-defined fetch strategies are used in the absence of any dynamically defined strategies SELECT::: Performs a separate SQL select to load the data. This can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed). This is the strategy generally termed N+1. @@ -40,13 +40,13 @@ _static_:: Performs a separate SQL select to load associated data based on the SQL restriction used to load the owner. Again, this can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed). _dynamic_ (sometimes referred to as runtime):: - Dynamic definition is really use-case centric. There are multiple ways to define dynamic fetching: + The dynamic definition is really use-case centric. There are multiple ways to define dynamic fetching: _fetch profiles_::: defined in mappings, but can be enabled/disabled on the `Session`. HQL/JPQL::: and both Hibernate and JPA Criteria queries have the ability to specify fetching, specific to said query. entity graphs::: Starting in Hibernate 4.2 (JPA 2.1) this is also an option. [[fetching-direct-vs-query]] -=== Direct fetching vs entity queries +=== Direct fetching vs. entity queries To see the difference between direct fetching and entity queries in regard to eagerly fetched associations, consider the following entities: @@ -308,7 +308,7 @@ include::{extrasdir}/fetching-batch-fetching-example.sql[] ---- ==== -As you can see in the example above, there are only two SQL statements used to fetch the `Employee` entities associated to multiple `Department` entities. +As you can see in the example above, there are only two SQL statements used to fetch the `Employee` entities associated with multiple `Department` entities. [TIP] ==== @@ -388,7 +388,7 @@ include::{sourcedir}/FetchModeSubselectTest.java[tags=fetching-strategies-fetch- ---- ==== -Now, we are going to fetch all `Department` entities that match a given filtering criteria +Now, we are going to fetch all `Department` entities that match a given filtering predicate and then navigate their `employees` collections. Hibernate is going to avoid the N+1 query issue by generating a single SQL statement to initialize all `employees` collections diff --git a/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc b/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc index 52ac549780ccaaa195a02f3ed0c4f34631c3faba..4876321cc870b1bb5d609261a0c2dd3cf2f2ca38 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/flushing/Flushing.adoc @@ -7,7 +7,7 @@ Flushing is the process of synchronizing the state of the persistence context wi The `EntityManager` and the Hibernate `Session` expose a set of methods, through which the application developer can change the persistent state of an entity. The persistence context acts as a transactional write-behind cache, queuing any entity state change. -Like any write-behind cache, changes are first applied in-memory and synchronized with the database during flush time. +Like any write-behind cache, changes are first applied in-memory and synchronized with the database during the flush time. The flush operation takes every entity state change and translates it to an `INSERT`, `UPDATE` or `DELETE` statement. [NOTE] @@ -21,7 +21,7 @@ Although JPA defines only two flushing strategies (https://javaee.github.io/java Hibernate has a much broader spectrum of flush types: ALWAYS:: Flushes the `Session` before every query. -AUTO:: This is the default mode and it flushes the `Session` only if necessary. +AUTO:: This is the default mode, and it flushes the `Session` only if necessary. COMMIT:: The `Session` tries to delay the flush until the current `Transaction` is committed, although it might flush prematurely too. MANUAL:: The `Session` flushing is delegated to the application, which must call `Session.flush()` explicitly in order to apply the persistence context changes. @@ -36,7 +36,7 @@ By default, Hibernate uses the `AUTO` flush mode which triggers a flush in the f ==== `AUTO` flush on commit -In the following example, an entity is persisted and then the transaction is committed. +In the following example, an entity is persisted, and then the transaction is committed. [[flushing-auto-flush-commit-example]] .Automatic flushing on commit @@ -79,7 +79,7 @@ include::{extrasdir}/flushing-auto-flush-jpql-example.sql[] ---- ==== -The reason why the `Advertisement` entity query didn't trigger a flush is because there's no overlapping between the `Advertisement` and the `Person` tables: +The reason why the `Advertisement` entity query didn't trigger a flush is that there's no overlapping between the `Advertisement` and the `Person` tables: [[flushing-auto-flush-jpql-entity-example]] .Automatic flushing on JPQL/HQL entities @@ -106,7 +106,7 @@ include::{extrasdir}/flushing-auto-flush-jpql-overlap-example.sql[] ---- ==== -This time, the flush was triggered by a JPQL query because the pending entity persist action overlaps with the query being executed. +This time, the flush was triggered by a JPQL query because the pending entity persists action overlaps with the query being executed. ==== `AUTO` flush on native SQL query @@ -214,7 +214,7 @@ include::{extrasdir}/flushing-always-flush-sql-example.sql[] === `MANUAL` flush Both the `EntityManager` and the Hibernate `Session` define a `flush()` method that, when called, triggers a manual flush. -Hibernate also defines a `MANUAL` flush mode so the persistence context can only be flushed manually. +Hibernate also provides a `MANUAL` flush mode so the persistence context can only be flushed manually. [[flushing-manual-flush-example]] .`MANUAL` flushing @@ -234,14 +234,14 @@ The `INSERT` statement was not executed because the persistence context because [NOTE] ==== -This mode is useful when using multi-request logical transactions and only the last request should flush the persistence context. +This mode is useful when using multi-request logical transactions, and only the last request should flush the persistence context. ==== [[flushing-order]] === Flush operation order From a database perspective, a row state can be altered using either an `INSERT`, an `UPDATE` or a `DELETE` statement. -Because entity state changes are automatically converted to SQL statements, it's important to know which entity actions are associated to a given SQL statement. +Because entity state changes are automatically converted to SQL statements, it's important to know which entity actions are associated with a given SQL statement. `INSERT`:: The `INSERT` statement is generated either by the `EntityInsertAction` or `EntityIdentityInsertAction`. These actions are scheduled by the `persist` operation, either explicitly or through cascading the `PersistEvent` from a parent to a child entity. `DELETE`:: The `DELETE` statement is generated by the `EntityDeleteAction` or `OrphanRemovalAction`. diff --git a/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc b/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc index 2a3b59b08a1011497da48a563252617c07926a0f..b6ea92ca12ed2120fecea265f3e2be559d798872 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc @@ -59,7 +59,7 @@ Any settings prefixed with `hibernate.connection.` (other than the "special ones `hibernate.c3p0.max_size` or `c3p0.maxPoolSize`:: The maximum size of the c3p0 pool. See http://www.mchange.com/projects/c3p0/#maxPoolSize[c3p0 maxPoolSize] `hibernate.c3p0.timeout` or `c3p0.maxIdleTime`:: The Connection idle time. See http://www.mchange.com/projects/c3p0/#maxIdleTime[c3p0 maxIdleTime] `hibernate.c3p0.max_statements` or `c3p0.maxStatements`:: Controls the c3p0 PreparedStatement cache size (if using). See http://www.mchange.com/projects/c3p0/#maxStatements[c3p0 maxStatements] -`hibernate.c3p0.acquire_increment` or `c3p0.acquireIncrement`:: Number of connections c3p0 should acquire at a time when pool is exhausted. See http://www.mchange.com/projects/c3p0/#acquireIncrement[c3p0 acquireIncrement] +`hibernate.c3p0.acquire_increment` or `c3p0.acquireIncrement`:: Number of connections c3p0 should acquire at a time when the pool is exhausted. See http://www.mchange.com/projects/c3p0/#acquireIncrement[c3p0 acquireIncrement] `hibernate.c3p0.idle_test_period` or `c3p0.idleConnectionTestPeriod`:: Idle time before a c3p0 pooled connection is validated. See http://www.mchange.com/projects/c3p0/#idleConnectionTestPeriod[c3p0 idleConnectionTestPeriod] `hibernate.c3p0.initialPoolSize`:: The initial c3p0 pool size. If not specified, default is to use the min pool size. See http://www.mchange.com/projects/c3p0/#initialPoolSize[c3p0 initialPoolSize] Any other settings prefixed with `hibernate.c3p0.`:: Will have the `hibernate.` portion stripped and be passed to c3p0. @@ -194,7 +194,7 @@ Although SQL is relatively standardized, each database vendor uses a subset and This is referred to as the database's dialect. Hibernate handles variations across these dialects through its `org.hibernate.dialect.Dialect` class and the various subclasses for each database vendor. -In most cases Hibernate will be able to determine the proper Dialect to use by asking some questions of the JDBC Connection during bootstrap. +In most cases, Hibernate will be able to determine the proper Dialect to use by asking some questions of the JDBC Connection during bootstrap. For information on Hibernate's ability to determine the proper Dialect to use (and your ability to influence that resolution), see <>. If for some reason it is not able to determine the proper one or you want to use a custom Dialect, you will need to set the `hibernate.dialect` setting. @@ -229,8 +229,8 @@ If for some reason it is not able to determine the proper one or you want to use |MySQL5 |Support for the MySQL database, version 5.x |MySQL5InnoDB |Support for the MySQL database, version 5.x preferring the InnoDB storage engine when exporting tables. |MySQL57InnoDB |Support for the MySQL database, version 5.7 preferring the InnoDB storage engine when exporting tables. May work with newer versions -|MariaDB |Support for the MariadB database. May work with newer versions -|MariaDB53 |Support for the MariadB database, version 5.3 and newer. +|MariaDB |Support for the MariaDB database. May work with newer versions +|MariaDB53 |Support for the MariaDB database, version 5.3 and newer. |Oracle8i |Support for the Oracle database, version 8i |Oracle9i |Support for the Oracle database, version 9i |Oracle10g |Support for the Oracle database, version 10g diff --git a/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc b/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc index 00c665632d5b952075c02cd826f542ba4434772c..faa602627fc2b7db489b314086f08f830d8c5523 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc @@ -100,7 +100,7 @@ If the version number is generated by the database, such as a trigger, use the a [[locking-optimistic-timestamp]] ===== Timestamp -Timestamps are a less reliable way of optimistic locking than version numbers, but can be used by applications for other purposes as well. +Timestamps are a less reliable way of optimistic locking than version numbers but can be used by applications for other purposes as well. Timestamping is automatically used if you the `@Version` annotation on a `Date` or `Calendar` property type. [[locking-optimistic-version-timestamp-example]] @@ -114,7 +114,7 @@ include::{sourcedir}/OptimisticLockingTest.java[tags=locking-optimistic-version- Hibernate can retrieve the timestamp value from the database or the JVM, by reading the value you specify for the `@org.hibernate.annotations.Source` annotation. The value can be either `org.hibernate.annotations.SourceType.DB` or `org.hibernate.annotations.SourceType.VM`. -The default behavior is to use the database, and is also used if you don't specify the annotation at all. +The default behavior is to use the database and is also used if you don't specify the annotation at all. The timestamp can also be generated by the database instead of Hibernate if you use the `@org.hibernate.annotations.Generated(GenerationTime.ALWAYS)` or the `@Source` annotation. @@ -161,7 +161,7 @@ include::{sourcedir}/OptimisticLockTest.java[tags=locking-optimistic-exclude-att ---- ==== -This way, if one tread modifies the `Phone` number while a second thread increments the `callCount` attribute, +This way, if one thread modifies the `Phone` number while a second thread increments the `callCount` attribute, the two concurrent transactions are not going to conflict as illustrated by the following example. [[locking-optimistic-exclude-attribute-example]] @@ -198,7 +198,7 @@ sometimes, you need rely on the actual database row column values to prevent *lo Hibernate supports a form of optimistic locking that does not require a dedicated "version attribute". This is also useful for use with modeling legacy schemas. -The idea is that you can get Hibernate to perform "version checks" using either all of the entity's attributes, or just the attributes that have changed. +The idea is that you can get Hibernate to perform "version checks" using either all of the entity's attributes or just the attributes that have changed. This is achieved through the use of the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLocking.html[`@OptimisticLocking`] annotation which defines a single attribute of type @@ -314,7 +314,7 @@ Hibernate always uses the locking mechanism of the database, and never lock obje Long before JPA 1.0, Hibernate already defined various explicit locking strategies through its `LockMode` enumeration. JPA comes with its own http://docs.oracle.com/javaee/7/api/javax/persistence/LockModeType.html[`LockModeType`] enumeration which defines similar strategies as the Hibernate-native `LockMode`. -[cols=",",, options="header"] +[cols=",,",, options="header"] |======================================================================= |`LockModeType`|`LockMode`|Description @@ -322,7 +322,7 @@ JPA comes with its own http://docs.oracle.com/javaee/7/api/javax/persistence/Loc |`READ` and `OPTIMISTIC`|`READ` | The entity version is checked towards the end of the currently running transaction. |`WRITE` and `OPTIMISTIC_FORCE_INCREMENT`|`WRITE` | The entity version is incremented automatically even if the entity has not changed. |`PESSIMISTIC_FORCE_INCREMENT`|`PESSIMISTIC_FORCE_INCREMENT` | The entity is locked pessimistically and its version is incremented automatically even if the entity has not changed. -|`PESSIMISTIC_READ`|`PESSIMISTIC_READ` | The entity is locked pessimistically using a shared lock, if the database supports such a feature. Otherwise, an explicit lock is used. +|`PESSIMISTIC_READ`|`PESSIMISTIC_READ` | The entity is locked pessimistically using a shared lock if the database supports such a feature. Otherwise, an explicit lock is used. |`PESSIMISTIC_WRITE`|`PESSIMISTIC_WRITE`, `UPGRADE` | The entity is locked using an explicit lock. |`PESSIMISTIC_WRITE` with a `javax.persistence.lock.timeout` setting of 0 |`UPGRADE_NOWAIT` | The lock acquisition request fails fast if the row s already locked. |`PESSIMISTIC_WRITE` with a `javax.persistence.lock.timeout` setting of -2 |`UPGRADE_SKIPLOCKED` | The lock acquisition request skips the already locked rows. It uses a `SELECT ... FOR UPDATE SKIP LOCKED` in Oracle and PostgreSQL 9.5, or `SELECT ... with (rowlock, updlock, readpast) in SQL Server`. @@ -385,7 +385,7 @@ The `javax.persistence.lock.scope` is https://hibernate.atlassian.net/browse/HHH Traditionally, Hibernate offered the `Session#lock()` method for acquiring an optimistic or a pessimistic lock on a given entity. Because varying the locking options was difficult when using a single `LockMode` parameter, Hibernate has added the `Session#buildLockRequest()` method API. -The following example shows how to obtain shared database lock without waiting for the lock acquisition request. +The following example shows how to obtain a shared database lock without waiting for the lock acquisition request. [[locking-buildLockRequest-example]] .`buildLockRequest` example @@ -448,8 +448,8 @@ include::{extrasdir}/locking-follow-on-secondary-query-example.sql[] The lock request was moved from the original query to a secondary one which takes the previously fetched entities to lock their associated database records. -Prior to Hibernate 5.2.1, the the follow-on-locking mechanism was applied uniformly to any locking query executing on Oracle. -Since 5.2.1, the Oracle Dialect tries to figure out if the current query demand the follow-on-locking mechanism. +Prior to Hibernate 5.2.1, the follow-on-locking mechanism was applied uniformly to any locking query executing on Oracle. +Since 5.2.1, the Oracle Dialect tries to figure out if the current query demands the follow-on-locking mechanism. Even more important is that you can overrule the default follow-on-locking detection logic and explicitly enable or disable it on a per query basis. @@ -469,6 +469,6 @@ include::{extrasdir}/locking-follow-on-explicit-example.sql[] [NOTE] ==== -The follow-on-locking mechanism should be explicitly enabled only if the current executing query fails because the `FOR UPDATE` clause cannot be applied, meaning that the Dialect resolving mechanism needs to be further improved. +The follow-on-locking mechanism should be explicitly enabled only if the currently executing query fails because the `FOR UPDATE` clause cannot be applied, meaning that the Dialect resolving mechanism needs to be further improved. ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc b/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc index 69b90ae4252674bf9028e44473867f04c555e5a9..16fb19f5de33782e71e2ea8012d455634baf4f9f 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/multitenancy/MultiTenancy.adoc @@ -75,7 +75,7 @@ include::{sourcedir}/AbstractMultiTenancyTest.java[tags=multitenacy-hibernate-se Additionally, when specifying the configuration, an `org.hibernate.MultiTenancyStrategy` should be named using the `hibernate.multiTenancy` setting. Hibernate will perform validations based on the type of strategy you specify. -The strategy here correlates to the isolation approach discussed above. +The strategy here correlates with the isolation approach discussed above. NONE:: (the default) No multitenancy is expected. diff --git a/documentation/src/main/asciidoc/userguide/chapters/osgi/OSGi.adoc b/documentation/src/main/asciidoc/userguide/chapters/osgi/OSGi.adoc index e56095d989475be5bb3a5e2467cb66d23bb06709..5bfb5ceea03d811e2318b7e842e8e721d825fae6 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/osgi/OSGi.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/osgi/OSGi.adoc @@ -53,10 +53,10 @@ In order to utilize container-managed JPA, an Enterprise OSGi JPA container must In Karaf, this means Aries JPA, which is included out-of-the-box (simply activate the `jpa` and `transaction` features). Originally, we intended to include those dependencies within our own `features.xml`. However, after guidance from the Karaf and Aries teams, it was pulled out. -This allows Hibernate OSGi to be portable and not be directly tied to Aries versions, instead having the user choose which to use. +This allows Hibernate OSGi to be portable and not be directly tied to Aries versions, instead of having the user choose which to use. That being said, the QuickStart/Demo projects include a sample https://github.com/hibernate/hibernate-demos/tree/master/hibernate-orm/osgi/managed-jpa/features.xml[features.xml] -showing which features need activated in Karaf in order to support this environment. +showing which features need to be activated in Karaf in order to support this environment. As mentioned, use this purely as a reference! === persistence.xml @@ -186,7 +186,7 @@ include::{sourcedir}/_native/HibernateUtil.java[tag=osgi-discover-SessionFactory The https://github.com/hibernate/hibernate-demos/tree/master/hibernate-orm/osgi/unmanaged-native[unmanaged-native] demo project displays the use of optional Hibernate modules. Each module adds additional dependency bundles that must first be activated, either manually or through an additional feature. As of ORM 4.2, Envers is fully supported. -Support for C3P0, Proxool, EhCache, and Infinispan were added in 4.3, however none of their 3rd party libraries currently work in OSGi (lots of `ClassLoader` problems, etc.). +Support for C3P0, Proxool, EhCache, and Infinispan were added in 4.3. However, none of their 3rd party libraries currently work in OSGi (lots of `ClassLoader` problems, etc.). We're tracking the issues in JIRA. === Extension Points @@ -201,7 +201,7 @@ The specified interface should be used during service registration. `org.hibernate.integrator.spi.Integrator`:: (as of 4.2) `org.hibernate.boot.registry.selector.StrategyRegistrationProvider`:: (as of 4.3) `org.hibernate.boot.model.TypeContributor`:: (as of 4.3) -JTA's:: `javax.transaction.TransactionManager` and `javax.transaction.UserTransaction` (as of 4.2), however these are typically provided by the OSGi container. +JTA's:: `javax.transaction.TransactionManager` and `javax.transaction.UserTransaction` (as of 4.2). However, these are typically provided by the OSGi container. The easiest way to register extension point implementations is through a `blueprint.xml` file. Add `OSGI-INF/blueprint/blueprint.xml` to your classpath. Envers' blueprint is a great example: @@ -225,10 +225,10 @@ Extension points can also be registered programmatically with `BundleContext#reg * Scanning is supported to find non-explicitly listed entities and mappings. However, they MUST be in the same bundle as your persistence unit (fairly typical anyway). Our OSGi `ClassLoader` only considers the "requesting bundle" (hence the requirement on using services to create `EntityManagerFactory`/`SessionFactory`), rather than attempting to scan all available bundles. - This is primarily for versioning considerations, collision protections, etc. + This is primarily for versioning considerations, collision protection, etc. * Some containers (ex: Aries) always return true for `PersistenceUnitInfo#excludeUnlistedClasses`, even if your `persistence.xml` explicitly has `exclude-unlisted-classes` set to `false`. They claim it's to protect JPA providers from having to implement scanning ("we handle it for you"), even though we still want to support it in many cases. - The work around is to set `hibernate.archive.autodetection` to, for example, `hbm,class`. + The workaround is to set `hibernate.archive.autodetection` to, for example, `hbm,class`. This tells hibernate to ignore the `excludeUnlistedClasses` value and scan for `*.hbm.xml` and entities regardless. * Scanning does not currently support annotated packages on `package-info.java`. * Currently, Hibernate OSGi is primarily tested using Apache Karaf and Apache Aries JPA. Additional testing is needed with Equinox, Gemini, and other container providers. diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/BytecodeEnhancement.adoc b/documentation/src/main/asciidoc/userguide/chapters/pc/BytecodeEnhancement.adoc index 83b9b39af8e5c4d9653c33cf1a6ebc6a53ad5a57..70b52470701f34568d638119ba3e3b94ae145610 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/pc/BytecodeEnhancement.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/BytecodeEnhancement.adoc @@ -17,11 +17,11 @@ Hibernate supports the enhancement of an application Java domain model for the p ===== Lazy attribute loading Think of this as partial loading support. -Essentially you can tell Hibernate that only part(s) of an entity should be loaded upon fetching from the database and when the other part(s) should be loaded as well. -Note that this is very much different from proxy-based idea of lazy loading which is entity-centric where the entity's state is loaded at once as needed. +Essentially, you can tell Hibernate that only part(s) of an entity should be loaded upon fetching from the database and when the other part(s) should be loaded as well. +Note that this is very much different from the proxy-based idea of lazy loading which is entity-centric where the entity's state is loaded at once as needed. With bytecode enhancement, individual attributes or groups of attributes are loaded as needed. -Lazy attributes can be designated to be loaded together and this is called a "lazy group". +Lazy attributes can be designated to be loaded together, and this is called a "lazy group". By default, all singular attributes are part of a single group, meaning that when one lazy singular attribute is accessed all lazy singular attributes are loaded. Lazy plural attributes, by default, are each a lazy group by themselves. This behavior is explicitly controllable through the `@org.hibernate.annotations.LazyGroup` annotation. @@ -35,9 +35,9 @@ include::{sourcedir}/BytecodeEnhancementTest.java[tags=BytecodeEnhancement-lazy- ---- ==== -In the above example we have 2 lazy attributes: `accountsPayableXrefId` and `image`. +In the above example, we have 2 lazy attributes: `accountsPayableXrefId` and `image`. Each is part of a different fetch group (accountsPayableXrefId is part of the default fetch group), -which means that accessing `accountsPayableXrefId` will not force the loading of image, and vice-versa. +which means that accessing `accountsPayableXrefId` will not force the loading of the `image` attribute, and vice-versa. [NOTE] ==== @@ -52,11 +52,11 @@ Historically Hibernate only supported diff-based dirty calculation for determini This essentially means that Hibernate would keep track of the last known state of an entity in regards to the database (typically the last read or write). Then, as part of flushing the persistence context, Hibernate would walk every entity associated with the persistence context and check its current state against that "last known database state". This is by far the most thorough approach to dirty checking because it accounts for data-types that can change their internal state (`java.util.Date` is the prime example of this). -However, in a persistence context with a large number of associated entities it can also be a performance-inhibiting approach. +However, in a persistence context with a large number of associated entities, it can also be a performance-inhibiting approach. If your application does not need to care about "internal state changing data-type" use cases, bytecode-enhanced dirty tracking might be a worthwhile alternative to consider, especially in terms of performance. In this approach Hibernate will manipulate the bytecode of your classes to add "dirty tracking" directly to the entity, allowing the entity itself to keep track of which of its attributes have changed. -During flush time, Hibernate simply asks your entity what has changed rather that having to perform the state-diff calculations. +During the flush time, Hibernate asks your entity what has changed rather than having to perform the state-diff calculations. [[BytecodeEnhancement-dirty-tracking-bidirectional]] ===== Bidirectional association management @@ -105,11 +105,11 @@ These are hard to discuss without diving into a discussion of Hibernate internal ==== Performing enhancement [[BytecodeEnhancement-enhancement-runtime]] -===== Run-time enhancement +===== Runtime enhancement -Currently, run-time enhancement of the domain model is only supported in managed JPA environments following the JPA-defined SPI for performing class transformations. +Currently, runtime enhancement of the domain model is only supported in managed JPA environments following the JPA-defined SPI for performing class transformations. -Even then, this support is disabled by default. To enable run-time enhancement, specify one of the following configuration properties: +Even then, this support is disabled by default. To enable runtime enhancement, specify one of the following configuration properties: `*hibernate.enhancer.enableDirtyTracking*` (e.g. `true` or `false` (default value)):: Enable dirty tracking feature in runtime bytecode enhancement. @@ -122,14 +122,14 @@ Enable association management feature in runtime bytecode enhancement which auto [NOTE] ==== -Also, at the moment, only annotated classes are supported for run-time enhancement. +Also, at the moment, only annotated classes are supported for runtime enhancement. ==== [[BytecodeEnhancement-enhancement-gradle]] ===== Gradle plugin Hibernate provides a Gradle plugin that is capable of providing build-time enhancement of the domain model as they are compiled as part of a Gradle build. -To use the plugin a project would first need to apply it: +To use the plugin, a project would first need to apply it: .Apply the Gradle plugin ==== @@ -157,7 +157,7 @@ Hibernate provides a Maven plugin capable of providing build-time enhancement of See the section on the <> for details on the configuration settings. Again, the default for those 3 is `false`. The Maven plugin supports one additional configuration settings: failOnError, which controls what happens in case of error. -Default behavior is to fail the build, but it can be set so that only a warning is issued. +The default behavior is to fail the build, but it can be set so that only a warning is issued. .Apply the Maven plugin ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc index 76cfe479c9cb908ad963a48bdd1f46e28fe93058..6af07c251e9b01f811ddf91e77b9eb74f7fb1aa9 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc @@ -1,5 +1,5 @@ [[pc]] -== Persistence Contexts +== Persistence Context :sourcedir: ../../../../../test/java/org/hibernate/userguide/pc :sourcedir-caching: ../../../../../test/java/org/hibernate/userguide/caching :extrasdir: extras @@ -12,8 +12,8 @@ Persistent data has a state in relation to both a persistence context and the un It has no persistent representation in the database and typically no identifier value has been assigned (unless the _assigned_ generator was used). `managed`, or `persistent`:: the entity has an associated identifier and is associated with a persistence context. It may or may not physically exist in the database yet. -`detached`:: the entity has an associated identifier, but is no longer associated with a persistence context (usually because the persistence context was closed or the instance was evicted from the context) -`removed`:: the entity has an associated identifier and is associated with a persistence context, however it is scheduled for removal from the database. +`detached`:: the entity has an associated identifier but is no longer associated with a persistence context (usually because the persistence context was closed or the instance was evicted from the context) +`removed`:: the entity has an associated identifier and is associated with a persistence context, however, it is scheduled for removal from the database. Much of the `org.hibernate.Session` and `javax.persistence.EntityManager` methods deal with moving entities between these states. @@ -78,7 +78,7 @@ include::{sourcedir}/PersistenceContextTest.java[tags=pc-remove-jpa-example] ==== [[pc-remove-native-example]] -.Deleting an entity with Hibernate API +.Deleting an entity with the Hibernate API ==== [source, JAVA, indent=0] ---- @@ -91,7 +91,7 @@ include::{sourcedir}/PersistenceContextTest.java[tags=pc-remove-native-example] Hibernate itself can handle deleting detached state. JPA, however, disallows it. The implication here is that the entity instance passed to the `org.hibernate.Session` delete method can be either in managed or detached state, -while the entity instance passed to remove on `javax.persistence.EntityManager` must be in managed state. +while the entity instance passed to remove on `javax.persistence.EntityManager` must be in the managed state. ==== [[pc-get-reference]] @@ -177,7 +177,7 @@ include::{sourcedir}/PersistenceContextTest.java[tags=pc-find-optional-by-id-nat [[pc-find-natural-id]] === Obtain an entity by natural-id -In addition to allowing to load by identifier, Hibernate allows applications to load by declared natural identifier. +In addition to allowing to load the entity by its identifier, Hibernate allows applications to load entities by the declared natural identifier. [[pc-find-by-natural-id-entity-example]] .Natural-id mapping @@ -219,23 +219,23 @@ include::{sourcedir}/PersistenceContextTest.java[tags=pc-find-optional-by-simple ---- ==== -Hibernate offer a consistent API for accessing persistent data by identifier or by the natural-id. Each of these defines the same two data access methods: +Hibernate offers a consistent API for accessing persistent data by identifier or by the natural-id. Each of these defines the same two data access methods: getReference:: Should be used in cases where the identifier is assumed to exist, where non-existence would be an actual error. Should never be used to test existence. That is because this method will prefer to create and return a proxy if the data is not already associated with the Session rather than hit the database. - The quintessential use-case for using this method is to create foreign-key based associations. + The quintessential use-case for using this method is to create foreign key based associations. load:: Will return the persistent data associated with the given identifier value or null if that identifier does not exist. -Each of these two methods define an overloading variant accepting a `org.hibernate.LockOptions` argument. +Each of these two methods defines an overloading variant accepting a `org.hibernate.LockOptions` argument. Locking is discussed in a separate <>. [[pc-managed-state]] === Modifying managed/persistent state -Entities in managed/persistent state may be manipulated by the application and any changes will be automatically detected and persisted when the persistence context is flushed. +Entities in managed/persistent state may be manipulated by the application, and any changes will be automatically detected and persisted when the persistence context is flushed. There is no need to call a particular method to make your modifications persistent. [[pc-managed-state-jpa-example]] @@ -320,7 +320,7 @@ include::{sourcedir}/DynamicUpdateTest.java[tags=pc-managed-state-dynamic-update ---- ==== -This time, when reruning the previous test case, Hibernate generates the following SQL UPDATE statement: +This time, when rerunning the previous test case, Hibernate generates the following SQL UPDATE statement: [[pc-managed-state-dynamic-update-example]] .Modifying the `Product` entity with a dynamic update @@ -416,12 +416,12 @@ Clearing the persistence context has the same effect. Evicting a particular entity from the persistence context makes it detached. And finally, serialization will make the deserialized form be detached (the original instance is still managed). -Detached data can still be manipulated, however the persistence context will no longer automatically know about these modification and the application will need to intervene to make the changes persistent again. +Detached data can still be manipulated, however, the persistence context will no longer automatically know about these modifications, and the application will need to intervene to make the changes persistent again. [[pc-detach-reattach]] ==== Reattaching detached data -Reattachment is the process of taking an incoming entity instance that is in detached state and re-associating it with the current persistence context. +Reattachment is the process of taking an incoming entity instance that is in the detached state and re-associating it with the current persistence context. [IMPORTANT] ==== @@ -459,7 +459,7 @@ Provided the entity is detached, `update` and `saveOrUpdate` operate exactly the [[pc-merge]] ==== Merging detached data -Merging is the process of taking an incoming entity instance that is in detached state and copying its data over onto a new managed instance. +Merging is the process of taking an incoming entity instance that is in the detached state and copying its data over onto a new managed instance. Although not exactly per se, the following example is a good visualization of the `merge` operation internals. @@ -829,7 +829,12 @@ as illustrated by the following example. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/CascadeOnDeleteTest.java[tags=pc-cascade-on-delete-mapping-example] +include::{sourcedir}/CascadeOnDeleteTest.java[tags=pc-cascade-on-delete-mapping-Person-example] +---- + +[source, JAVA, indent=0] +---- +include::{sourcedir}/CascadeOnDeleteTest.java[tags=pc-cascade-on-delete-mapping-Phone-example] ---- [source, SQL, indent=0] @@ -852,4 +857,65 @@ include::{sourcedir}/CascadeOnDeleteTest.java[tags=pc-cascade-on-delete-example] ---- include::{extrasdir}/pc-cascade-on-delete-example.sql[] ---- +==== + +[[pc-exception-handling]] +=== Exception handling + +If the JPA `EntityManager` or the Hibernate-specific `Session` throws an exception, including any JDBC https://docs.oracle.com/javase/8/docs/api/java/sql/SQLException.html[`SQLException`], you have to immediately rollback the database transaction and close the current `EntityManager` or `Session`. + +Certain methods of the JPA `EntityManager` or the Hibernate `Session` will not leave the Persistence Context in a consistent state. As a rule of thumb, no exception thrown by Hibernate can be treated as recoverable. Ensure that the Session will be closed by calling the `close()` method in a finally block. + +Rolling back the database transaction does not put your business objects back into the state they were at the start of the transaction. This means that the database state and the business objects will be out of sync. Usually, this is not a problem because exceptions are not recoverable and you will have to start over after rollback anyway. + +The JPA https://docs.oracle.com/javaee/7/api/javax/persistence/PersistenceException.html[`PersistenceException`] or the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/HibernateException.html[`HibernateException`] wraps most of the errors that can occur in a Hibernate persistence layer. + +Both the `PersistenceException` and the `HibernateException` are runtime exceptions because, in our opinion, we should not force the application developer to catch an unrecoverable exception at a low layer. In most systems, unchecked and fatal exceptions are handled in one of the first frames of the method call stack (i.e., in higher layers) and either an error message is presented to the application user or some other appropriate action is taken. Note that Hibernate might also throw other unchecked exceptions that are not a `HibernateException`. These are not recoverable either, and appropriate action should be taken. + +Hibernate wraps the JDBC `SQLException`, thrown while interacting with the database, in a +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/JDBCException.html[`JDBCException`]. +In fact, Hibernate will attempt to convert the exception into a more meaningful subclass of `JDBCException`. The underlying `SQLException` is always available via https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/JDBCException.html#getSQLException--[`JDBCException.getSQLException()`]. Hibernate converts the `SQLException` into an appropriate JDBCException subclass using the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/exception/spi/SQLExceptionConverter.html[`SQLExceptionConverter`] +attached to the current `SessionFactory`. + +By default, the `SQLExceptionConverter` is defined by the configured Hibernate `Dialect` via the +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html#buildSQLExceptionConversionDelegate--[`buildSQLExceptionConversionDelegate`] method +which is overridden by several database-specific `Dialects`. + +However, it is also possible to plug in a custom implementation. See the +<> configuration property for more details. + +The standard `JDBCException` subtypes are: + +ConstraintViolationException:: + indicates some form of integrity constraint violation. +DataException:: + indicates that evaluation of the valid SQL statement against the given data + resulted in some illegal operation, mismatched types, truncation or incorrect cardinality. +GenericJDBCException:: + a generic exception which did not fall into any of the other categories. +JDBCConnectionException:: + indicates an error with the underlying JDBC communication. +LockAcquisitionException:: + indicates an error acquiring a lock level necessary to perform the requested operation. +LockTimeoutException:: + indicates that the lock acquisition request has timed out. +PessimisticLockException:: + indicates that a lock acquisition request has failed. +QueryTimeoutException:: + indicates that the current executing query has timed out. +SQLGrammarException:: + indicates a grammar or syntax problem with the issued SQL. + +[NOTE] +==== +Starting with Hibernate 5.2, the Hibernate `Session` extends the JPA `EntityManager`. For this reason, when a `SessionFactory` is built via Hibernate's native bootstrapping, +the `HibernateException` or `SQLException` can be wrapped in a JPA https://docs.oracle.com/javaee/7/api/javax/persistence/PersistenceException.html[`PersistenceException`] when thrown +by `Session` methods that implement `EntityManager` methods (e.g., https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Session.html#merge-java.lang.Object-[Session.merge(Object object)], +https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Session.html#flush--[Session.flush()]). + +If your `SessionFactory` is built via Hibernate's native bootstrapping, and you don't want the Hibernate exceptions to be wrapped in the JPA `PersistenceException`, you need to set the +`hibernate.native_exception_handling_51_compliance` configuration property to `true`. See the +<> configuration property for more details. ==== \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc b/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc index 8a96fa7d6210cf316891ec5e07827ec0752b3aea..503c19a7c23899fb3aa269687e1a489a006443bf 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc @@ -24,7 +24,7 @@ Originally, Hibernate would always require that users specify which dialect to u Generally, this required their users to configure the Hibernate dialect or defining their own method of setting that value. Starting with version 3.2, Hibernate introduced the notion of automatically detecting the dialect to use based on the `java.sql.DatabaseMetaData` obtained from a `java.sql.Connection` to that database. -This was much better, expect that this resolution was limited to databases Hibernate know about ahead of time and was in no way configurable or overrideable. +This was much better, except that this resolution was limited to databases Hibernate know about ahead of time and was in no way configurable or overrideable. Starting with version 3.3, Hibernate has a fare more powerful way to automatically determine which dialect to should be used by relying on a series of delegates which implement the `org.hibernate.dialect.resolver.DialectResolver` which defines only a single method: @@ -35,7 +35,7 @@ public Dialect resolveDialect(DatabaseMetaData metaData) throws JDBCConnectionEx The basic contract here is that if the resolver 'understands' the given database metadata then it returns the corresponding Dialect; if not it returns null and the process continues to the next resolver. The signature also identifies `org.hibernate.exception.JDBCConnectionException` as possibly being thrown. -A `JDBCConnectionException` here is interpreted to imply a "non transient" (aka non-recoverable) connection problem and is used to indicate an immediate stop to resolution attempts. +A `JDBCConnectionException` here is interpreted to imply a __non-transient__ (aka non-recoverable) connection problem and is used to indicate an immediate stop to resolution attempts. All other exceptions result in a warning and continuing on to the next resolver. The cool part about these resolvers is that users can also register their own custom resolvers which will be processed ahead of the built-in Hibernate ones. @@ -50,14 +50,14 @@ To register one or more resolvers, simply specify them (separated by commas, tab === Identifier generation When considering portability between databases, another important decision is selecting the identifier generation strategy you want to use. -Originally Hibernate provided the _native_ generator for this purpose, which was intended to select between a __sequence__, __identity__, or _table_ strategy depending on the capability of the underlying database. +Originally, Hibernate provided the _native_ generator for this purpose, which was intended to select between a __sequence__, __identity__, or _table_ strategy depending on the capability of the underlying database. However, an insidious implication of this approach comes about when targeting some databases which support _identity_ generation and some which do not. _identity_ generation relies on the SQL definition of an IDENTITY (or auto-increment) column to manage the identifier value. It is what is known as a _post-insert_ generation strategy because the insert must actually happen before we can know the identifier value. Because Hibernate relies on this identifier value to uniquely reference entities within a persistence context, -it must then issue the insert immediately when the users requests that the entity be associated with the session (e.g. like via `save()` or `persist()`) , regardless of current transactional semantics. +it must then issue the insert immediately when the user requests that the entity be associated with the session (e.g. like via `save()` or `persist()`), regardless of current transactional semantics. [NOTE] ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc index a6e3e4d4e1d46bef91eb38d323591bd4fcf0a83b..cc86b78d9a89091eefb6c6a28da5d351803608e1 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/criteria/Criteria.adoc @@ -18,9 +18,9 @@ They are type-safe in terms of using interfaces and classes to represent various They can also be type-safe in terms of referencing attributes as we will see in a bit. Users of the older Hibernate `org.hibernate.Criteria` query API will recognize the general approach, though we believe the JPA API to be superior as it represents a clean look at the lessons learned from that API. -Criteria queries are essentially an object graph, where each part of the graph represents an increasing (as we navigate down this graph) more atomic part of query. +Criteria queries are essentially an object graph, where each part of the graph represents an increasing (as we navigate down this graph) more atomic part of the query. The first step in performing a criteria query is building this graph. -The `javax.persistence.criteria.CriteriaBuilder` interface is the first thing with which you need to become acquainted to begin using criteria queries. +The `javax.persistence.criteria.CriteriaBuilder` interface is the first thing with which you need to become acquainted with begin using criteria queries. Its role is that of a factory for all the individual pieces of the criteria. You obtain a `javax.persistence.criteria.CriteriaBuilder` instance by calling the `getCriteriaBuilder()` method of either `javax.persistence.EntityManagerFactory` or `javax.persistence.EntityManager`. @@ -69,7 +69,7 @@ It was done here only for completeness of an example. The `Person_.name` reference is an example of the static form of JPA Metamodel reference. We will use that form exclusively in this chapter. -See the documentation for the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/topical/html/metamodelgen/MetamodelGenerator.html[Hibernate JPA Metamodel Generator] for additional details on the JPA static Metamodel. +See the documentation for the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/topical/html_single/metamodelgen/MetamodelGenerator.html[Hibernate JPA Metamodel Generator] for additional details on the JPA static Metamodel. ==== [[criteria-typedquery-expression]] @@ -148,7 +148,7 @@ Specifically, notice the constructor and its argument types. Since we will be returning `PersonWrapper` objects, we use `PersonWrapper` as the type of our criteria query. This example illustrates the use of the `javax.persistence.criteria.CriteriaBuilder` method construct which is used to build a wrapper expression. -For every row in the result we are saying we would like a `PersonWrapper` instantiated with the remaining arguments by the matching constructor. +For every row in the result, we are saying we would like a `PersonWrapper` instantiated with the remaining arguments by the matching constructor. This wrapper expression is then passed as the select. [[criteria-tuple]] @@ -273,7 +273,7 @@ include::{sourcedir}/CriteriaTest.java[tags=criteria-from-fetch-example] [NOTE] ==== Technically speaking, embedded attributes are always fetched with their owner. -However in order to define the fetching of _Phone#addresses_ we needed a `javax.persistence.criteria.Fetch` because element collections are `LAZY` by default. +However, in order to define the fetching of _Phone#addresses_ we needed a `javax.persistence.criteria.Fetch` because element collections are `LAZY` by default. ==== [[criteria-path]] diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc index 3e25f6b17b9bec2f3695c42b330c3a0a28c96bc6..3047cce411ae955ed69ae7692686fac4633f8660 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc @@ -6,7 +6,7 @@ The Hibernate Query Language (HQL) and Java Persistence Query Language (JPQL) are both object model focused query languages similar in nature to SQL. JPQL is a heavily-inspired-by subset of HQL. -A JPQL query is always a valid HQL query, the reverse is not true however. +A JPQL query is always a valid HQL query, the reverse is not true, however. Both HQL and JPQL are non-type-safe ways to perform query operations. Criteria queries offer a type-safe approach to querying. See <> for more information. @@ -15,7 +15,7 @@ Criteria queries offer a type-safe approach to querying. See <> for additional details on collection related expressions. +See <> for additional details on collection-related expressions. [[hql-polymorphism]] === Polymorphism @@ -987,7 +987,7 @@ It returns every object of every entity type defined by your application mapping [[hql-expressions]] === Expressions -Essentially expressions are references that resolve to basic or tuple values. +Essentially, expressions are references that resolve to basic or tuple values. [[hql-identification-variable]] === Identification variable @@ -1036,7 +1036,7 @@ The actual suffix is case-insensitive. The boolean literals are `TRUE` and `FALSE`, again case-insensitive. Enums can even be referenced as literals. The fully-qualified enum class name must be used. -HQL can also handle constants in the same manner, though JPQL does not define that as supported. +HQL can also handle constants in the same manner, though JPQL does not define that as being supported. Entity names can also be used as literal. See <>. @@ -1046,7 +1046,7 @@ Date/time literals can be specified using the JDBC escape syntax: * `{t 'hh:mm:ss'}` for times * `{ts 'yyyy-mm-dd hh:mm:ss[.millis]'}` (millis optional) for timestamps. -These Date/time literals only work if you JDBC drivers supports them. +These Date/time literals only work if the underlying JDBC driver supports them. ==== [[hql-numeric-arithmetic]] @@ -1073,13 +1073,13 @@ The following rules apply to the result of arithmetic operations: * else, (the assumption being that both operands are of integral type) the result is `Integer` (except for division, in which case the result type is not further defined) Date arithmetic is also supported, albeit in a more limited fashion. -This is due partially to differences in database support and partially to the lack of support for `INTERVAL` definition in the query language itself. +This is due to differences in database support and partly to the lack of support for `INTERVAL` definition in the query language itself. [[hql-concatenation]] === Concatenation (operation) HQL defines a concatenation operator in addition to supporting the concatenation (`CONCAT`) function. -This is not defined by JPQL, so portable applications should avoid it use. +This is not defined by JPQL, so portable applications should avoid its use. The concatenation operator is taken from the SQL concatenation operator (e.g `||`). [[hql-concatenation-example]] @@ -1378,7 +1378,7 @@ ELEMENTS:: Only allowed in the where clause. Often used in conjunction with `ALL`, `ANY` or `SOME` restrictions. INDICES:: - Similar to `elements` except that `indices` refers to the collections indices (keys/positions) as a whole. + Similar to `elements` except that the `indices` expression refers to the collections indices (keys/positions) as a whole. [[hql-collection-expressions-example]] .Collection-related expressions examples @@ -1407,7 +1407,7 @@ See also <> as there is a good deal of overlap. We can also refer to the type of an entity as an expression. This is mainly useful when dealing with entity inheritance hierarchies. -The type can expressed using a `TYPE` function used to refer to the type of an identification variable representing an entity. +The type can be expressed using a `TYPE` function used to refer to the type of an identification variable representing an entity. The name of the entity also serves as a way to refer to an entity type. Additionally, the entity type can be parameterized, in which case the entity's Java Class reference would be bound as the parameter value. @@ -1501,7 +1501,7 @@ There is a particular expression type that is only valid in the select clause. Hibernate calls this "dynamic instantiation". JPQL supports some of that feature and calls it a "constructor expression". -So rather than dealing with the `Object[]` (again, see <>) here we are wrapping the values in a type-safe java object that will be returned as the results of the query. +So rather than dealing with the `Object[]` (again, see <>) here, we are wrapping the values in a type-safe Java object that will be returned as the results of the query. [[hql-select-clause-dynamic-instantiation-example]] .Dynamic HQL and JPQL instantiation example @@ -1558,7 +1558,7 @@ If the user doesn't assign aliases, the key will be the index of each particular === Predicates Predicates form the basis of the where clause, the having clause and searched case expressions. -They are expressions which resolve to a truth value, generally `TRUE` or `FALSE`, although boolean comparisons involving `NULL` generally resolve to `UNKNOWN`. +They are expressions which resolve to a truth value, generally `TRUE` or `FALSE`, although boolean comparisons involving `NULL` resolve typically to `UNKNOWN`. [[hql-relational-comparisons]] === Relational comparisons @@ -1596,8 +1596,8 @@ It resolves to false if the subquery result is empty. [[hql-null-predicate]] === Nullness predicate -Check a value for nullness. -Can be applied to basic attribute references, entity references and parameters. +It check a value for nullness. +It can be applied to basic attribute references, entity references, and parameters. HQL additionally allows it to be applied to component/embeddable types. [[hql-null-predicate-example]] @@ -1678,9 +1678,9 @@ include::{extrasdir}/predicate_in_bnf.txt[] The types of the `single_valued_expression` and the individual values in the `single_valued_list` must be consistent. -JPQL limits the valid types here to string, numeric, date, time, timestamp, and enum types, and , in JPQL, `single_valued_expression` can only refer to: +JPQL limits the valid types here to string, numeric, date, time, timestamp, and enum types, and, in JPQL, `single_valued_expression` can only refer to: -* "state fields", which is its term for simple attributes. Specifically this excludes association and component/embedded attributes. +* "state fields", which is its term for simple attributes. Specifically, this excludes association and component/embedded attributes. * entity type expressions. See <> In HQL, `single_valued_expression` can refer to a far more broad set of expression types. @@ -1752,7 +1752,7 @@ If the predicate is true, NOT resolves to false. If the predicate is unknown (e. The `AND` operator is used to combine 2 predicate expressions. The result of the AND expression is true if and only if both predicates resolve to true. -If either predicates resolves to unknown, the AND expression resolves to unknown as well. Otherwise, the result is false. +If either predicate resolves to unknown, the AND expression resolves to unknown as well. Otherwise, the result is false. [[hql-or-predicate]] === OR predicate operator @@ -1817,7 +1817,7 @@ The types of expressions considered valid as part of the `ORDER BY` clause inclu Additionally, JPQL says that all values referenced in the `ORDER BY` clause must be named in the `SELECT` clause. HQL does not mandate that restriction, but applications desiring database portability should be aware that not all databases support referencing values in the `ORDER BY` clause that are not referenced in the select clause. -Individual expressions in the order-by can be qualified with either `ASC` (ascending) or `DESC` (descending) to indicated the desired ordering direction. +Individual expressions in the order-by can be qualified with either `ASC` (ascending) or `DESC` (descending) to indicate the desired ordering direction. Null values can be placed in front or at the end of the sorted set using `NULLS FIRST` or `NULLS LAST` clause respectively. [[hql-order-by-example]] diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc index 95f2d0da960778b9bffcdcad30e8ab8da9d2c170..3214cba9f9129232692ea9bc5c7b40c0a4c8f68c 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc @@ -5,7 +5,7 @@ :extrasdir: extras You may also express queries in the native SQL dialect of your database. -This is useful if you want to utilize database specific features such as window functions, Common Table Expressions (CTE) or the `CONNECT BY` option in Oracle. +This is useful if you want to utilize database-specific features such as window functions, Common Table Expressions (CTE) or the `CONNECT BY` option in Oracle. It also provides a clean migration path from a direct SQL/JDBC based application to Hibernate/JPA. Hibernate also allows you to specify handwritten SQL (including stored procedures) for all create, update, delete, and retrieve operations. @@ -84,7 +84,7 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-scalar-query-partial-explic ---- ==== -This is essentially the same query as before, but now `ResultSetMetaData` is used to determine the type of `name`, where as the type of `id` is explicitly specified. +This is essentially the same query as before, but now `ResultSetMetaData` is used to determine the type of `name`, whereas the type of `id` is explicitly specified. How the `java.sql.Types` returned from `ResultSetMetaData` is mapped to Hibernate types is controlled by the `Dialect`. If a specific type is not mapped, or does not result in the expected type, it is possible to customize it via calls to `registerHibernateType` in the Dialect. @@ -112,7 +112,7 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-entity-query-example] ---- ==== -Assuming that `Person` is mapped as a class with the columns `id`, `name`, `nickName`, `address`, `createdOn` and `version`, +Assuming that `Person` is mapped as a class with the columns `id`, `name`, `nickName`, `address`, `createdOn`, and `version`, the following query will also return a `List` where each element is a `Person` entity. [[sql-jpa-entity-query-explicit-result-set-example]] @@ -138,7 +138,7 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-entity-query-explicit-resul If the entity is mapped with a `many-to-one` or a child-side `one-to-one` to another entity, it is required to also return this when performing the native query, -otherwise a database specific _column not found_ error will occur. +otherwise, a database-specific _column not found_ error will occur. [[sql-jpa-entity-associations-query-many-to-one-example]] .JPA native query selecting entities with many-to-one association @@ -230,7 +230,7 @@ include::{extrasdir}/sql-hibernate-entity-associations-query-one-to-many-join-ex ---- ==== -At this stage you are reaching the limits of what is possible with native queries, without starting to enhance the sql queries to make them usable in Hibernate. +At this stage, you are reaching the limits of what is possible with native queries, without starting to enhance the sql queries to make them usable in Hibernate. Problems can arise when returning multiple entities of the same type or when the default alias/column names are not enough. [[sql-multi-entity-query]] @@ -261,7 +261,7 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-multi-entity-query-example] The query was intended to return all `Person` and `Partner` instances with the same name. The query fails because there is a conflict of names since the two entities are mapped to the same column names (e.g. `id`, `name`, `version`). -Also, on some databases the returned column aliases will most likely be on the form `pr.id`, `pr.name`, etc. +Also, on some databases, the returned column aliases will most likely be on the form `pr.id`, `pr.name`, etc. which are not equal to the columns specified in the mappings (`id` and `name`). The following form is not vulnerable to column name duplication: @@ -281,13 +281,13 @@ There's no such equivalent in JPA because the `Query` interface doesn't define a ==== The `{pr.*}` and `{pt.*}` notation used above is shorthand for "all properties". -Alternatively, you can list the columns explicitly, but even in this case Hibernate injects the SQL column aliases for each property. +Alternatively, you can list the columns explicitly, but even in this case, Hibernate injects the SQL column aliases for each property. The placeholder for a column alias is just the property name qualified by the table alias. [[sql-alias-references]] === Alias and property references -In most cases the above alias injection is needed. +In most cases, the above alias injection is needed. For queries relating to more complex mappings, like composite properties, inheritance discriminators, collections etc., you can use specific aliases that allow Hibernate to inject the proper aliases. The following table shows the different ways you can use the alias injection. @@ -409,7 +409,7 @@ and the Hibernate `org.hibernate.annotations.NamedNativeQuery` annotation extend `timeout()`:: The query timeout (in seconds). By default, there's no timeout. `callable()`:: - Does the SQL query represent a call to a procedure/function? Default is false. + Does the SQL query represent a call to a procedure/function? The default is false. `comment()`:: A comment added to the SQL query for tuning the execution plan. `cacheMode()`:: @@ -670,7 +670,7 @@ Fortunately, Hibernate allows you to resolve the current global catalog and sche {h-schema}:: resolves the current `hibernate.default_schema` configuration property value. {h-domain}:: resolves the current `hibernate.default_catalog` and `hibernate.default_schema` configuration property values (e.g. catalog.schema). -Withe these placeholders, you can imply the catalog, schema, or both catalog and schema for every native query. +With these placeholders, you can imply the catalog, schema, or both catalog and schema for every native query. So, when running the following native query: @@ -863,15 +863,15 @@ include::{sourcedir}/OracleStoredProcedureTest.java[tags=sql-jpa-call-sp-ref-cur ==== [[sql-crud]] -=== Custom SQL for create, update, and delete +=== Custom SQL for CRUD (Create, Read, Update and Delete) -Hibernate can use custom SQL for create, update, and delete operations. +Hibernate can use custom SQL for CRUD operations. The SQL can be overridden at the statement level or individual column level. This section describes statement overrides. For columns, see <>. The following example shows how to define custom SQL operations using annotations. -`@SQLInsert`, `@SQLUpdate` and `@SQLDelete` override the INSERT, UPDATE, DELETE statements of a given entity. +`@SQLInsert`, `@SQLUpdate`, and `@SQLDelete` override the INSERT, UPDATE, DELETE statements of a given entity. For the SELECT clause, a `@Loader` must be defined along with a `@NamedNativeQuery` used for loading the underlying table record. For collections, Hibernate allows defining a custom `@SQLDeleteAll` which is used for removing all child records associated with a given parent entity. @@ -894,7 +894,8 @@ The same is done for the `phones` collection. The `@SQLDeleteAll` and the `SQLIn [NOTE] ==== -You also call a store procedure using the custom CRUD statements; the only requirement is to set the `callable` attribute to `true`. +You can also call a store procedure using the custom CRUD statements. +The only requirement is to set the `callable` attribute to `true`. ==== To check that the execution happens correctly, Hibernate allows you to define one of those three strategies: @@ -927,7 +928,7 @@ include::{sourcedir}/CustomSQLSecondaryTableTest.java[tags=sql-custom-crud-secon [TIP] ==== The SQL is directly executed in your database, so you can use any dialect you like. -This will, however, reduce the portability of your mapping if you use database specific SQL. +This will, however, reduce the portability of your mapping if you use database-specific SQL. ==== You can also use stored procedures for customizing the CRUD statements. diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/spatial/Spatial.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/spatial/Spatial.adoc index 36e8d9bf8a1bdcceb52f48081f623e9fa5a17982..226c0793435a681dd2676b98e751f760ed0c26ee 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/spatial/Spatial.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/spatial/Spatial.adoc @@ -11,8 +11,8 @@ Since 5.0, Hibernate Spatial is now part of the Hibernate ORM project, and it allows you to deal with geographic data in a standardized way. Hibernate Spatial provides a standardized, cross-database interface to geographic data storage and query functions. -It supports most of the functions described by the OGC Simple Feature Specification. Supported databases are: Oracle 10g/11g, -PostgreSql/PostGIS, MySQL, Microsoft SQL Server and H2/GeoDB. +It supports most of the functions described by the OGC Simple Feature Specification. Supported databases are Oracle 10g/11g, +PostgreSQL/PostGIS, MySQL, Microsoft SQL Server and H2/GeoDB. Spatial data types are not part of the Java standard library, and they are absent from the JDBC specification. Over the years http://tsusiatsoftware.net/jts/main.html[JTS] has emerged the _de facto_ standard to fill this gap. JTS is @@ -148,10 +148,10 @@ There are several dialects for MySQL: MySQL versions before 5.6.1 had only limited support for spatial operators. Most operators only took account of the minimum bounding rectangles (MBR) of the geometries, and not the geometries themselves. -This changed in version 5.6.1 were MySQL introduced `ST_*` spatial operators. +This changed in version 5.6.1, when MySQL introduced `ST_*` spatial operators. The dialect `MySQLSpatial56Dialect` uses these newer, more precise operators. -These dialects may therefore produce results that differ from that of the other spatial dialects. +These dialects may, therefore, produce results that differ from that of the other spatial dialects. For more information, see this page in the MySQL reference guide (esp. the section https://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions.html[Functions That Test Spatial Relations Between Geometry Objects]) ==== @@ -164,21 +164,22 @@ This dialect has been tested on both Oracle 10g and Oracle 11g with the `SDO_GEO This dialect can be configured using the Hibernate property: + `hibernate.spatial.connection_finder`::: -the fully-qualified classname for the implementation of the `ConnectionFinder` to use (see below). +the fully-qualified class name for the implementation of the `ConnectionFinder` to use (see below). .The `ConnectionFinder` interface [NOTE] ==== -The `SDOGeometryType` requires access to an `OracleConnection` object wehen converting a geometry to SDO_GEOMETRY. +The `SDOGeometryType` requires access to an `OracleConnection` object when converting a geometry to SDO_GEOMETRY. In some environments, however, the `OracleConnection` is not available (e.g. because a Java EE container or connection pool proxy wraps the connection object in its own `Connection` implementation). A `ConnectionFinder` knows how to retrieve the `OracleConnection` from the wrapper or proxy Connection object that is passed into prepared statements. -The default implementation will, when the passed object is not already an `OracleConnection`, attempt to retrieve the `OracleConnection` by recursive reflection. +When the passed object is not already an `OracleConnection`, the default implementation will attempt to retrieve the `OracleConnection` by recursive reflection. It will search for methods that return `Connection` objects, execute these methods and check the result. -If the result is of type `OracleConnection` the object is returned, otherwise it recurses on it. +If the result is of type `OracleConnection` the object is returned. +Otherwise, it recurses on it. -In may cases this strategy will suffice. -If not, you can provide your own implementation of this interface on the class path, and configure it in the `hibernate.spatial.connection_finder` property. +In may cases, this strategy will suffice. +If not, you can provide your own implementation of this interface on the classpath, and configure it in the `hibernate.spatial.connection_finder` property. Note that implementations must be thread-safe and have a default no-args constructor. ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc b/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc index fa5d49b0d83bdaa5da803bfb0db1ea6fb140be67..0f2bdfffb0e17536f7bca0c9ccaefd0b80da589d 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc @@ -119,7 +119,7 @@ include::{extrasdir}/schema-generation-database-checks-persist-example.sql[] ==== [[schema-generation-column-default-value]] -=== Default value for database column +=== Default value for a database column With Hibernate, you can specify a default value for a given database column using the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/ColumnDefault.html[`@ColumnDefault`] annotation. diff --git a/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc b/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc index 082988573c9dbb8033af4cb875c731e9c5828393..6a7cc0090e6697272aff0b4cea944b0679d27645 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/transactions/Transactions.adoc @@ -17,10 +17,10 @@ This documentation largely treats the physical and logic notions of a transactio [[transactions-physical]] === Physical Transactions -Hibernate uses the JDBC API for persistence. In the world of Java there are two well-defined mechanism for dealing with transactions in JDBC: JDBC itself and JTA. +Hibernate uses the JDBC API for persistence. In the world of Java, there are two well-defined mechanisms for dealing with transactions in JDBC: JDBC itself and JTA. Hibernate supports both mechanisms for integrating with transactions and allowing applications to manage physical transactions. -Transaction handling per `Session` is handled by the `org.hibernate.resource.transaction.spi.TransactionCoordinator` contract, +The transaction handling per `Session` is handled by the `org.hibernate.resource.transaction.spi.TransactionCoordinator` contract, which are built by the `org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder` service. `TransactionCoordinatorBuilder` represents a strategy for dealing with transactions whereas TransactionCoordinator represents one instance of that strategy related to a Session. Which `TransactionCoordinatorBuilder` implementation to use is defined by the `hibernate.transaction.coordinator_class` setting. @@ -50,9 +50,9 @@ The Hibernate `Session` acts as a transaction-scoped cache providing repeatable [IMPORTANT] ==== To reduce lock contention in the database, the physical database transaction needs to be as short as possible. -Long database transactions prevent your application from scaling to a highly-concurrent load. +Long-running database transactions prevent your application from scaling to a highly-concurrent load. Do not hold a database transaction open during end-user-level work, but open it after the end-user-level work is finished. -This is concept is referred to as `transactional write-behind`. +This concept is referred to as `transactional write-behind`. ==== [[transactions-physical-jtaplatform]] @@ -99,11 +99,11 @@ To use this API, you would obtain the `org.hibernate.Transaction` from the Sessi `markRollbackOnly`:: that works in both JTA and JDBC `getTimeout` and `setTimeout`:: that again work in both JTA and JDBC `registerSynchronization`:: that allows you to register JTA Synchronizations even in non-JTA environments. -In fact in both JTA and JDBC environments, these `Synchronizations` are kept locally by Hibernate. +In fact, in both JTA and JDBC environments, these `Synchronizations` are kept locally by Hibernate. In JTA environments, Hibernate will only ever register one single `Synchronization` with the `TransactionManager` to avoid ordering problems. Additionally, it exposes a getStatus method that returns an `org.hibernate.resource.transaction.spi.TransactionStatus` enum. -This method checks with the underlying transaction system if needed, so care should be taken to minimize its use; it can have a big performance impact in certain JTA set ups. +This method checks with the underlying transaction system if needed, so care should be taken to minimize its use; it can have a big performance impact in certain JTA setups. Let's take a look at using the Transaction API in the various environments. @@ -134,7 +134,7 @@ include::{sourcedir}/TransactionsTest.java[tags=transactions-api-bmt-example] ---- ==== -In the CMT case we really could have omitted all of the Transaction calls. +In the CMT case, we really could have omitted all of the Transaction calls. But the point of the examples was to show that the Transaction API really does insulate your code from the underlying transaction mechanism. In fact, if you strip away the comments and the single configuration setting supplied at bootstrap, the code is exactly the same in all 3 examples. In other words, we could develop that code and drop it, as-is, in any of the 3 transaction environments. @@ -184,7 +184,7 @@ If you use JTA, you can utilize the JTA interfaces to demarcate transactions. If you execute in an EJB container that supports CMT, transaction boundaries are defined declaratively and you do not need any transaction or session demarcation operations in your code. Refer to <> for more information and code examples. The `hibernate.current_session_context_class` configuration parameter defines which `org.hibernate.context.spi.CurrentSessionContext` implementation should be used. -For backwards compatibility, if this configuration parameter is not set but a `org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform` is configured, Hibernate will use the `org.hibernate.context.internal.JTASessionContext`. +For backward compatibility, if this configuration parameter is not set but a `org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform` is configured, Hibernate will use the `org.hibernate.context.internal.JTASessionContext`. === Transactional patterns (and anti-patterns) @@ -195,7 +195,7 @@ This is an anti-pattern of opening and closing a `Session` for each database cal It is also an anti-pattern in terms of database transactions. Group your database calls into a planned sequence. In the same way, do not auto-commit after every SQL statement in your application. -Hibernate disables, or expects the application server to disable, auto-commit mode immediately. +Hibernate disables or expects the application server to disable, auto-commit mode immediately. Database transactions are never optional. All communication with a database must be encapsulated by a transaction. Avoid auto-commit behavior for reading data because many small transactions are unlikely to perform better than one clearly-defined unit of work, and are more difficult to maintain and extend. @@ -216,7 +216,7 @@ Web applications are a prime example of this type of system, though certainly no At the beginning of handling such a request, the application opens a Hibernate Session, starts a transaction, performs all data related work, ends the transaction and closes the Session. The crux of the pattern is the one-to-one relationship between the transaction and the Session. -Within this pattern there is a common technique of defining a current session to simplify the need of passing this `Session` around to all the application components that may need access to it. +Within this pattern, there is a common technique of defining a current session to simplify the need of passing this `Session` around to all the application components that may need access to it. Hibernate provides support for this technique through the `getCurrentSession` method of the `SessionFactory`. The concept of a _current_ session has to have a scope that defines the bounds in which the notion of _current_ is valid. This is the purpose of the `org.hibernate.context.spi.CurrentSessionContext` contract. @@ -230,7 +230,7 @@ Using this implementation, a `Session` will be opened the first time `getCurrent This is best represented with the `org.hibernate.context.internal.ManagedSessionContext` implementation of the `org.hibernate.context.spi.CurrentSessionContext` contract. Here an external component is responsible for managing the lifecycle and scoping of a _current_ session. At the start of such a scope, `ManagedSessionContext#bind()` method is called passing in the `Session`. -At the end, its `unbind()` method is called. +In the end, its `unbind()` method is called. Some common examples of such _external components_ include: ** `javax.servlet.Filter` implementation ** AOP interceptor with a pointcut on the service methods @@ -285,7 +285,7 @@ Automatic versioning is used to isolate concurrent modifications. |Extended `Session` |The Hibernate `Session` can be disconnected from the underlying JDBC connection after the database transaction has been committed and reconnected when a new client request occurs. This pattern is known as session-per-conversation and makes even reattachment unnecessary. -Automatic versioning is used to isolate concurrent modifications and the `Session` will not be allowed to flush automatically, only explicitly. +Automatic versioning is used to isolate concurrent modifications, and the `Session` will not be allowed to flush automatically, only explicitly. |======================================================================= Session-per-request-with-detached-objects and session-per-conversation each have advantages and disadvantages. @@ -295,7 +295,7 @@ Session-per-request-with-detached-objects and session-per-conversation each have The _session-per-application_ is also considered an anti-pattern. The Hibernate `Session`, like the JPA `EntityManager`, is not a thread-safe object and it is intended to be confined to a single thread at once. -If the `Session` is shared among multiple threads, there will be race conditions as well as visibility issues , so beware of this. +If the `Session` is shared among multiple threads, there will be race conditions as well as visibility issues, so beware of this. An exception thrown by Hibernate means you have to rollback your database transaction and close the `Session` immediately. If your `Session` is bound to the application, you have to stop the application. @@ -303,6 +303,8 @@ Rolling back the database transaction does not put your business objects back in This means that the database state and the business objects will be out of sync. Usually, this is not a problem because exceptions are not recoverable and you will have to start over after rollback anyway. +For more details, check out the <> chapter. + The `Session` caches every object that is in a persistent state (watched and checked for dirty state by Hibernate). If you keep it open for a long time or simply load too much data, it will grow endlessly until you get an `OutOfMemoryException`. One solution is to call `clear()` and `evict()` to manage the `Session` cache, but you should consider a Stored Procedure if you need mass data operations. diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalTest.java index e239f3b0b21ae5d48c236009dfedc3a815a70a83..81fb51ead26526fbd4a44f3ca5270d926bf4c638 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalTest.java @@ -71,9 +71,14 @@ public static class Person { @NaturalId private String registrationNumber; + @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) private List
addresses = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-bidirectional-example[] + public Person() { } @@ -85,6 +90,7 @@ public List
getAddresses() { return addresses; } + //tag::associations-many-to-many-bidirectional-example[] public void addAddress(Address address) { addresses.add( address ); address.getOwners().add( this ); @@ -130,6 +136,10 @@ public static class Address { @ManyToMany(mappedBy = "addresses") private List owners = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-bidirectional-example[] + public Address() { } @@ -159,6 +169,7 @@ public List getOwners() { return owners; } + //tag::associations-many-to-many-bidirectional-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalWithLinkEntityTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalWithLinkEntityTest.java index be795f6030737ce2d5cf9925e7a04e5b104d1536..4f4d645945976db6c18bb95ce84598184ea5455d 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalWithLinkEntityTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyBidirectionalWithLinkEntityTest.java @@ -81,9 +81,17 @@ public static class Person implements Serializable { @NaturalId private String registrationNumber; - @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany( + mappedBy = "person", + cascade = CascadeType.ALL, + orphanRemoval = true + ) private List addresses = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-bidirectional-with-link-entity-example[] + public Person() { } @@ -99,6 +107,7 @@ public List getAddresses() { return addresses; } + //tag::associations-many-to-many-bidirectional-with-link-entity-example[] public void addAddress(Address address) { PersonAddress personAddress = new PersonAddress( this, address ); addresses.add( personAddress ); @@ -142,6 +151,10 @@ public static class PersonAddress implements Serializable { @ManyToOne private Address address; + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-bidirectional-with-link-entity-example[] + public PersonAddress() { } @@ -166,6 +179,7 @@ public void setAddress(Address address) { this.address = address; } + //tag::associations-many-to-many-bidirectional-with-link-entity-example[] @Override public boolean equals(Object o) { if ( this == o ) { @@ -199,9 +213,17 @@ public static class Address implements Serializable { private String postalCode; - @OneToMany(mappedBy = "address", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany( + mappedBy = "address", + cascade = CascadeType.ALL, + orphanRemoval = true + ) private List owners = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-bidirectional-with-link-entity-example[] + public Address() { } @@ -231,6 +253,7 @@ public List getOwners() { return owners; } + //tag::associations-many-to-many-bidirectional-with-link-entity-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyUnidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyUnidirectionalTest.java index a8e8174f06c73e3027fb0cd36ea689a70daec3e2..99c5ba5c75f3464e3a017141abb3e6a0fdf544fe 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyUnidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToManyUnidirectionalTest.java @@ -100,12 +100,17 @@ public static class Person { @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) private List
addresses = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-unidirectional-example[] + public Person() { } public List
getAddresses() { return addresses; } + //tag::associations-many-to-many-unidirectional-example[] } @Entity(name = "Address") @@ -120,6 +125,10 @@ public static class Address { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::associations-many-to-many-unidirectional-example[] + public Address() { } @@ -139,6 +148,7 @@ public String getStreet() { public String getNumber() { return number; } + //tag::associations-many-to-many-unidirectional-example[] } //end::associations-many-to-many-unidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToOneTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToOneTest.java index eb99f055582ad65df69197aada385968e2d12c16..93c98facf337e33af8ba356fc7920234475d7f6c 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/ManyToOneTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/ManyToOneTest.java @@ -58,8 +58,8 @@ public static class Person { @GeneratedValue private Long id; - public Person() { - } + //Getters and setters are omitted for brevity + } @Entity(name = "Phone") @@ -78,6 +78,10 @@ public static class Phone { ) private Person person; + //Getters and setters are omitted for brevity + + //end::associations-many-to-one-example[] + public Phone() { } @@ -100,6 +104,7 @@ public Person getPerson() { public void setPerson(Person person) { this.person = person; } + //tag::associations-many-to-one-example[] } //end::associations-many-to-one-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java index 82b9442fceda2061e755960eb2860d62fb7f589f..f4487c9b6c69d372844cddfb2a8908e8c6411fa0 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java @@ -1,22 +1,37 @@ package org.hibernate.userguide.associations; import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import javax.persistence.ConstraintMode; import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; +import javax.persistence.ForeignKey; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; +import org.hibernate.Criteria; +import org.hibernate.Session; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; +import org.hibernate.criterion.ProjectionList; +import org.hibernate.criterion.Projections; +import org.hibernate.criterion.Restrictions; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.junit.After; +import org.junit.Before; import org.junit.Test; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.hibernate.testing.transaction.TransactionUtil2.inTransaction; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; /** @@ -24,6 +39,13 @@ */ public class NotFoundTest extends BaseEntityManagerFunctionalTestCase { + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); + } + @Override protected Class[] getAnnotatedClasses() { return new Class[] { @@ -32,75 +54,182 @@ protected Class[] getAnnotatedClasses() { }; } - @Test - public void test() { - doInJPA( this::entityManagerFactory, entityManager -> { + @Before + public void createTestData() { + inTransaction( entityManagerFactory(), (entityManager) -> { //tag::associations-not-found-persist-example[] - City _NewYork = new City(); - _NewYork.setName( "New York" ); - entityManager.persist( _NewYork ); - - Person person = new Person(); - person.setId( 1L ); - person.setName( "John Doe" ); - person.setCityName( "New York" ); + City newYork = new City( 1, "New York" ); + entityManager.persist( newYork ); + + Person person = new Person( 1, "John Doe", newYork ); entityManager.persist( person ); //end::associations-not-found-persist-example[] } ); + } - doInJPA( this::entityManagerFactory, entityManager -> { - //tag::associations-not-found-find-example[] - Person person = entityManager.find( Person.class, 1L ); - assertEquals( "New York", person.getCity().getName() ); - //end::associations-not-found-find-example[] + @After + public void dropTestData() { + inTransaction( entityManagerFactory(), (em) -> { + em.createQuery( "delete Person" ).executeUpdate(); + em.createQuery( "delete City" ).executeUpdate(); + } ); + } - //tag::associations-not-found-non-existing-persist-example[] - person.setCityName( "Atlantis" ); - //end::associations-not-found-non-existing-persist-example[] + @Test + public void test() { + doInJPA(this::entityManagerFactory, entityManager -> { + //tag::associations-not-found-find-baseline[] + Person person = entityManager.find(Person.class, 1); + assertEquals("New York", person.getCity().getName()); + //end::associations-not-found-find-baseline[] + }); - } ); + breakForeignKey(); - doInJPA( this::entityManagerFactory, entityManager -> { + doInJPA(this::entityManagerFactory, entityManager -> { //tag::associations-not-found-non-existing-find-example[] - Person person = entityManager.find( Person.class, 1L ); + Person person = entityManager.find(Person.class, 1); - assertEquals( "Atlantis", person.getCityName() ); - assertNull( null, person.getCity() ); + assertNull(null, person.getCity()); //end::associations-not-found-non-existing-find-example[] + }); + } + + private void breakForeignKey() { + inTransaction( entityManagerFactory(), (em) -> { + //tag::associations-not-found-break-fk[] + // the database allows this because there is no physical foreign-key + em.createQuery( "delete City" ).executeUpdate(); + //end::associations-not-found-break-fk[] + } ); + } + + @Test + public void queryTest() { + breakForeignKey(); + + inTransaction( entityManagerFactory(), (entityManager) -> { + //tag::associations-not-found-implicit-join-example[] + final List nullResults = entityManager + .createQuery( "from Person p where p.city.id is null", Person.class ) + .list(); + assertThat( nullResults.size(), is( 0 ) ); + + final List nonNullResults = entityManager + .createQuery( "from Person p where p.city.id is not null", Person.class ) + .list(); + assertThat( nonNullResults.size(), is( 0 ) ); + //end::associations-not-found-implicit-join-example[] + } ); + } + + @Test + public void queryTestFk() { + breakForeignKey(); + + inTransaction( entityManagerFactory(), (entityManager) -> { + sqlStatementInterceptor.clear(); + //tag::associations-not-found-fk-function-example[] + final List nullResults = entityManager + .createQuery( "select p.name from Person p where fk( p.city ) is null", String.class ) + .list(); + + assertThat( nullResults.size(), is( 0 ) ); + + final List nonNullResults = entityManager + .createQuery( "select p.name from Person p where fk( p.city ) is not null", String.class ) + .list(); + assertThat( nonNullResults.size(), is( 1 ) ); + assertThat( nonNullResults.get( 0 ), is( "John Doe" ) ); + //end::associations-not-found-fk-function-example[] + + // In addition, make sure that the two executed queries do not create a join + assertThat( sqlStatementInterceptor.getSqlQueries().size(), is(2) ); + assertFalse( sqlStatementInterceptor.getSqlQueries().get( 0 ).contains( " join " ) ); + assertFalse( sqlStatementInterceptor.getSqlQueries().get( 1 ).contains( " join " ) ); + } ); + } + + @Test + public void cirteriaTestFk() { + breakForeignKey(); + + inTransaction( entityManagerFactory(), (entityManager) -> { + sqlStatementInterceptor.clear(); + Session session = entityManager.unwrap( Session.class ); + //tag::associations-not-found-fk-criteria-example[] + Criteria criteria = session.createCriteria( Person.class ); + ProjectionList projList = Projections.projectionList(); + projList.add( Projections.property( "name" ) ); + criteria.setProjection( projList ); + criteria.add( Restrictions.fkIsNull( "city" ) ); + final List nullResults = criteria.list(); + + assertThat( nullResults.size(), is( 0 ) ); + + criteria = session.createCriteria( Person.class ); + projList = Projections.projectionList(); + projList.add( Projections.property( "name" ) ); + criteria.setProjection( projList ); + criteria.add( Restrictions.fkIsNotNull( "city" ) ); + final List nonNullResults = criteria.list(); + + assertThat( nonNullResults.size(), is( 1 ) ); + assertThat( nonNullResults.get( 0 ), is( "John Doe" ) ); + + // selecting Person -> city Foreign key + criteria = session.createCriteria( Person.class ); + projList = Projections.projectionList(); + projList.add( Projections.fk( "city" ) ); + criteria.setProjection( projList ); + criteria.add( Restrictions.fkIsNotNull( "city" ) ); + + final List foreigKeyResults = criteria.list(); + assertThat( foreigKeyResults.size(), is( 1 ) ); + assertThat( foreigKeyResults.get( 0 ), is( 1 ) ); + //end::associations-not-found-fk-criteria-example[] + + // In addition, make sure that the two executed queries do not create a join + assertThat( sqlStatementInterceptor.getSqlQueries().size(), is( 3 ) ); + assertFalse( sqlStatementInterceptor.getSqlQueries().get( 0 ).contains( " join " ) ); + assertFalse( sqlStatementInterceptor.getSqlQueries().get( 1 ).contains( " join " ) ); + assertFalse( sqlStatementInterceptor.getSqlQueries().get( 2 ).contains( " join " ) ); } ); } //tag::associations-not-found-domain-model-example[] - @Entity - @Table( name = "Person" ) + @Entity(name = "Person") + @Table(name = "Person") public static class Person { @Id - private Long id; - + private Integer id; private String name; - private String cityName; - - @ManyToOne( fetch = FetchType.LAZY ) - @NotFound ( action = NotFoundAction.IGNORE ) - @JoinColumn( - name = "cityName", - referencedColumnName = "name", - insertable = false, - updatable = false - ) + @ManyToOne + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn(name = "city_fk", referencedColumnName = "id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) private City city; //Getters and setters are omitted for brevity - //end::associations-not-found-domain-model-example[] + //end::associations-not-found-domain-model-example[] + - public Long getId() { + public Person() { + } + + public Person(Integer id, String name, City city) { + this.id = id; + this.name = name; + this.city = city; + } + + public Integer getId() { return id; } - public void setId(Long id) { + public void setId(Integer id) { this.id = id; } @@ -112,40 +241,39 @@ public void setName(String name) { this.name = name; } - public String getCityName() { - return cityName; - } - - public void setCityName(String cityName) { - this.cityName = cityName; - this.city = null; - } - public City getCity() { return city; } - //tag::associations-not-found-domain-model-example[] + //tag::associations-not-found-domain-model-example[] } - @Entity - @Table( name = "City" ) + @Entity(name = "City") + @Table(name = "City") public static class City implements Serializable { @Id - @GeneratedValue - private Long id; + private Integer id; private String name; //Getters and setters are omitted for brevity - //end::associations-not-found-domain-model-example[] + //end::associations-not-found-domain-model-example[] + - public Long getId() { + public City() { + } + + public City(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { return id; } - public void setId(Long id) { + public void setId(Integer id) { this.id = id; } @@ -156,7 +284,7 @@ public String getName() { public void setName(String name) { this.name = name; } - //tag::associations-not-found-domain-model-example[] + //tag::associations-not-found-domain-model-example[] } //end::associations-not-found-domain-model-example[] -} \ No newline at end of file +} diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyBidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyBidirectionalTest.java index 2a21d8191824641b4f8e9ad914c33748bbdc6ae6..2bd1ad81235027fc37812aec55df8e5a975273da 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyBidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyBidirectionalTest.java @@ -62,9 +62,14 @@ public static class Person { @Id @GeneratedValue private Long id; + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-one-to-many-bidirectional-example[] + public Person() { } @@ -76,6 +81,7 @@ public List getPhones() { return phones; } + //tag::associations-one-to-many-bidirectional-example[] public void addPhone(Phone phone) { phones.add( phone ); phone.setPerson( this ); @@ -101,6 +107,10 @@ public static class Phone { @ManyToOne private Person person; + //Getters and setters are omitted for brevity + + //end::associations-one-to-many-bidirectional-example[] + public Phone() { } @@ -124,6 +134,7 @@ public void setPerson(Person person) { this.person = person; } + //tag::associations-one-to-many-bidirectional-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyUnidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyUnidirectionalTest.java index 132f1dda9495c1c5be826425d16d6cd442243678..7e94064210080f6539c41f72e48ec81362ed9cf4 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyUnidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToManyUnidirectionalTest.java @@ -59,15 +59,22 @@ public static class Person { @Id @GeneratedValue private Long id; + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::associations-one-to-many-unidirectional-example[] + public Person() { } public List getPhones() { return phones; } + + //tag::associations-one-to-many-unidirectional-example[] } @Entity(name = "Phone") @@ -80,6 +87,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::associations-one-to-many-unidirectional-example[] + public Phone() { } @@ -94,6 +105,7 @@ public Long getId() { public String getNumber() { return number; } + //tag::associations-one-to-many-unidirectional-example[] } //end::associations-one-to-many-unidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalLazyTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalLazyTest.java index b6764982b871948c7db39fba3893c5bb99a21400..fcdd7722533ef70c9f73a49d2d0660355875dd2b 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalLazyTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalLazyTest.java @@ -64,15 +64,16 @@ public static class Phone { @LazyToOne( LazyToOneOption.NO_PROXY ) private PhoneDetails details; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-bidirectional-lazy-example[] + public Phone() { } public Phone(String number) { this.number = number; } - //Getters and setters are omitted for brevity - - //end::associations-one-to-one-bidirectional-lazy-example[] public Long getId() { return id; @@ -86,6 +87,7 @@ public PhoneDetails getDetails() { return details; } + //tag::associations-one-to-one-bidirectional-lazy-example[] public void addDetails(PhoneDetails details) { details.setPhone( this ); this.details = details; @@ -97,7 +99,6 @@ public void removeDetails() { this.details = null; } } - //tag::associations-one-to-one-bidirectional-lazy-example[] } @Entity(name = "PhoneDetails") @@ -115,6 +116,10 @@ public static class PhoneDetails { @JoinColumn(name = "phone_id") private Phone phone; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-bidirectional-lazy-example[] + public PhoneDetails() { } @@ -124,8 +129,6 @@ public PhoneDetails(String provider, String technology) { } //Getters and setters are omitted for brevity - //end::associations-one-to-one-bidirectional-lazy-example[] - public String getProvider() { return provider; } diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalTest.java index 542dcd0563e8a168d5b291b4df35232c554a95d2..bb6a4ba800be15376b044c0e90821597d2033f64 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneBidirectionalTest.java @@ -94,9 +94,18 @@ public static class Phone { @Column(name = "`number`") private String number; - @OneToOne(mappedBy = "phone", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + @OneToOne( + mappedBy = "phone", + cascade = CascadeType.ALL, + orphanRemoval = true, + fetch = FetchType.LAZY + ) private PhoneDetails details; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-bidirectional-example[] + public Phone() { } @@ -116,6 +125,7 @@ public PhoneDetails getDetails() { return details; } + //tag::associations-one-to-one-bidirectional-example[] public void addDetails(PhoneDetails details) { details.setPhone( this ); this.details = details; @@ -144,6 +154,10 @@ public static class PhoneDetails { @JoinColumn(name = "phone_id") private Phone phone; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-bidirectional-example[] + public PhoneDetails() { } @@ -171,6 +185,7 @@ public Phone getPhone() { public void setPhone(Phone phone) { this.phone = phone; } + //tag::associations-one-to-one-bidirectional-example[] } //end::associations-one-to-one-bidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneUnidirectionalTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneUnidirectionalTest.java index 11136ed7415ccf7e48f95752719bd3c91e09f032..b596d540dac73d1cf514c2ba801e24419549e6e2 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneUnidirectionalTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/OneToOneUnidirectionalTest.java @@ -59,6 +59,10 @@ public static class Phone { @JoinColumn(name = "details_id") private PhoneDetails details; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-unidirectional-example[] + public Phone() { } @@ -81,6 +85,7 @@ public PhoneDetails getDetails() { public void setDetails(PhoneDetails details) { this.details = details; } + //tag::associations-one-to-one-unidirectional-example[] } @Entity(name = "PhoneDetails") @@ -94,6 +99,10 @@ public static class PhoneDetails { private String technology; + //Getters and setters are omitted for brevity + + //end::associations-one-to-one-unidirectional-example[] + public PhoneDetails() { } @@ -113,6 +122,7 @@ public String getTechnology() { public void setTechnology(String technology) { this.technology = technology; } + //tag::associations-one-to-one-unidirectional-example[] } //end::associations-one-to-one-unidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/caching/NonStrictReadWriteCacheTest.java b/documentation/src/test/java/org/hibernate/userguide/caching/NonStrictReadWriteCacheTest.java index fb7e80c24d53a29a3f65d7f56a908cd5454ddf6d..91f339832676109f4c83307a7a9860d0c889dc30 100644 --- a/documentation/src/test/java/org/hibernate/userguide/caching/NonStrictReadWriteCacheTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/caching/NonStrictReadWriteCacheTest.java @@ -164,6 +164,10 @@ public static class Phone { @Version private int version; + //Getters and setters are omitted for brevity + + //end::caching-entity-mapping-example[] + public Phone() {} public Phone(String mobile) { @@ -185,6 +189,7 @@ public Person getPerson() { public void setPerson(Person person) { this.person = person; } + //tag::caching-entity-mapping-example[] } //end::caching-entity-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java b/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java index 19d450b86b03be19db2dda87cf63f198c2125610..4f12e691c2a650e8a56b72cf3bff1b1787558689 100644 --- a/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java @@ -267,7 +267,11 @@ public static class Person { @Column(name = "code", unique = true) private String code; - public Person() {} + //Getters and setters are omitted for brevity + + //end::caching-entity-natural-id-mapping-example[] + + public Person() {} public Person(String name) { this.name = name; @@ -292,6 +296,7 @@ public String getCode() { public void setCode(String code) { this.code = code; } + //tag::caching-entity-natural-id-mapping-example[] } //end::caching-entity-natural-id-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/ArrayTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/ArrayTest.java index 3ef6e3601b8661d23c4e2f80b211decdf547162a..fad6eb82ab0607e1f7946dcdd7c0813ca3f44566 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/ArrayTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/ArrayTest.java @@ -51,8 +51,13 @@ public static class Person { @Id private Long id; + private String[] phones; + //Getters and setters are omitted for brevity + + //end::collections-array-binary-example[] + public Person() { } @@ -67,6 +72,7 @@ public String[] getPhones() { public void setPhones(String[] phones) { this.phones = phones; } + //tag::collections-array-binary-example[] } //end::collections-array-binary-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeElementCollectionTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeElementCollectionTest.java index 777979396689a5e75e1ffa2443785b62229dc701..f432646cac10b02c2d6c4ee0efcefc5967dc36d3 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeElementCollectionTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BasicTypeElementCollectionTest.java @@ -90,9 +90,14 @@ public static class Person { @ElementCollection private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::collections-collection-proxy-entity-example[] + public List getPhones() { return phones; } + //tag::collections-collection-proxy-entity-example[] } //end::collections-collection-proxy-entity-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalBagTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalBagTest.java index fb537a348345ddec8078a6fed9336e059ce67607..6b407f7c4a7348fa46c3b5f1c864c8a9e7500f5a 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalBagTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalBagTest.java @@ -56,9 +56,14 @@ public static class Person { @Id private Long id; + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::collections-bidirectional-bag-example[] + public Person() { } @@ -70,6 +75,7 @@ public List getPhones() { return phones; } + //tag::collections-bidirectional-bag-example[] public void addPhone(Phone phone) { phones.add( phone ); phone.setPerson( this ); @@ -96,6 +102,10 @@ public static class Phone { @ManyToOne private Person person; + //Getters and setters are omitted for brevity + + //end::collections-bidirectional-bag-example[] + public Phone() { } @@ -125,6 +135,7 @@ public void setPerson(Person person) { this.person = person; } + //tag::collections-bidirectional-bag-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalMapTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalMapTest.java index a75e26e1e087ba400b44fde162c43c10f5a97232..a9739f5007cf5019e24726434c3b255e88ae5b4d 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalMapTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalMapTest.java @@ -74,6 +74,10 @@ public static class Person { @MapKeyEnumerated private Map phoneRegister = new HashMap<>(); + //Getters and setters are omitted for brevity + + //end::collections-map-bidirectional-example[] + public Person() { } @@ -85,6 +89,7 @@ public Map getPhoneRegister() { return phoneRegister; } + //tag::collections-map-bidirectional-example[] public void addPhone(Phone phone) { phone.setPerson( this ); phoneRegister.put( phone.getType(), phone ); @@ -108,6 +113,10 @@ public static class Phone { @ManyToOne private Person person; + //Getters and setters are omitted for brevity + + //end::collections-map-bidirectional-example[] + public Phone() { } @@ -136,6 +145,7 @@ public Person getPerson() { public void setPerson(Person person) { this.person = person; } + //tag::collections-map-bidirectional-example[] } //end::collections-map-bidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSetTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSetTest.java index 16e3df90a7bf2332e5c0f004dcb87d115ea34d02..171a3bd5f4853b93acdfa71aee8dfeff08b6a9b2 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSetTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/BidirectionalSetTest.java @@ -69,6 +69,10 @@ public static class Person { @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) private Set phones = new HashSet<>(); + //Getters and setters are omitted for brevity + + //end::collections-bidirectional-set-example[] + public Person() { } @@ -80,6 +84,7 @@ public Set getPhones() { return phones; } + //tag::collections-bidirectional-set-example[] public void addPhone(Phone phone) { phones.add( phone ); phone.setPerson( this ); @@ -106,6 +111,10 @@ public static class Phone { @ManyToOne private Person person; + //Getters and setters are omitted for brevity + + //end::collections-bidirectional-set-example[] + public Phone() { } @@ -135,6 +144,7 @@ public void setPerson(Person person) { this.person = person; } + //tag::collections-bidirectional-set-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/ElementCollectionMapTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/ElementCollectionMapTest.java index 52391b68ff1e6ea55b827347b05ebf277466320b..a1a4d272ca79f5e845f03f7f05b59e08d10b1ab9 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/ElementCollectionMapTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/ElementCollectionMapTest.java @@ -78,6 +78,10 @@ public static class Person { @Column(name = "since") private Map phoneRegister = new HashMap<>(); + //Getters and setters are omitted for brevity + + //end::collections-map-value-type-entity-key-example[] + public Person() {} public Person(Long id) { @@ -87,6 +91,7 @@ public Person(Long id) { public Map getPhoneRegister() { return phoneRegister; } + //tag::collections-map-value-type-entity-key-example[] } @Embeddable @@ -97,6 +102,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-map-value-type-entity-key-example[] + public Phone() { } @@ -112,6 +121,7 @@ public PhoneType getType() { public String getNumber() { return number; } + //tag::collections-map-value-type-entity-key-example[] } //end::collections-map-value-type-entity-key-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/EmbeddableTypeElementCollectionTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/EmbeddableTypeElementCollectionTest.java index 8d38a3ccf6b792352628ed02611ed38eada0ed5f..128fd2f53735a72b320bc1d9a751b26301e6b77c 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/EmbeddableTypeElementCollectionTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/EmbeddableTypeElementCollectionTest.java @@ -55,9 +55,14 @@ public static class Person { @ElementCollection private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::collections-embeddable-type-collection-lifecycle-entity-example[] + public List getPhones() { return phones; } + //tag::collections-embeddable-type-collection-lifecycle-entity-example[] } @Embeddable @@ -68,6 +73,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-embeddable-type-collection-lifecycle-entity-example[] + public Phone() { } @@ -83,6 +92,7 @@ public String getType() { public String getNumber() { return number; } + //tag::collections-embeddable-type-collection-lifecycle-entity-example[] } //end::collections-embeddable-type-collection-lifecycle-entity-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyTypeTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyTypeTest.java index deec47df3b991a24b627bc5bb7ef3d5f7725b53c..67c80a2e4b39cea755e6e8b8f8e1f7318e28a4ce 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyTypeTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyTypeTest.java @@ -147,6 +147,10 @@ public static class Person { @Column(name = "phone_number") private Map callRegister = new HashMap<>(); + //Getters and setters are omitted for brevity + + //end::collections-map-custom-key-type-mapping-example[] + public void setId(Long id) { this.id = id; } @@ -154,7 +158,7 @@ public void setId(Long id) { public Map getCallRegister() { return callRegister; } + //tag::collections-map-custom-key-type-mapping-example[] } - //end::collections-map-custom-key-type-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/OrderedBySQLTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/OrderedBySQLTest.java index 59d05cf6e788e8163cc0d8c076ca8673127c905d..cd641a82492ad3e5a2e4e2712771a30b6d1d8a6e 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/OrderedBySQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/OrderedBySQLTest.java @@ -80,8 +80,13 @@ public static class Person { private String name; - @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) - @org.hibernate.annotations.OrderBy(clause = "CHAR_LENGTH(name) DESC") + @OneToMany( + mappedBy = "person", + cascade = CascadeType.ALL + ) + @org.hibernate.annotations.OrderBy( + clause = "CHAR_LENGTH(name) DESC" + ) private List
articles = new ArrayList<>(); //Getters and setters are omitted for brevity @@ -128,6 +133,9 @@ public static class Article { @ManyToOne(fetch = FetchType.LAZY) private Person person; + //Getters and setters are omitted for brevity + //end::collections-customizing-ordered-by-sql-clause-mapping-example[] + private Article() { } @@ -136,9 +144,6 @@ public Article(String name, String content) { this.content = content; } - //Getters and setters are omitted for brevity - //end::collections-customizing-ordered-by-sql-clause-mapping-example[] - public Long getId() { return id; } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalBagTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalBagTest.java index 35cb180a3f6e5cffeff469af2ab911cfa7fd6107..4cea556997936575c8d991d6317d57a5c260da16 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalBagTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalBagTest.java @@ -55,9 +55,14 @@ public static class Person { @Id private Long id; + @OneToMany(cascade = CascadeType.ALL) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-bag-example[] + public Person() { } @@ -68,6 +73,7 @@ public Person(Long id) { public List getPhones() { return phones; } + //tag::collections-unidirectional-bag-example[] } @Entity(name = "Phone") @@ -81,6 +87,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-bag-example[] + public Phone() { } @@ -101,6 +111,7 @@ public String getType() { public String getNumber() { return number; } + //tag::collections-unidirectional-bag-example[] } //end::collections-unidirectional-bag-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalComparatorSortedSetTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalComparatorSortedSetTest.java index 104bcb0d9bc5c660441c5093a6a6868e6e67abbe..c91d76b7b432cc3d968f412f64747d66ddd4a5a4 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalComparatorSortedSetTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalComparatorSortedSetTest.java @@ -75,6 +75,10 @@ public static class Person { @SortComparator(ReverseComparator.class) private SortedSet phones = new TreeSet<>(); + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-sorted-set-custom-comparator-example[] + public Person() { } @@ -85,9 +89,11 @@ public Person(Long id) { public Set getPhones() { return phones; } + //tag::collections-unidirectional-sorted-set-custom-comparator-example[] } public static class ReverseComparator implements Comparator { + @Override public int compare(Phone o1, Phone o2) { return o2.compareTo( o1 ); @@ -106,6 +112,10 @@ public static class Phone implements Comparable { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-sorted-set-custom-comparator-example[] + public Phone() { } @@ -127,6 +137,7 @@ public String getNumber() { return number; } + //tag::collections-unidirectional-sorted-set-custom-comparator-example[] @Override public int compareTo(Phone o) { return number.compareTo( o.getNumber() ); diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalMapTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalMapTest.java index 549097850625c13902f1d8eb2eeb2997ba64b70a..26bd94c90a7ec8d167e3a0814f9c73c1230113e2 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalMapTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalMapTest.java @@ -77,13 +77,17 @@ public static class Person { @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinTable( - name = "phone_register", - joinColumns = @JoinColumn(name = "phone_id"), - inverseJoinColumns = @JoinColumn(name = "person_id")) + name = "phone_register", + joinColumns = @JoinColumn(name = "phone_id"), + inverseJoinColumns = @JoinColumn(name = "person_id")) @MapKey(name = "since") @MapKeyTemporal(TemporalType.TIMESTAMP) private Map phoneRegister = new HashMap<>(); + //Getters and setters are omitted for brevity + + //end::collections-map-unidirectional-example[] + public Person() { } @@ -95,6 +99,7 @@ public Map getPhoneRegister() { return phoneRegister; } + //tag::collections-map-unidirectional-example[] public void addPhone(Phone phone) { phoneRegister.put( phone.getSince(), phone ); } @@ -114,6 +119,10 @@ public static class Phone { private Date since; + //Getters and setters are omitted for brevity + + //end::collections-map-unidirectional-example[] + public Phone() { } @@ -134,6 +143,7 @@ public String getNumber() { public Date getSince() { return since; } + //tag::collections-map-unidirectional-example[] } //end::collections-map-unidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalOrderedByListTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalOrderedByListTest.java index f986287e190fdfd1410c9428bd84c10784b317f2..0aaf90a899f07e4df5d9d197a413a1644028a57e 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalOrderedByListTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalOrderedByListTest.java @@ -54,10 +54,15 @@ public static class Person { @Id private Long id; + @OneToMany(cascade = CascadeType.ALL) @OrderBy("number") private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-ordered-list-order-by-example[] + public Person() { } @@ -68,6 +73,7 @@ public Person(Long id) { public List getPhones() { return phones; } + //tag::collections-unidirectional-ordered-list-order-by-example[] } @Entity(name = "Phone") @@ -81,6 +87,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-ordered-list-order-by-example[] + public Phone() { } @@ -101,6 +111,7 @@ public String getType() { public String getNumber() { return number; } + //tag::collections-unidirectional-ordered-list-order-by-example[] } //end::collections-unidirectional-ordered-list-order-by-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSetTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSetTest.java index e531e713feaaaa85c5b9a80dbb0830e596ac4993..2f16438f95d770987c33eb514904b6c28bdc0ce8 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSetTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSetTest.java @@ -64,9 +64,13 @@ public static class Person { @Id private Long id; + @OneToMany(cascade = CascadeType.ALL) private Set phones = new HashSet<>(); + //Getters and setters are omitted for brevity + //end::collections-unidirectional-set-example[] + public Person() { } @@ -77,6 +81,7 @@ public Person(Long id) { public Set getPhones() { return phones; } + //tag::collections-unidirectional-set-example[] } @Entity(name = "Phone") @@ -91,6 +96,10 @@ public static class Phone { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-set-example[] + public Phone() { } @@ -112,6 +121,7 @@ public String getNumber() { return number; } + //tag::collections-unidirectional-set-example[] @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSortedSetTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSortedSetTest.java index e9a19768b1dd7a2a0b24278dc2cb8f344a127744..f330b31d0d2be954ea69a9ff92c62f8224799fa4 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSortedSetTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/UnidirectionalSortedSetTest.java @@ -69,10 +69,15 @@ public static class Person { @Id private Long id; + @OneToMany(cascade = CascadeType.ALL) @SortNatural private SortedSet phones = new TreeSet<>(); + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-sorted-set-natural-comparator-example[] + public Person() { } @@ -83,6 +88,7 @@ public Person(Long id) { public Set getPhones() { return phones; } + //tag::collections-unidirectional-sorted-set-natural-comparator-example[] } @Entity(name = "Phone") @@ -97,6 +103,10 @@ public static class Phone implements Comparable { @Column(name = "`number`") private String number; + //Getters and setters are omitted for brevity + + //end::collections-unidirectional-sorted-set-natural-comparator-example[] + public Phone() { } @@ -118,6 +128,7 @@ public String getNumber() { return number; } + //tag::collections-unidirectional-sorted-set-natural-comparator-example[] @Override public int compareTo(Phone o) { return number.compareTo( o.getNumber() ); diff --git a/documentation/src/test/java/org/hibernate/userguide/events/BaseEntity.java b/documentation/src/test/java/org/hibernate/userguide/events/BaseEntity.java index f8e5f4d6da7328d7c232ea78d2f075217d2566de..213fb02685626d2fe04aba88c7dd35728bce36fc 100644 --- a/documentation/src/test/java/org/hibernate/userguide/events/BaseEntity.java +++ b/documentation/src/test/java/org/hibernate/userguide/events/BaseEntity.java @@ -14,6 +14,10 @@ public abstract class BaseEntity { private Timestamp updatedOn; + //Getters and setters are omitted for brevity + +//end::events-default-listener-mapping-example[] + public Timestamp getCreatedOn() { return createdOn; } @@ -29,6 +33,7 @@ public Timestamp getUpdatedOn() { void setUpdatedOn(Timestamp updatedOn) { this.updatedOn = updatedOn; } +//tag::events-default-listener-mapping-example[] } //end::events-default-listener-mapping-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java b/documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java index 14a823708dd8dbc21d3ba0b0eafc2ebf7e4c89cf..e464f8150ee3e8ece3b22c035beff60054511a15 100644 --- a/documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java @@ -79,7 +79,6 @@ public static class Department { //Getters and setters omitted for brevity } - //tag::fetching-direct-vs-query-domain-model-example[] @Entity(name = "Employee") public static class Employee { diff --git a/documentation/src/test/java/org/hibernate/userguide/flush/AutoFlushTest.java b/documentation/src/test/java/org/hibernate/userguide/flush/AutoFlushTest.java index 047976dad0a29c61173913de953a69deafface56..1e7dd08a19706258a047cd4bb3e777b7d7d34dca 100644 --- a/documentation/src/test/java/org/hibernate/userguide/flush/AutoFlushTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/flush/AutoFlushTest.java @@ -174,6 +174,10 @@ public static class Person { private String name; + //Getters and setters are omitted for brevity + + //end::flushing-auto-flush-jpql-entity-example[] + public Person() {} public Person(String name) { @@ -187,7 +191,7 @@ public Long getId() { public String getName() { return name; } - + //tag::flushing-auto-flush-jpql-entity-example[] } @Entity(name = "Advertisement") @@ -199,6 +203,10 @@ public static class Advertisement { private String title; + //Getters and setters are omitted for brevity + + //end::flushing-auto-flush-jpql-entity-example[] + public Long getId() { return id; } @@ -214,6 +222,7 @@ public String getTitle() { public void setTitle(String title) { this.title = title; } + //tag::flushing-auto-flush-jpql-entity-example[] } //end::flushing-auto-flush-jpql-entity-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/hql/CallStatistics.java b/documentation/src/test/java/org/hibernate/userguide/hql/CallStatistics.java index 757693c023d394d1266c314749734de3b53be62d..34c1ff4a6680ad17c234e752babab87d18f3895f 100644 --- a/documentation/src/test/java/org/hibernate/userguide/hql/CallStatistics.java +++ b/documentation/src/test/java/org/hibernate/userguide/hql/CallStatistics.java @@ -16,14 +16,14 @@ public class CallStatistics { private final long total; private final int min; private final int max; - private final double abg; + private final double avg; - public CallStatistics(long count, long total, int min, int max, double abg) { + public CallStatistics(long count, long total, int min, int max, double avg) { this.count = count; this.total = total; this.min = min; this.max = max; - this.abg = abg; + this.avg = avg; } //Getters and setters omitted for brevity diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/DiscriminatorNotNullSingleTableTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/DiscriminatorNotNullSingleTableTest.java index e3dea013e31ee38780c9c3779ccbd256205a4153..2c3bbf9ef3c5e73af04875824af4fd6b232939a0 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/DiscriminatorNotNullSingleTableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/DiscriminatorNotNullSingleTableTest.java @@ -120,6 +120,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-value-example[] + public Long getId() { return id; } @@ -151,6 +155,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-single-table-discriminator-value-example[] } @Entity(name = "DebitAccount") @@ -159,6 +164,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-value-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -166,6 +175,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-single-table-discriminator-value-example[] } @Entity(name = "CreditAccount") @@ -174,6 +184,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-value-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -181,6 +195,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-single-table-discriminator-value-example[] } @Entity(name = "OtherAccount") @@ -189,6 +204,10 @@ public static class OtherAccount extends Account { private boolean active; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-value-example[] + public boolean isActive() { return active; } @@ -196,6 +215,7 @@ public boolean isActive() { public void setActive(boolean active) { this.active = active; } + //tag::entity-inheritance-single-table-discriminator-value-example[] } //end::entity-inheritance-single-table-discriminator-value-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTablePrimaryKeyJoinColumnTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTablePrimaryKeyJoinColumnTest.java index d4fc162da793161d73858a633d492baa4ad5a372..bfe845bd7593e67e35c25265064da09ec083448c 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTablePrimaryKeyJoinColumnTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTablePrimaryKeyJoinColumnTest.java @@ -74,6 +74,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-primary-key-join-column-example[] + public Long getId() { return id; } @@ -105,6 +109,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-joined-table-primary-key-join-column-example[] } @Entity(name = "DebitAccount") @@ -113,6 +118,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-primary-key-join-column-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -120,6 +129,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-joined-table-primary-key-join-column-example[] } @Entity(name = "CreditAccount") @@ -128,6 +138,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-primary-key-join-column-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -135,6 +149,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-joined-table-primary-key-join-column-example[] } //end::entity-inheritance-joined-table-primary-key-join-column-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTableTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTableTest.java index 889ec4df5ba4c713f34743c8cec5d6ddabc72135..9442c377f72d67cf4df4590aca308a69864491ec 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/JoinTableTest.java @@ -76,6 +76,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-example[] + public Long getId() { return id; } @@ -107,6 +111,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-joined-table-example[] } @Entity(name = "DebitAccount") @@ -114,6 +119,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -121,6 +130,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-joined-table-example[] } @Entity(name = "CreditAccount") @@ -128,6 +138,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-joined-table-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -135,6 +149,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-joined-table-example[] } //end::entity-inheritance-joined-table-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/MappedSuperclassTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/MappedSuperclassTest.java index 4323f77913d689818701d969c203644617dd8d92..1ddbf7f551c82add5ec1f76ca9ea5c54d9cab1f2 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/MappedSuperclassTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/MappedSuperclassTest.java @@ -65,6 +65,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-mapped-superclass-example[] + public Long getId() { return id; } @@ -96,6 +100,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-mapped-superclass-example[] } @Entity(name = "DebitAccount") @@ -103,6 +108,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-mapped-superclass-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -110,6 +119,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-mapped-superclass-example[] } @Entity(name = "CreditAccount") @@ -117,6 +127,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-mapped-superclass-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -124,6 +138,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-mapped-superclass-example[] } //end::entity-inheritance-mapped-superclass-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java index 1cd3d890c869c7eb19c801f23c0e8d9957b2d3e1..ec59c3bc2da42ee0d25c1e9cbf937c381e736fed 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java @@ -90,6 +90,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-formula-example[] + public Long getId() { return id; } @@ -121,6 +125,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-single-table-discriminator-formula-example[] } @Entity(name = "DebitAccount") @@ -131,6 +136,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-formula-example[] + private DebitAccount() { } @@ -149,6 +158,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-single-table-discriminator-formula-example[] } @Entity(name = "CreditAccount") @@ -159,6 +169,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-discriminator-formula-example[] + private CreditAccount() { } @@ -177,6 +191,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-single-table-discriminator-formula-example[] } //end::entity-inheritance-single-table-discriminator-formula-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableTest.java index 1a225b6791ebcc8fb62500b4933e8f47984ba06f..fc0316edfc36ce41b1070738cf5ea32534330fe4 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableTest.java @@ -78,6 +78,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-example[] + public Long getId() { return id; } @@ -109,6 +113,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-single-table-example[] } @Entity(name = "DebitAccount") @@ -116,6 +121,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -123,6 +132,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-single-table-example[] } @Entity(name = "CreditAccount") @@ -130,6 +140,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-single-table-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -137,6 +151,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-single-table-example[] } //end::entity-inheritance-single-table-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/TablePerClassTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/TablePerClassTest.java index d4994028d78d7080243bba06e8abb58db2ca248c..2db426456cc86f3fa7bc20699e34372927c93b81 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/TablePerClassTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/TablePerClassTest.java @@ -76,6 +76,10 @@ public static class Account { private BigDecimal interestRate; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-table-per-class-example[] + public Long getId() { return id; } @@ -107,6 +111,7 @@ public BigDecimal getInterestRate() { public void setInterestRate(BigDecimal interestRate) { this.interestRate = interestRate; } + //tag::entity-inheritance-table-per-class-example[] } @Entity(name = "DebitAccount") @@ -114,6 +119,10 @@ public static class DebitAccount extends Account { private BigDecimal overdraftFee; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-table-per-class-example[] + public BigDecimal getOverdraftFee() { return overdraftFee; } @@ -121,6 +130,7 @@ public BigDecimal getOverdraftFee() { public void setOverdraftFee(BigDecimal overdraftFee) { this.overdraftFee = overdraftFee; } + //tag::entity-inheritance-table-per-class-example[] } @Entity(name = "CreditAccount") @@ -128,6 +138,10 @@ public static class CreditAccount extends Account { private BigDecimal creditLimit; + //Getters and setters are omitted for brevity + + //end::entity-inheritance-table-per-class-example[] + public BigDecimal getCreditLimit() { return creditLimit; } @@ -135,6 +149,7 @@ public BigDecimal getCreditLimit() { public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + //tag::entity-inheritance-table-per-class-example[] } //end::entity-inheritance-table-per-class-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeDefTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeDefTest.java index b0492aafa44c86dd1cb43d92093a539a6dea443e..43eeb43f084c47ca59a58bc1aabbc816429d00f6 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeDefTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeDefTest.java @@ -66,6 +66,9 @@ public static class Product { private BitSet bitSet; + //Getters and setters are omitted for brevity + //end::basic-custom-type-BitSetTypeDef-mapping-example[] + public Integer getId() { return id; } @@ -81,6 +84,7 @@ public BitSet getBitSet() { public void setBitSet(BitSet bitSet) { this.bitSet = bitSet; } + //tag::basic-custom-type-BitSetTypeDef-mapping-example[] } //end::basic-custom-type-BitSetTypeDef-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeTest.java index 9d76689f7c7ab15a11c659cfc97f2255acfb27c5..d7e42646335130a40c8edf20d627643bed464c07 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetTypeTest.java @@ -76,6 +76,9 @@ public Integer getId() { return id; } + //Getters and setters are omitted for brevity + //end::basic-custom-type-BitSetType-mapping-example[] + public void setId(Integer id) { this.id = id; } @@ -87,6 +90,7 @@ public BitSet getBitSet() { public void setBitSet(BitSet bitSet) { this.bitSet = bitSet; } + //tag::basic-custom-type-BitSetType-mapping-example[] } //end::basic-custom-type-BitSetType-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java index 2452c6bb92e7543d2758ab5ed0ba0b40f102a614..a0c7fd7293fe1d93f83d31271b9a6afa56892523 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java @@ -119,6 +119,8 @@ public static class Product { @Type( type = "bitset" ) private BitSet bitSet; + + //Constructors, getters, and setters are omitted for brevity //end::basic-custom-type-BitSetUserType-mapping-example[] public Product() { } @@ -127,7 +129,6 @@ public Product(Number id, BitSet bitSet) { this.id = id.intValue(); this.bitSet = bitSet; } - //tag::basic-custom-type-BitSetUserType-mapping-example[] public Integer getId() { return id; @@ -144,6 +145,7 @@ public BitSet getBitSet() { public void setBitSet(BitSet bitSet) { this.bitSet = bitSet; } + //tag::basic-custom-type-BitSetUserType-mapping-example[] } //end::basic-custom-type-BitSetUserType-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterJoinTableTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterJoinTableTest.java index bafed596f03c6a161d25651e9e4a5600938e3a04..83c3466bcfe760fccacba4d6561f4766510bd386 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterJoinTableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterJoinTableTest.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; @@ -35,180 +36,190 @@ */ public class FilterJoinTableTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Client.class, - Account.class - }; - } - - @Test - public void testLifecycle() { - //tag::mapping-filter-join-table-persistence-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - - Client client = new Client(); - client.setId( 1L ); - client.setName( "John Doe" ); - entityManager.persist( client ); - - Account account1 = new Account( ); - account1.setId( 1L ); - account1.setType( AccountType.CREDIT ); - account1.setAmount( 5000d ); - account1.setRate( 1.25 / 100 ); - account1.setActive( true ); - client.getAccounts().add( account1 ); - entityManager.persist( account1 ); - - Account account2 = new Account( ); - account2.setId( 2L ); - account2.setType( AccountType.DEBIT ); - account2.setAmount( 0d ); - account2.setRate( 1.05 / 100 ); - account2.setActive( false ); - client.getAccounts().add( account2 ); - entityManager.persist( account2 ); - - Account account3 = new Account( ); - account3.setType( AccountType.DEBIT ); - account3.setId( 3L ); - account3.setAmount( 250d ); - account3.setRate( 1.05 / 100 ); - account3.setActive( true ); - client.getAccounts().add( account3 ); - entityManager.persist( account3 ); - } ); - //end::mapping-filter-join-table-persistence-example[] - - //tag::mapping-filter-join-table-collection-query-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - Client client = entityManager.find( Client.class, 1L ); - assertEquals( 3, client.getAccounts().size()); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "firstAccounts"); - - Client client = entityManager.find( Client.class, 1L ); - - entityManager - .unwrap( Session.class ) - .enableFilter( "firstAccounts" ) - .setParameter( "maxOrderId", 1); - - assertEquals( 2, client.getAccounts().size()); - } ); - //end::mapping-filter-join-table-collection-query-example[] - } - - //tag::mapping-filter-join-table-example[] - public enum AccountType { - DEBIT, - CREDIT - } - - @Entity(name = "Client") - @FilterDef(name="firstAccounts", parameters=@ParamDef( name="maxOrderId", type="int" ) ) - @Filter(name="firstAccounts", condition="order_id <= :maxOrderId") - public static class Client { - - @Id - private Long id; - - private String name; - - @OneToMany - @OrderColumn(name = "order_id") - @FilterJoinTable(name="firstAccounts", condition="order_id <= :maxOrderId") - private List accounts = new ArrayList<>( ); - - //Getters and setters omitted for brevity - - //end::mapping-filter-join-table-example[] - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List getAccounts() { - return accounts; - } - //tag::mapping-filter-join-table-example[] - } - - @Entity(name = "Account") - public static class Account { - - @Id - private Long id; - - @Column(name = "account_type") - @Enumerated(EnumType.STRING) - private AccountType type; - - private Double amount; - - private Double rate; - - private boolean active; - - //Getters and setters omitted for brevity - - //end::mapping-filter-join-table-example[] - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public AccountType getType() { - return type; - } - - public void setType(AccountType type) { - this.type = type; - } - - public Double getAmount() { - return amount; - } - - public void setAmount(Double amount) { - this.amount = amount; - } - - public Double getRate() { - return rate; - } - - public void setRate(Double rate) { - this.rate = rate; - } - - public boolean isActive() { - return active; - } - - public void setActive(boolean active) { - this.active = active; - } - - //tag::mapping-filter-join-table-example[] - } - //end::mapping-filter-join-table-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Client.class, + Account.class + }; + } + + @Test + public void testLifecycle() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::mapping-filter-join-table-persistence-example[] + Client client = new Client() + .setId( 1L ) + .setName( "John Doe" ); + + client.addAccount( + new Account() + .setId( 1L ) + .setType( AccountType.CREDIT ) + .setAmount( 5000d ) + .setRate( 1.25 / 100 ) + ); + + client.addAccount( + new Account() + .setId( 2L ) + .setType( AccountType.DEBIT ) + .setAmount( 0d ) + .setRate( 1.05 / 100 ) + ); + + client.addAccount( + new Account() + .setType( AccountType.DEBIT ) + .setId( 3L ) + .setAmount( 250d ) + .setRate( 1.05 / 100 ) + ); + + entityManager.persist( client ); + //end::mapping-filter-join-table-persistence-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::mapping-no-filter-join-table-collection-query-example[] + Client client = entityManager.find( Client.class, 1L ); + + assertEquals( 3, client.getAccounts().size()); + //end::mapping-no-filter-join-table-collection-query-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "firstAccounts"); + + //tag::mapping-filter-join-table-collection-query-example[] + Client client = entityManager.find( Client.class, 1L ); + + entityManager + .unwrap( Session.class ) + .enableFilter( "firstAccounts" ) + .setParameter( "maxOrderId", 1); + + assertEquals( 2, client.getAccounts().size()); + //end::mapping-filter-join-table-collection-query-example[] + } ); + } + + public enum AccountType { + DEBIT, + CREDIT + } + + //tag::mapping-filter-join-table-example[] + @Entity(name = "Client") + @FilterDef( + name="firstAccounts", + parameters=@ParamDef( + name="maxOrderId", + type="int" + ) + ) + @Filter( + name="firstAccounts", + condition="order_id <= :maxOrderId" + ) + public static class Client { + + @Id + private Long id; + + private String name; + + @OneToMany(cascade = CascadeType.ALL) + @OrderColumn(name = "order_id") + @FilterJoinTable( + name="firstAccounts", + condition="order_id <= :maxOrderId" + ) + private List accounts = new ArrayList<>( ); + + //Getters and setters omitted for brevity + //end::mapping-filter-join-table-example[] + public Long getId() { + return id; + } + + public Client setId(Long id) { + this.id = id; + return this; + } + + public String getName() { + return name; + } + + public Client setName(String name) { + this.name = name; + return this; + } + + public List getAccounts() { + return accounts; + } + //tag::mapping-filter-join-table-example[] + + public void addAccount(Account account) { + this.accounts.add( account ); + } + } + + @Entity(name = "Account") + public static class Account { + + @Id + private Long id; + + @Column(name = "account_type") + @Enumerated(EnumType.STRING) + private AccountType type; + + private Double amount; + + private Double rate; + + //Getters and setters omitted for brevity + //end::mapping-filter-join-table-example[] + public Long getId() { + return id; + } + + public Account setId(Long id) { + this.id = id; + return this; + } + + public AccountType getType() { + return type; + } + + public Account setType(AccountType type) { + this.type = type; + return this; + } + + public Double getAmount() { + return amount; + } + + public Account setAmount(Double amount) { + this.amount = amount; + return this; + } + + public Double getRate() { + return rate; + } + + public Account setRate(Double rate) { + this.rate = rate; + return this; + } + + //tag::mapping-filter-join-table-example[] + } + //end::mapping-filter-join-table-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java index a44b6a6b1fd8a773041b36d842c3367d0eade5ab..f678989df3e956262bb34187e7460388adccd0d5 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java @@ -8,10 +8,12 @@ import java.util.ArrayList; import java.util.List; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; +import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.NoResultException; @@ -39,267 +41,301 @@ */ public class FilterTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Client.class, - Account.class - }; - } - - @Test - public void testLifecycle() { - //tag::mapping-filter-persistence-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - - Client client = new Client(); - client.setId( 1L ); - client.setName( "John Doe" ); - entityManager.persist( client ); - - Account account1 = new Account( ); - account1.setId( 1L ); - account1.setType( AccountType.CREDIT ); - account1.setAmount( 5000d ); - account1.setRate( 1.25 / 100 ); - account1.setActive( true ); - account1.setClient( client ); - client.getAccounts().add( account1 ); - entityManager.persist( account1 ); - - Account account2 = new Account( ); - account2.setId( 2L ); - account2.setType( AccountType.DEBIT ); - account2.setAmount( 0d ); - account2.setRate( 1.05 / 100 ); - account2.setActive( false ); - account2.setClient( client ); - client.getAccounts().add( account2 ); - entityManager.persist( account2 ); - - Account account3 = new Account( ); - account3.setType( AccountType.DEBIT ); - account3.setId( 3L ); - account3.setAmount( 250d ); - account3.setRate( 1.05 / 100 ); - account3.setActive( true ); - account3.setClient( client ); - client.getAccounts().add( account3 ); - entityManager.persist( account3 ); - } ); - //end::mapping-filter-persistence-example[] - - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "activeAccount"); - - entityManager - .unwrap( Session.class ) - .enableFilter( "activeAccount" ) - .setParameter( "active", true); - - Account account1 = entityManager.find( Account.class, 1L ); - Account account2 = entityManager.find( Account.class, 2L ); - - assertNotNull( account1 ); - assertNotNull( account2 ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "activeAccount"); - - entityManager - .unwrap( Session.class ) - .enableFilter( "activeAccount" ) - .setParameter( "active", true); - - Account account1 = entityManager.createQuery( - "select a from Account a where a.id = :id", Account.class) - .setParameter( "id", 1L ) - .getSingleResult(); - assertNotNull( account1 ); - try { - Account account2 = entityManager.createQuery( - "select a from Account a where a.id = :id", Account.class) - .setParameter( "id", 2L ) - .getSingleResult(); - } - catch (NoResultException expected) { - } - } ); - - //tag::mapping-filter-entity-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "activeAccount"); - - entityManager - .unwrap( Session.class ) - .enableFilter( "activeAccount" ) - .setParameter( "active", true); - - Account account = entityManager.find( Account.class, 2L ); - assertFalse( account.isActive() ); - } ); - //end::mapping-filter-entity-example[] - - // tag::mapping-filter-entity-query-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - List accounts = entityManager.createQuery( - "select a from Account a", Account.class) - .getResultList(); - assertEquals( 3, accounts.size()); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "activeAccount"); - - entityManager - .unwrap( Session.class ) - .enableFilter( "activeAccount" ) - .setParameter( "active", true); - - List accounts = entityManager.createQuery( - "select a from Account a", Account.class) - .getResultList(); - assertEquals( 2, accounts.size()); - } ); - //end::mapping-filter-entity-query-example[] - - //tag::mapping-filter-collection-query-example[] - doInJPA( this::entityManagerFactory, entityManager -> { - Client client = entityManager.find( Client.class, 1L ); - assertEquals( 3, client.getAccounts().size() ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - log.infof( "Activate filter [%s]", "activeAccount"); - - entityManager - .unwrap( Session.class ) - .enableFilter( "activeAccount" ) - .setParameter( "active", true); - - Client client = entityManager.find( Client.class, 1L ); - assertEquals( 2, client.getAccounts().size() ); - } ); - //end::mapping-filter-collection-query-example[] - } - - //tag::mapping-filter-example[] - public enum AccountType { - DEBIT, - CREDIT - } - - @Entity(name = "Client") - public static class Client { - - @Id - private Long id; - - private String name; - - @OneToMany(mappedBy = "client") - @Filter(name="activeAccount", condition="active = :active") - private List accounts = new ArrayList<>( ); - - //Getters and setters omitted for brevity - - //end::mapping-filter-example[] - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List getAccounts() { - return accounts; - } - //tag::mapping-filter-example[] - } - - @Entity(name = "Account") - @FilterDef(name="activeAccount", parameters=@ParamDef( name="active", type="boolean" ) ) - @Filter(name="activeAccount", condition="active = :active") - public static class Account { - - @Id - private Long id; - - @ManyToOne - private Client client; - - @Column(name = "account_type") - @Enumerated(EnumType.STRING) - private AccountType type; - - private Double amount; - - private Double rate; - - private boolean active; - - //Getters and setters omitted for brevity - - //end::mapping-filter-example[] - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Client getClient() { - return client; - } - - public void setClient(Client client) { - this.client = client; - } - - public AccountType getType() { - return type; - } - - public void setType(AccountType type) { - this.type = type; - } - - public Double getAmount() { - return amount; - } - - public void setAmount(Double amount) { - this.amount = amount; - } - - public Double getRate() { - return rate; - } - - public void setRate(Double rate) { - this.rate = rate; - } - - public boolean isActive() { - return active; - } - - public void setActive(boolean active) { - this.active = active; - } - - //tag::mapping-filter-example[] - } - //end::mapping-filter-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Client.class, + Account.class + }; + } + + @Test + public void testLifecycle() { + doInJPA( this::entityManagerFactory, entityManager -> { + + //tag::mapping-filter-persistence-example[] + Client client = new Client() + .setId( 1L ) + .setName( "John Doe" ); + + client.addAccount( + new Account() + .setId( 1L ) + .setType( AccountType.CREDIT ) + .setAmount( 5000d ) + .setRate( 1.25 / 100 ) + .setActive( true ) + ); + + client.addAccount( + new Account() + .setId( 2L ) + .setType( AccountType.DEBIT ) + .setAmount( 0d ) + .setRate( 1.05 / 100 ) + .setActive( false ) + ); + + client.addAccount( + new Account() + .setType( AccountType.DEBIT ) + .setId( 3L ) + .setAmount( 250d ) + .setRate( 1.05 / 100 ) + .setActive( true ) + ); + + entityManager.persist( client ); + //end::mapping-filter-persistence-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + Account account1 = entityManager.find( Account.class, 1L ); + Account account2 = entityManager.find( Account.class, 2L ); + + assertNotNull( account1 ); + assertNotNull( account2 ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + Account account1 = entityManager.createQuery( + "select a from Account a where a.id = :id", Account.class) + .setParameter( "id", 1L ) + .getSingleResult(); + assertNotNull( account1 ); + try { + Account account2 = entityManager.createQuery( + "select a from Account a where a.id = :id", Account.class) + .setParameter( "id", 2L ) + .getSingleResult(); + } + catch (NoResultException expected) { + } + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + //tag::mapping-filter-entity-example[] + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + Account account = entityManager.find( Account.class, 2L ); + + assertFalse( account.isActive() ); + //end::mapping-filter-entity-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::mapping-no-filter-entity-query-example[] + List accounts = entityManager.createQuery( + "select a from Account a", Account.class) + .getResultList(); + + assertEquals( 3, accounts.size()); + //end::mapping-no-filter-entity-query-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + //tag::mapping-filter-entity-query-example[] + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + List accounts = entityManager.createQuery( + "select a from Account a", Account.class) + .getResultList(); + + assertEquals( 2, accounts.size()); + //end::mapping-filter-entity-query-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::mapping-no-filter-collection-query-example[] + Client client = entityManager.find( Client.class, 1L ); + + assertEquals( 3, client.getAccounts().size() ); + //end::mapping-no-filter-collection-query-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + + //tag::mapping-filter-collection-query-example[] + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + Client client = entityManager.find( Client.class, 1L ); + + assertEquals( 2, client.getAccounts().size() ); + //end::mapping-filter-collection-query-example[] + } ); + } + + public enum AccountType { + DEBIT, + CREDIT + } + + //tag::mapping-filter-Client-example[] + @Entity(name = "Client") + public static class Client { + + @Id + private Long id; + + private String name; + + @OneToMany( + mappedBy = "client", + cascade = CascadeType.ALL + ) + @Filter( + name="activeAccount", + condition="active_status = :active" + ) + private List accounts = new ArrayList<>( ); + + //Getters and setters omitted for brevity + //end::mapping-filter-Client-example[] + public Long getId() { + return id; + } + + public Client setId(Long id) { + this.id = id; + return this; + } + + public String getName() { + return name; + } + + public Client setName(String name) { + this.name = name; + return this; + } + + public List getAccounts() { + return accounts; + } + //tag::mapping-filter-Client-example[] + + public void addAccount(Account account) { + account.setClient( this ); + this.accounts.add( account ); + } + } + //end::mapping-filter-Client-example[] + + //tag::mapping-filter-Account-example[] + @Entity(name = "Account") + @FilterDef( + name="activeAccount", + parameters = @ParamDef( + name="active", + type="boolean" + ) + ) + @Filter( + name="activeAccount", + condition="active_status = :active" + ) + public static class Account { + + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Client client; + + @Column(name = "account_type") + @Enumerated(EnumType.STRING) + private AccountType type; + + private Double amount; + + private Double rate; + + @Column(name = "active_status") + private boolean active; + + //Getters and setters omitted for brevity + //end::mapping-filter-Account-example[] + public Long getId() { + return id; + } + + public Account setId(Long id) { + this.id = id; + return this; + } + + public Client getClient() { + return client; + } + + public Account setClient(Client client) { + this.client = client; + return this; + } + + public AccountType getType() { + return type; + } + + public Account setType(AccountType type) { + this.type = type; + return this; + } + + public Double getAmount() { + return amount; + } + + public Account setAmount(Double amount) { + this.amount = amount; + return this; + } + + public Double getRate() { + return rate; + } + + public Account setRate(Double rate) { + this.rate = rate; + return this; + } + + public boolean isActive() { + return active; + } + + public Account setActive(boolean active) { + this.active = active; + return this; + } + + //tag::mapping-filter-Account-example[] + } + //end::mapping-filter-Account-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinColumnOrFormulaTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinColumnOrFormulaTest.java index d904fe94c7c0d0d5c9c709d0ee381840da4a31c5..0ed53059ab9574091c1659bbf52d5e6e7fc065b5 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinColumnOrFormulaTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinColumnOrFormulaTest.java @@ -164,8 +164,11 @@ public void setCountry(Country country) { this.country = country; } - //tag::mapping-JoinColumnOrFormula-example[] + //tag::mapping-JoinColumnOrFormula-example[] } + //end::mapping-JoinColumnOrFormula-example[] + + //tag::mapping-JoinColumnOrFormula-example[] @Entity(name = "Country") @Table(name = "countries") @@ -181,6 +184,10 @@ public static class Country implements Serializable { @Column(name = "is_default") private boolean _default; + //Getters and setters, equals and hashCode methods omitted for brevity + + //end::mapping-JoinColumnOrFormula-example[] + public int getId() { return id; } @@ -229,6 +236,7 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( getId() ); } + //tag::mapping-JoinColumnOrFormula-example[] } //end::mapping-JoinColumnOrFormula-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinFormulaTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinFormulaTest.java index bb6411061d591bbb134f207339b2152078394b9d..53c129cf9eab1f02d6803290c07f7780c0a7adaa 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinFormulaTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JoinFormulaTest.java @@ -143,6 +143,9 @@ public Country getCountry() { //tag::mapping-JoinFormula-example[] } + //end::mapping-JoinFormula-example[] + + //tag::mapping-JoinFormula-example[] @Entity(name = "Country") @Table(name = "countries") @@ -153,6 +156,10 @@ public static class Country { private String name; + //Getters and setters, equals and hashCode methods omitted for brevity + + //end::mapping-JoinFormula-example[] + public int getId() { return id; } @@ -185,6 +192,7 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( getId() ); } + //tag::mapping-JoinFormula-example[] } //end::mapping-JoinFormula-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ParentTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ParentTest.java index 178b8523a575ccf1560f827d3a5cc416f0fe2719..6a9b25b800bcd981ee49d2549949879e21c481ef 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ParentTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ParentTest.java @@ -70,6 +70,10 @@ public static class GPS { @Parent private City city; + //Getters and setters omitted for brevity + + //end::mapping-Parent-example[] + private GPS() { } @@ -93,7 +97,11 @@ public City getCity() { public void setCity(City city) { this.city = city; } + //tag::mapping-Parent-example[] } + //end::mapping-Parent-example[] + + //tag::mapping-Parent-example[] @Entity(name = "City") public static class City { diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/converter/MoneyConverterTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/converter/MoneyConverterTest.java index 9a658a46899fc03d58257cd871cbe639874c069d..6fc5389b293fc016c8f810ae07c86c6d0105b258 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/converter/MoneyConverterTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/converter/MoneyConverterTest.java @@ -54,6 +54,9 @@ public static class Money { private long cents; + //Getters and setters are omitted for brevity + //end::basic-jpa-convert-money-converter-mapping-example[] + public Money(long cents) { this.cents = cents; } @@ -65,23 +68,12 @@ public long getCents() { public void setCents(long cents) { this.cents = cents; } + //tag::basic-jpa-convert-money-converter-mapping-example[] } - - public static class MoneyConverter - implements AttributeConverter { - - @Override - public Long convertToDatabaseColumn(Money attribute) { - return attribute == null ? null : attribute.getCents(); - } - - @Override - public Money convertToEntityAttribute(Long dbData) { - return dbData == null ? null : new Money( dbData ); - } - } + //end::basic-jpa-convert-money-converter-mapping-example[] //tag::basic-jpa-convert-money-converter-mapping-example[] + @Entity(name = "Account") public static class Account { @@ -94,8 +86,7 @@ public static class Account { private Money balance; //Getters and setters are omitted for brevity - - //end::basic-jpa-convert-money-converter-mapping-example[] + //end::basic-jpa-convert-money-converter-mapping-example[] public Long getId() { return id; } @@ -121,5 +112,19 @@ public void setBalance(Money balance) { } //tag::basic-jpa-convert-money-converter-mapping-example[] } + + public static class MoneyConverter + implements AttributeConverter { + + @Override + public Long convertToDatabaseColumn(Money attribute) { + return attribute == null ? null : attribute.getCents(); + } + + @Override + public Money convertToEntityAttribute(Long dbData) { + return dbData == null ? null : new Money( dbData ); + } + } //end::basic-jpa-convert-money-converter-mapping-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableImplicitOverrideTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableImplicitOverrideTest.java index 42c6ee12a785778d15f657882ed72317914fd26e..c6dc6f87f0a14f3c0182a4bccd5b174afdd3dc06 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableImplicitOverrideTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableImplicitOverrideTest.java @@ -141,6 +141,9 @@ public static class Publisher { @ManyToOne(fetch = FetchType.LAZY) private Country country; + //Getters and setters, equals and hashCode methods omitted for brevity + //end::embeddable-multiple-namingstrategy-entity-mapping[] + public Publisher(String name, Country country) { this.name = name; this.country = country; @@ -148,9 +151,6 @@ public Publisher(String name, Country country) { private Publisher() {} - //Getters and setters are omitted for brevity - //end::embeddable-multiple-namingstrategy-entity-mapping[] - public String getName() { return name; } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableOverrideTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableOverrideTest.java index c6c0c2969fb2f02923f36401604bd0ade1d57879..31d070b62c5a92732b707bd5820d93e8df0bbf72 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableOverrideTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/EmbeddableOverrideTest.java @@ -161,6 +161,10 @@ public static class Publisher { @ManyToOne(fetch = FetchType.LAZY) private Country country; + //Getters and setters, equals and hashCode methods omitted for brevity + + //end::embeddable-type-association-mapping-example[] + public Publisher(String name, Country country) { this.name = name; this.country = country; @@ -168,9 +172,6 @@ public Publisher(String name, Country country) { private Publisher() {} - //Getters and setters are omitted for brevity - //end::embeddable-type-association-mapping-example[] - public String getName() { return name; } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/SimpleEmbeddableTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/SimpleEmbeddableTest.java index d1a741d3f3e86f94d285e23f15cb2d3ccc1828fc..6ae683fde56d93a8b01221e2e4b5ea6bf2d40a27 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/SimpleEmbeddableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/SimpleEmbeddableTest.java @@ -110,6 +110,11 @@ public static class Publisher { @Column(name = "publisher_country") private String country; + //Getters and setters, equals and hashCode methods omitted for brevity + + //end::embeddable-type-mapping-example[] + + public Publisher(String name, String country) { this.name = name; this.country = country; @@ -117,9 +122,6 @@ public Publisher(String name, String country) { private Publisher() {} - //Getters and setters are omitted for brevity - //end::embeddable-type-mapping-example[] - public String getName() { return name; } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java index 0d89292922c341de2f8a1f0398aee7359968635d..4da5ae8b2008e3a81cc15c9207f1ff71d4c845c5 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/CreationTimestampTest.java @@ -53,6 +53,9 @@ public static class Event { @CreationTimestamp private Date timestamp; + //Constructors, getters, and setters are omitted for brevity + //end::mapping-generated-CreationTimestamp-example[] + public Event() {} public Long getId() { @@ -62,6 +65,7 @@ public Long getId() { public Date getTimestamp() { return timestamp; } + //tag::mapping-generated-CreationTimestamp-example[] } //end::mapping-generated-CreationTimestamp-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java index f758bf49f21761acdf34e605bc934d6239ddd791..870997a9b4d37f23c24a424f081ad33a9254bc08 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java @@ -56,6 +56,9 @@ public static class Event { @FunctionCreationTimestamp private Date timestamp; + //Constructors, getters, and setters are omitted for brevity + //end::mapping-database-generated-value-example[] + public Event() {} public Long getId() { @@ -65,7 +68,11 @@ public Long getId() { public Date getTimestamp() { return timestamp; } + //tag::mapping-database-generated-value-example[] } + //end::mapping-database-generated-value-example[] + + //tag::mapping-database-generated-value-example[] @ValueGenerationType(generatedBy = FunctionCreationValueGeneration.class) @Retention(RetentionPolicy.RUNTIME) diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java index d3bd2e47ed9567fe65abeb64c72554c4a1fd6be3..7457a5137343d734e756cacb50e5bbab7c1ff3d9 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/InMemoryValueGenerationTest.java @@ -56,6 +56,8 @@ public static class Event { @FunctionCreationTimestamp private Date timestamp; + //Constructors, getters, and setters are omitted for brevity + //end::mapping-in-memory-generated-value-example[] public Event() {} public Long getId() { @@ -65,7 +67,11 @@ public Long getId() { public Date getTimestamp() { return timestamp; } + //tag::mapping-in-memory-generated-value-example[] } + //end::mapping-in-memory-generated-value-example[] + + //tag::mapping-in-memory-generated-value-example[] @ValueGenerationType(generatedBy = FunctionCreationValueGeneration.class) @Retention(RetentionPolicy.RUNTIME) diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/BytecodeEnhancementTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/BytecodeEnhancementTest.java index e082d7bcd668e188bfe8632bbaf541f7c7eb92db..e8f447acb74572a772fa391f6b367fbf714a044c 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/BytecodeEnhancementTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/BytecodeEnhancementTest.java @@ -88,6 +88,10 @@ public class Customer { @LazyGroup( "lobs" ) private Blob image; + //Getters and setters are omitted for brevity + + //end::BytecodeEnhancement-lazy-loading-example[] + public Integer getId() { return id; } @@ -119,6 +123,7 @@ public Blob getImage() { public void setImage(Blob image) { this.image = image; } + //tag::BytecodeEnhancement-lazy-loading-example[] } //end::BytecodeEnhancement-lazy-loading-example[] @@ -132,7 +137,11 @@ public static class Person { private String name; @OneToMany(mappedBy = "author") - private List books = new ArrayList<>( ); + private List books = new ArrayList<>(); + + //Getters and setters are omitted for brevity + + //end::BytecodeEnhancement-dirty-tracking-bidirectional-example[] public Long getId() { return id; @@ -153,6 +162,7 @@ public void setName(String name) { public List getBooks() { return books; } + //tag::BytecodeEnhancement-dirty-tracking-bidirectional-example[] } @Entity(name = "Book") @@ -169,6 +179,10 @@ public static class Book { @ManyToOne private Person author; + //Getters and setters are omitted for brevity + + //end::BytecodeEnhancement-dirty-tracking-bidirectional-example[] + public Long getId() { return id; } @@ -200,6 +214,7 @@ public String getIsbn() { public void setIsbn(String isbn) { this.isbn = isbn; } + //tag::BytecodeEnhancement-dirty-tracking-bidirectional-example[] } //end::BytecodeEnhancement-dirty-tracking-bidirectional-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java index a8ebb432baaf207038b4b0d42efbd566ed5785bf..be78241ad8ed8dbe5d0d97cf3fe6e4f87521b75f 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java @@ -50,7 +50,7 @@ public void test() { } ); } - //tag::pc-cascade-on-delete-mapping-example[] + //tag::pc-cascade-on-delete-mapping-Person-example[] @Entity(name = "Person") public static class Person { @@ -60,7 +60,8 @@ public static class Person { private String name; //Getters and setters are omitted for brevity - //end::pc-cascade-on-delete-mapping-example[] + + //end::pc-cascade-on-delete-mapping-Person-example[] public Long getId() { return id; @@ -77,9 +78,11 @@ public String getName() { public void setName(String name) { this.name = name; } - //tag::pc-cascade-on-delete-mapping-example[] + //tag::pc-cascade-on-delete-mapping-Person-example[] } - + //end::pc-cascade-on-delete-mapping-Person-example[] + + //tag::pc-cascade-on-delete-mapping-Phone-example[] @Entity(name = "Phone") public static class Phone { @@ -94,7 +97,8 @@ public static class Phone { private Person owner; //Getters and setters are omitted for brevity - //end::pc-cascade-on-delete-mapping-example[] + + //end::pc-cascade-on-delete-mapping-Phone-example[] public Long getId() { return id; @@ -119,7 +123,7 @@ public Person getOwner() { public void setOwner(Person owner) { this.owner = owner; } - //tag::pc-cascade-on-delete-mapping-example[] + //tag::pc-cascade-on-delete-mapping-Phone-example[] } - //end::pc-cascade-on-delete-mapping-example[] + //end::pc-cascade-on-delete-mapping-Phone-example[] } \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/PersistenceContextTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/PersistenceContextTest.java index 87d7c75254be34137f604914a2267fdeaf84bce3..48908b4f63ca92d81e608f3886dcd0e8d0c3f046 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/PersistenceContextTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/PersistenceContextTest.java @@ -418,6 +418,10 @@ public static class Book { @ManyToOne private Person author; + //Getters and setters are omitted for brevity + + //end::pc-find-by-natural-id-entity-example[] + public Long getId() { return id; } @@ -449,6 +453,7 @@ public String getIsbn() { public void setIsbn(String isbn) { this.isbn = isbn; } + //tag::pc-find-by-natural-id-entity-example[] } //end::pc-find-by-natural-id-entity-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/Person.java b/documentation/src/test/java/org/hibernate/userguide/pc/Person.java index 452e8a918602d84eed56dff8aba6cacdc2cfa739..620af209a6c80eb70ec682b32e8b6b64b89f788f 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/Person.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/Person.java @@ -22,6 +22,9 @@ public class Person { @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity +//end::pc-cascade-domain-model-example[] + public Long getId() { return id; } @@ -42,6 +45,8 @@ public List getPhones() { return phones; } +//tag::pc-cascade-domain-model-example[] + public void addPhone(Phone phone) { this.phones.add( phone ); phone.setOwner( this ); diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/Phone.java b/documentation/src/test/java/org/hibernate/userguide/pc/Phone.java index d9e58df212545db69f5b1e67a35f9add27e30d87..9208313a0cbe1c0317944e68d53d5a1bb9c82078 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/Phone.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/Phone.java @@ -22,6 +22,9 @@ public class Phone { @ManyToOne(fetch = FetchType.LAZY) private Person owner; + //Getters and setters are omitted for brevity +//end::pc-cascade-domain-model-example[] + public Long getId() { return id; } @@ -45,5 +48,6 @@ public Person getOwner() { public void setOwner(Person owner) { this.owner = owner; } +//tag::pc-cascade-domain-model-example[] } //end::pc-cascade-domain-model-example[] \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/persister/Author.java b/documentation/src/test/java/org/hibernate/userguide/persister/Author.java index f1ca79138da5cacf71dc48e0308fd7e2bfdcb606..4dd34a788188b19aad48d72b20200db45e853fa0 100644 --- a/documentation/src/test/java/org/hibernate/userguide/persister/Author.java +++ b/documentation/src/test/java/org/hibernate/userguide/persister/Author.java @@ -31,6 +31,7 @@ public class Author { public Set books = new HashSet<>(); //Getters and setters omitted for brevity + //end::entity-persister-mapping[] public Integer getId() { diff --git a/documentation/src/test/java/org/hibernate/userguide/schema/SchemaGenerationTest.java b/documentation/src/test/java/org/hibernate/userguide/schema/SchemaGenerationTest.java index 0f75862e02927b4d6e7b1c333b096594f855a0c4..dc0ecf863a5e7a88c012b646d8f47d50c3774d05 100644 --- a/documentation/src/test/java/org/hibernate/userguide/schema/SchemaGenerationTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/schema/SchemaGenerationTest.java @@ -91,6 +91,10 @@ public class Customer { @LazyGroup( "lobs" ) private Blob image; + //Getters and setters are omitted for brevity + + //end::schema-generation-domain-model-example[] + public Integer getId() { return id; } @@ -122,6 +126,7 @@ public Blob getImage() { public void setImage(Blob image) { this.image = image; } + //tag::schema-generation-domain-model-example[] } @Entity(name = "Person") @@ -133,7 +138,11 @@ public static class Person { private String name; @OneToMany(mappedBy = "author") - private List books = new ArrayList<>( ); + private List books = new ArrayList<>(); + + //Getters and setters are omitted for brevity + + //end::schema-generation-domain-model-example[] public Long getId() { return id; @@ -154,6 +163,7 @@ public void setName(String name) { public List getBooks() { return books; } + //tag::schema-generation-domain-model-example[] } @Entity(name = "Book") @@ -170,6 +180,10 @@ public static class Book { @ManyToOne private Person author; + //Getters and setters are omitted for brevity + + //end::schema-generation-domain-model-example[] + public Long getId() { return id; } @@ -201,6 +215,7 @@ public String getIsbn() { public void setIsbn(String isbn) { this.isbn = isbn; } + //tag::schema-generation-domain-model-example[] } //end::schema-generation-domain-model-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/Captain.java b/documentation/src/test/java/org/hibernate/userguide/sql/Captain.java index aaa2e2a76c570323ddfdb3fb498e897b1838b8e6..a48168b5a56afaabe294b51b288f4a2172273fdd 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/Captain.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/Captain.java @@ -21,6 +21,10 @@ public class Captain { @EmbeddedId private Identity id; + //Getters and setters are omitted for brevity + +//end::sql-composite-key-entity-associations_named-query-example[] + public Identity getId() { return id; } @@ -28,5 +32,6 @@ public Identity getId() { public void setId(Identity id) { this.id = id; } +//tag::sql-composite-key-entity-associations_named-query-example[] } //end::sql-composite-key-entity-associations_named-query-example[] \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLSecondaryTableTest.java b/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLSecondaryTableTest.java index 9635a8b5348d368df263440f704f201b33a503a2..6f7067f747f7b5e24cc5fc7c1248ff3fbe80b328 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLSecondaryTableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLSecondaryTableTest.java @@ -43,122 +43,127 @@ @RequiresDialect(PostgreSQL82Dialect.class) public class CustomSQLSecondaryTableTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Person.class - }; - } - - @Before - public void init() { - doInJPA( this::entityManagerFactory, entityManager -> { - Session session = entityManager.unwrap( Session.class ); - session.doWork( connection -> { - try(Statement statement = connection.createStatement(); ) { - statement.executeUpdate( "ALTER TABLE person ADD COLUMN valid boolean" ); - statement.executeUpdate( "ALTER TABLE person_details ADD COLUMN valid boolean" ); - } - } ); - }); - } - - @Test - public void test_sql_custom_crud() { - - Person _person = doInJPA( this::entityManagerFactory, entityManager -> { - Person person = new Person(); - person.setName( "John Doe" ); - entityManager.persist( person ); - person.setImage( new byte[] {1, 2, 3} ); - return person; - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - Long postId = _person.getId(); - Person person = entityManager.find( Person.class, postId ); - assertArrayEquals(new byte[] {1, 2, 3}, person.getImage()); - entityManager.remove( person ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - Long postId = _person.getId(); - Person person = entityManager.find( Person.class, postId ); - assertNull(person); - } ); - } - - - //tag::sql-custom-crud-secondary-table-example[] - @Entity(name = "Person") - @Table(name = "person") - @SQLInsert( - sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) " - ) - @SQLDelete( - sql = "UPDATE person SET valid = false WHERE id = ? " - ) - @SecondaryTable(name = "person_details", - pkJoinColumns = @PrimaryKeyJoinColumn(name = "person_id")) - @org.hibernate.annotations.Table( - appliesTo = "person_details", - sqlInsert = @SQLInsert( - sql = "INSERT INTO person_details (image, person_id, valid) VALUES (?, ?, true) ", - check = ResultCheckStyle.COUNT - ), - sqlDelete = @SQLDelete( - sql = "UPDATE person_details SET valid = false WHERE person_id = ? " - ) - ) - @Loader(namedQuery = "find_valid_person") - @NamedNativeQueries({ - @NamedNativeQuery( - name = "find_valid_person", - query = "select " + - " p.id, " + - " p.name, " + - " pd.image " + - "from person p " + - "left outer join person_details pd on p.id = pd.person_id " + - "where p.id = ? and p.valid = true and pd.valid = true", - resultClass = Person.class - ) - }) - public static class Person { - - @Id - @GeneratedValue - private Long id; - - private String name; - - @Column(name = "image", table = "person_details") - private byte[] image; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public byte[] getImage() { - return image; - } - - public void setImage(byte[] image) { - this.image = image; - } - } - //end::sql-custom-crud-secondary-table-example[] + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + Session session = entityManager.unwrap( Session.class ); + session.doWork( connection -> { + try(Statement statement = connection.createStatement(); ) { + statement.executeUpdate( "ALTER TABLE person ADD COLUMN valid boolean" ); + statement.executeUpdate( "ALTER TABLE person_details ADD COLUMN valid boolean" ); + } + } ); + }); + } + + @Test + public void test_sql_custom_crud() { + + Person _person = doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person(); + person.setName( "John Doe" ); + entityManager.persist( person ); + person.setImage( new byte[] {1, 2, 3} ); + return person; + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Long postId = _person.getId(); + Person person = entityManager.find( Person.class, postId ); + assertArrayEquals(new byte[] {1, 2, 3}, person.getImage()); + entityManager.remove( person ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Long postId = _person.getId(); + Person person = entityManager.find( Person.class, postId ); + assertNull(person); + } ); + } + + + //tag::sql-custom-crud-secondary-table-example[] + @Entity(name = "Person") + @Table(name = "person") + @SQLInsert( + sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) " + ) + @SQLDelete( + sql = "UPDATE person SET valid = false WHERE id = ? " + ) + @SecondaryTable(name = "person_details", + pkJoinColumns = @PrimaryKeyJoinColumn(name = "person_id")) + @org.hibernate.annotations.Table( + appliesTo = "person_details", + sqlInsert = @SQLInsert( + sql = "INSERT INTO person_details (image, person_id, valid) VALUES (?, ?, true) ", + check = ResultCheckStyle.COUNT + ), + sqlDelete = @SQLDelete( + sql = "UPDATE person_details SET valid = false WHERE person_id = ? " + ) + ) + @Loader(namedQuery = "find_valid_person") + @NamedNativeQueries({ + @NamedNativeQuery( + name = "find_valid_person", + query = "SELECT " + + " p.id, " + + " p.name, " + + " pd.image " + + "FROM person p " + + "LEFT OUTER JOIN person_details pd ON p.id = pd.person_id " + + "WHERE p.id = ? AND p.valid = true AND pd.valid = true", + resultClass = Person.class + ) + }) + public static class Person { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @Column(name = "image", table = "person_details") + private byte[] image; + + //Getters and setters are omitted for brevity + + //end::sql-custom-crud-secondary-table-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public byte[] getImage() { + return image; + } + + public void setImage(byte[] image) { + this.image = image; + } + //tag::sql-custom-crud-secondary-table-example[] + } + //end::sql-custom-crud-secondary-table-example[] } diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLTest.java b/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLTest.java index 427ff6ac49937f3ae67183e4fd4a1c177ddedb30..0489bcb251c6cc883a548c1b4979f7c40861b610 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/CustomSQLTest.java @@ -99,7 +99,6 @@ public void test_sql_custom_crud() { } ); } - //tag::sql-custom-crud-example[] @Entity(name = "Person") @SQLInsert( @@ -107,9 +106,11 @@ public void test_sql_custom_crud() { check = ResultCheckStyle.COUNT ) @SQLUpdate( - sql = "UPDATE person SET name = ? where id = ? ") + sql = "UPDATE person SET name = ? where id = ? " + ) @SQLDelete( - sql = "UPDATE person SET valid = false WHERE id = ? ") + sql = "UPDATE person SET valid = false WHERE id = ? " + ) @Loader(namedQuery = "find_valid_person") @NamedNativeQueries({ @NamedNativeQuery( @@ -136,6 +137,10 @@ public static class Person { @Where( clause = "valid = true" ) private List phones = new ArrayList<>(); + //Getters and setters are omitted for brevity + + //end::sql-custom-crud-example[] + public Long getId() { return id; } @@ -155,7 +160,7 @@ public void setName(String name) { public List getPhones() { return phones; } + //tag::sql-custom-crud-example[] } //end::sql-custom-crud-example[] - } diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/Dimensions.java b/documentation/src/test/java/org/hibernate/userguide/sql/Dimensions.java index 30c8f9ff6ca9601a371a5135ff70aa11ccb96bac..e34918adf321b8263654d5637eb1e7f1e2b4a2e0 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/Dimensions.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/Dimensions.java @@ -21,6 +21,10 @@ public class Dimensions { private int width; + //Getters and setters are omitted for brevity + +//end::sql-composite-key-entity-associations_named-query-example[] + public int getLength() { return length; } @@ -36,5 +40,6 @@ public int getWidth() { public void setWidth(int width) { this.width = width; } +//tag::sql-composite-key-entity-associations_named-query-example[] } //end::sql-composite-key-entity-associations_named-query-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/Identity.java b/documentation/src/test/java/org/hibernate/userguide/sql/Identity.java index af6162aba20c0a6af63da5e7c8ffc58b2404f0dc..ea195067bc99a65026b6d2fc31a7585022ce8569 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/Identity.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/Identity.java @@ -22,6 +22,10 @@ public class Identity implements Serializable { private String lastname; + //Getters and setters are omitted for brevity + +//end::sql-composite-key-entity-associations_named-query-example[] + public String getFirstname() { return firstname; } @@ -38,6 +42,7 @@ public void setLastname(String lastname) { this.lastname = lastname; } +//tag::sql-composite-key-entity-associations_named-query-example[] public boolean equals(Object o) { if ( this == o ) return true; if ( o == null || getClass() != o.getClass() ) return false; diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/PersonSummaryDTO.java b/documentation/src/test/java/org/hibernate/userguide/sql/PersonSummaryDTO.java index 0a1d2f22357309125c8addf94b82d9c6ff431889..92d6d1e459ceec0e372083de990b7e3b6dadff14 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/PersonSummaryDTO.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/PersonSummaryDTO.java @@ -16,6 +16,8 @@ public class PersonSummaryDTO { private String name; + //Getters and setters are omitted for brevity + public Number getId() { return id; } diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java b/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java index a25e23a9279e17df94833d8e4f43b43e7b4e635e..dec186e2a26216dad3405f8c7fb495c90b857d0b 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java @@ -495,10 +495,10 @@ public void test_sql_hibernate_multi_entity_query_example() { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-multi-entity-query-example[] List entities = session.createNativeQuery( - "SELECT * " + - "FROM Person pr, Partner pt " + - "WHERE pr.name = pt.name" ) - .list(); + "SELECT * " + + "FROM Person pr, Partner pt " + + "WHERE pr.name = pt.name" ) + .list(); //end::sql-hibernate-multi-entity-query-example[] assertEquals( 2, entities.size() ); } ); diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/SpaceShip.java b/documentation/src/test/java/org/hibernate/userguide/sql/SpaceShip.java index 9fca478e1cbb55ff49823e906d327a3ac226c84f..c2b56e7f6509158eeedbd6a43018e9552069db20 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/SpaceShip.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/SpaceShip.java @@ -81,6 +81,10 @@ public class SpaceShip { private Dimensions dimensions; + //Getters and setters are omitted for brevity + +//end::sql-composite-key-entity-associations_named-query-example[] + public String getName() { return name; } @@ -120,5 +124,6 @@ public Dimensions getDimensions() { public void setDimensions(Dimensions dimensions) { this.dimensions = dimensions; } +//tag::sql-composite-key-entity-associations_named-query-example[] } //end::sql-composite-key-entity-associations_named-query-example[] diff --git a/documentation/src/test/resources/log4j.properties b/documentation/src/test/resources/log4j.properties deleted file mode 100644 index 49358a1a2d754ecead55770f5792d42cd8e20a47..0000000000000000000000000000000000000000 --- a/documentation/src/test/resources/log4j.properties +++ /dev/null @@ -1,56 +0,0 @@ -# -# Hibernate, Relational Persistence for Idiomatic Java -# -# License: GNU Lesser General Public License (LGPL), version 2.1 or later. -# See the lgpl.txt file in the root directory or . -# -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.Target=System.out -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n - -log4j.rootLogger=info, stdout - -log4j.logger.org.hibernate=info -#log4j.logger.org.hibernate=warn - -log4j.logger.org.hibernate.ejb=info -log4j.logger.org.hibernate.ejb.packaging=info -log4j.logger.org.hibernate.reflection=info - -#log4j.logger.org.hibernate.engine.Cascades=warn -#log4j.logger.org.hibernate.hql=warn - -### log just the SQL -log4j.logger.org.hibernate.SQL=debug - -### log JDBC bind parameters ### -log4j.logger.org.hibernate.type=trace -log4j.logger.org.hibernate.type.descriptor.sql=trace -log4j.logger.org.hibernate.id.enhanced.TableGenerator=trace -log4j.logger.org.hibernate.id.IdentifierGeneratorHelper=trace -log4j.logger.org.hibernate.persister.entity.AbstractEntityPersister=trace -log4j.logger.org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl=trace - -### log schema export/update ### -log4j.logger.org.hibernate.tool.hbm2ddl=info - -### log HQL parse trees -#log4j.logger.org.hibernate.hql=warn - -### log cache activity ### -#log4j.logger.org.hibernate.cache=warn - -### log JDBC resource acquisition -#log4j.logger.org.hibernate.jdbc=warn - -### enable the following line if you want to track down connection ### -### leakages when using DriverManagerConnectionProvider ### -#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace - -### When entity copy merge functionality is enabled using: -### hibernate.event.merge.entity_copy_observer=log, the following will -### provide information about merged entity copies. -#log4j.logger.org.hibernate.event.internal.EntityCopyAllowedLoggedObserver=warn - -log4j.logger.org.hibernate.userguide=debug diff --git a/documentation/src/test/resources/log4j2.properties b/documentation/src/test/resources/log4j2.properties new file mode 100644 index 0000000000000000000000000000000000000000..bcca35fbe733a686ae631877fed877f852442967 --- /dev/null +++ b/documentation/src/test/resources/log4j2.properties @@ -0,0 +1,75 @@ +# +# Hibernate, Relational Persistence for Idiomatic Java +# +# License: GNU Lesser General Public License (LGPL), version 2.1 or later. +# See the lgpl.txt file in the root directory or . +# +appender.stdout.type=Console +appender.stdout.name=STDOUT +appender.stdout.layout.type=PatternLayout +appender.stdout.layout.pattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n + +rootLogger.level=info +rootLogger.appenderRef.stdout.ref=STDOUT + +logger.hibernate.name=org.hibernate +logger.hibernate.level=info +#logger.hibernate.level=warn + +logger.ejb.name=org.hibernate.ejb +logger.ejb.level=info +logger.ejb-packaging.name=org.hibernate.ejb.packaging +logger.ejb-packaging.level=info +logger.reflection.name=org.hibernate.reflection +logger.reflection.level=info + +logger.cascades.name=org.hibernate.engine.Cascades +#logger.cascades.level=warn + +### log just the SQL +logger.sql.name=org.hibernate.SQL +logger.sql.level=debug + +### log JDBC bind parameters ### +logger.hibernate-type.name=org.hibernate.type +logger.hibernate-type.level=trace +logger.type-sql.name=org.hibernate.type.descriptor.sql +logger.type-sql.level=trace +logger.table-generator.name=org.hibernate.id.enhanced.TableGenerator +logger.table-generator.level=trace +logger.identifier-generator-helper.name=org.hibernate.id.IdentifierGeneratorHelper +logger.identifier-generator-helper.level=trace +logger.abstract-entity-persister.name=org.hibernate.persister.entity.AbstractEntityPersister +logger.abstract-entity-persister.level=trace +logger.entity-reference-initializer-impl.name=org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl +logger.entity-reference-initializer-impl.level=trace + +### log schema export/update ### +logger.hbm2ddl.name=org.hibernate.tool.hbm2ddl +logger.hbm2ddl.level=info + +### log HQL parse trees +logger.hql.name=org.hibernate.hql +#logger.hql.level=warn + +### log cache activity ### +logger.cache.name=org.hibernate.cache +#logger.cache.level=warn + +### log JDBC resource acquisition +logger.hibernate-jdbc.name=org.hibernate.jdbc +#logger.hibernate-jdbc.level=warn + +### enable the following line if you want to track down connection ### +### leakages when using DriverManagerConnectionProvider ### +logger.driver-manager-connection-provider.name=org.hibernate.connection.DriverManagerConnectionProvider +#logger.driver-manager-connection-provider.level=trace + +### When entity copy merge functionality is enabled using: +### hibernate.event.merge.entity_copy_observer=log, the following will +### provide information about merged entity copies. +logger.entity-copy-allowed-logged-observer.name=org.hibernate.event.internal.EntityCopyAllowedLoggedObserver +#logger.entity-copy-allowed-logged-observer.level=warn + +logger.userguide.name=org.hibernate.userguide +logger.userguide.level=debug diff --git a/etc/log4j.properties b/etc/log4j2.properties similarity index 37% rename from etc/log4j.properties rename to etc/log4j2.properties index d0b320610af5d8f23fc93f1dad2240dba62a11be..e12215cd729948e19d994addfecbdeb012db6366 100644 --- a/etc/log4j.properties +++ b/etc/log4j2.properties @@ -6,49 +6,61 @@ # ### direct log messages to stdout ### -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.Target=System.out -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n +appender.stdout.type=Console +appender.stdout.name=STDOUT +appender.stdout.layout.type=PatternLayout +appender.stdout.layout.pattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ### direct messages to file hibernate.log ### -#log4j.appender.file=org.apache.log4j.FileAppender -#log4j.appender.file.File=hibernate.log -#log4j.appender.file.layout=org.apache.log4j.PatternLayout -#log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n +#appender.file.type=File +#appender.file.name=file +#appender.file.fileName=hibernate.log +#appender.file.layout.type=PatternLayout +#appender.file.layout.pattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ### set log levels - for more verbose logging change 'info' to 'debug' ### -log4j.rootLogger=warn, stdout +rootLogger.level=warn +rootLogger.appenderRef.stdout.ref=STDOUT -#log4j.logger.org.hibernate=info -log4j.logger.org.hibernate=debug +logger.hibernate.name=org.hibernate +#logger.hibernate.level=info +logger.hibernate.level=debug ### log HQL query parser activity -#log4j.logger.org.hibernate.hql.ast.AST=debug +logger.hql-ast.name=org.hibernate.hql.ast.AST +#logger.hql-ast.level=debug ### log just the SQL -#log4j.logger.org.hibernate.SQL=debug +logger.sql.name=org.hibernate.SQL +#logger.sql.level=debug ### log JDBC bind parameters ### -log4j.logger.org.hibernate.type=info -#log4j.logger.org.hibernate.type=debug +logger.hibernate-type.name=org.hibernate.type +logger.hibernate-type.level=info +#logger.hibernate-type.level=debug ### log schema export/update ### -log4j.logger.org.hibernate.tool.hbm2ddl=debug +logger.hbm2ddl.name=org.hibernate.tool.hbm2ddl +logger.hbm2ddl.level=debug ### log HQL parse trees -#log4j.logger.org.hibernate.hql=debug +logger.hql.name=org.hibernate.hql +#logger.hql.level=debug ### log cache activity ### -#log4j.logger.org.hibernate.cache=debug +logger.cache.name=org.hibernate.cache +#logger.cache.level=debug ### log transaction activity -#log4j.logger.org.hibernate.transaction=debug +logger.hibernate-transaction.name=org.hibernate.transaction +#logger.hibernate-transaction.level=debug ### log JDBC resource acquisition -#log4j.logger.org.hibernate.jdbc=debug +logger.hibernate-jdbc.name=org.hibernate.jdbc +#logger.hibernate-jdbc.level=debug ### enable the following line if you want to track down connection ### ### leakages when using DriverManagerConnectionProvider ### -#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace +logger.driver-manager-connection-provider.name=org.hibernate.connection.DriverManagerConnectionProvider +#logger.driver-manager-connection-provider.level=trace diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000000000000000000000000000000000000..b8ec248b503f500839768b971dea605941cbd662 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,17 @@ +# Keep all these properties in sync unless you know what you are doing! +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=256m -XX:+HeapDumpOnOutOfMemoryError -Duser.language=en -Duser.country=US -Duser.timezone=UTC -Dfile.encoding=UTF-8 +# Needs add-opens because of https://github.com/gradle/gradle/issues/15538 +toolchain.compiler.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=256m -XX:+HeapDumpOnOutOfMemoryError -Duser.language=en -Duser.country=US -Duser.timezone=UTC -Dfile.encoding=UTF-8 --add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED +toolchain.javadoc.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=256m -XX:+HeapDumpOnOutOfMemoryError -Duser.language=en -Duser.country=US -Duser.timezone=UTC -Dfile.encoding=UTF-8 +toolchain.launcher.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=256m -XX:+HeapDumpOnOutOfMemoryError -Duser.language=en -Duser.country=US -Duser.timezone=UTC -Dfile.encoding=UTF-8 + +# JDK auto-detection is not quite ready yet in Gradle 6.7. +# On Fedora in particular, if you have the package java-1.8.0-openjdk-headless-1.8.0.265.b01-1.fc32.x86_64 installed, +# Gradle will look for the Java binaries in /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.265.b01-1.fc32.x86_64/bin/java +# but it won't find it and will fail. +# It's just a JRE, so it's perfectly normal that the JDK is not present; +# the JRE is under /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.265.b01-1.fc32.x86_64/jre +org.gradle.java.installations.auto-detect=false +# We can't rely on Gradle's auto-download of JDKs as it doesn't support EA releases. +# See https://github.com/gradle/gradle/blob/fc7ea24f3c525d8d12a4346eb0f15976a6be9414/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/install/internal/AdoptOpenJdkRemoteBinary.java#L114 +org.gradle.java.installations.auto-download=false diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle deleted file mode 100644 index 7a71134bd99e82b3c244d21cf43ff56c8b7eaedc..0000000000000000000000000000000000000000 --- a/gradle/base-information.gradle +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later - * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html - */ - -apply plugin: 'base' - -ext { - ormVersion = new HibernateVersion( '5.3.3-SNAPSHOT', project ) - baselineJavaVersion = '1.8' - jpaVersion = new JpaVersion('2.2') -} - -group = 'org.hibernate' -version = project.ormVersion.fullName - -class JpaVersion { - /** The *normal* name (1.0, 2.0, ..) */ - final String name; - - final String osgiName - - JpaVersion(String version){ - this.name = version - this.osgiName = version + ".0" - } - - @Override - String toString() { - return name - } -} - -class HibernateVersion { - final String fullName - final String majorVersion - final String family - - final String osgiVersion - - final boolean isSnapshot - - HibernateVersion(String fullName, Project project) { - this.fullName = fullName - - final String[] hibernateVersionComponents = fullName.split( '\\.' ) - this.majorVersion = hibernateVersionComponents[0] - this.family = hibernateVersionComponents[0] + '.' + hibernateVersionComponents[1] - - this.isSnapshot = fullName.endsWith( '-SNAPSHOT' ) - - this.osgiVersion = isSnapshot ? family + '.' + hibernateVersionComponents[2] + '.SNAPSHOT' : fullName - } - - @Override - String toString() { - return this.fullName - } -} diff --git a/gradle/databases.gradle b/gradle/databases.gradle index 41e200dff57cd0f92ae4e4e78fdb1f2e6ba55a5e..8e741bab1706a1de98fbbf22288e1b06ae242df9 100644 --- a/gradle/databases.gradle +++ b/gradle/databases.gradle @@ -59,8 +59,7 @@ ext { 'jdbc.url' : 'jdbc:mysql://127.0.0.1/hibernate_orm_test?useSSL=false' ], mariadb : [ - 'db.dialect' : '', -// 'db.dialect' : 'org.hibernate.dialect.MariaDB102Dialect', + 'db.dialect' : 'org.hibernate.dialect.MariaDB102Dialect', 'jdbc.driver': 'org.mariadb.jdbc.Driver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', diff --git a/gradle/java-module.gradle b/gradle/java-module.gradle index 4c948132867cf34bb05cf332da085155fe57b7d8..5647421056fea0fadd9d9cf3efd7f6f627ccd79f 100644 --- a/gradle/java-module.gradle +++ b/gradle/java-module.gradle @@ -10,7 +10,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'de.thetaphi:forbiddenapis:2.5' + classpath 'de.thetaphi:forbiddenapis:3.0.1' } } @@ -21,12 +21,11 @@ import org.apache.tools.ant.filters.ReplaceTokens * Support for modules that contain Java code */ -apply from: rootProject.file( 'gradle/base-information.gradle' ) apply from: rootProject.file( 'gradle/libraries.gradle' ) apply from: rootProject.file( 'gradle/databases.gradle' ) apply plugin: 'java' -apply plugin: 'osgi' +apply plugin: 'biz.aQute.bnd.builder' apply plugin: 'checkstyle' apply plugin: 'build-dashboard' @@ -35,17 +34,11 @@ apply plugin: 'project-report' ext { java9ModuleNameBase = project.name.startsWith( 'hibernate-' ) ? name.drop( 'hibernate-'.length() ): name java9ModuleName = "org.hibernate.orm.$project.java9ModuleNameBase" - forbiddenAPITargetJDKCompatibility = '10' + forbiddenAPITargetJDKCompatibility = '11' } - -sourceCompatibility = project.baselineJavaVersion -targetCompatibility = project.baselineJavaVersion - -afterEvaluate { - if ( !project.description ) { - project.description = "The Hibernate ORM $project.name module" - } +if ( !project.description ) { + project.description = "The Hibernate ORM $project.name module" } @@ -56,9 +49,6 @@ configurations { provided { description = 'Non-exported compile-time dependencies.' } - jbossLoggingTool { - description = 'Dependencies for running the jboss-logging tooling.' - } asciidoclet { description = "Dependencies for Asciidoctor Javadoc taglet" } @@ -72,17 +62,19 @@ dependencies { provided libraries.logging_annotations - jbossLoggingTool( libraries.logging_processor ) + annotationProcessor( libraries.logging_processor ) + annotationProcessor( libraries.logging ) + annotationProcessor( libraries.logging_annotations ) testCompile( libraries.junit ) + testCompile( libraries.assertj ) testCompile( libraries.byteman ) testCompile( libraries.byteman_install ) testCompile( libraries.byteman_bmunit ) - testRuntime( libraries.log4j ) + testRuntime( libraries.log4j2 ) testRuntime( libraries.javassist ) testRuntime( libraries.byteBuddy ) - testRuntime( libraries.woodstox ) //Databases testRuntime( libraries.h2 ) @@ -108,32 +100,6 @@ dependencies { testRuntime( libraries.hana ) } - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Java 9 ftw! - if ( JavaVersion.current().isJava9Compatible() ) { - // The JDK used to run Gradle is Java 9+, and we assume that that is the same - // JDK for executing tasks - compile( 'com.sun.xml.bind:jaxb-impl:2.2.11' ) - compile( 'org.glassfish.jaxb:jaxb-xjc:2.2.11' ) - compile( 'org.jvnet.jaxb2_commons:jaxb2-basics:0.11.0' ) - compile( 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.11.0' ) - compile( 'javax.annotation:jsr250-api:1.0' ) - - testCompile( 'com.sun.xml.bind:jaxb-impl:2.2.11' ) - testCompile( 'org.glassfish.jaxb:jaxb-xjc:2.2.11' ) - testCompile( 'org.jvnet.jaxb2_commons:jaxb2-basics:0.11.0' ) - testCompile( 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.11.0' ) - testCompile( 'javax.annotation:jsr250-api:1.0' ) - - testRuntime( 'com.sun.xml.bind:jaxb-impl:2.2.11' ) - testRuntime( 'org.glassfish.jaxb:jaxb-xjc:2.2.11' ) - testRuntime( 'org.jvnet.jaxb2_commons:jaxb2-basics:0.11.0' ) - testRuntime( 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.11.0' ) - } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - // Mac-specific project.ext.toolsJar = file("${System.getProperty('java.home')}/../lib/tools.jar") if ( project.toolsJar.exists() ) { @@ -145,15 +111,85 @@ dependencies { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Compilation -tasks.withType(JavaCompile) { +tasks.withType( JavaCompile ) { options.encoding = 'UTF-8' } +if ( !gradle.ext.javaToolchainEnabled ) { + tasks.compileJava.configure { + sourceCompatibility = JavaVersion.toVersion( gradle.ext.javaVersions.main.release ) + targetCompatibility = JavaVersion.toVersion( gradle.ext.javaVersions.main.release ) + } + tasks.compileTestJava.configure { + sourceCompatibility = JavaVersion.toVersion( gradle.ext.javaVersions.test.release ) + targetCompatibility = JavaVersion.toVersion( gradle.ext.javaVersions.test.release ) + } +} +else { + // Configure generated bytecode + // "sourceCompatibility" is not supported with toolchains. We have to work around that limitation. + tasks.compileJava.configure { + if ( gradle.ext.javaVersions.main.compiler.asInt() < 9 ) { + options.compilerArgs << '-source' + options.compilerArgs << gradle.ext.javaVersions.main.release.toString() + options.compilerArgs << '-target' + options.compilerArgs << gradle.ext.javaVersions.main.release.toString() + } else { + options.release = gradle.ext.javaVersions.main.release.asInt() + // Needs add-opens because of https://github.com/gradle/gradle/issues/15538 + options.forkOptions.jvmArgs.addAll( ["--add-opens", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED"] ) + } + } + tasks.compileTestJava.configure { + if ( gradle.ext.javaVersions.test.compiler.asInt() < 9 ) { + options.compilerArgs << '-source' + options.compilerArgs << gradle.ext.javaVersions.test.release.toString() + options.compilerArgs << '-target' + options.compilerArgs << gradle.ext.javaVersions.test.release.toString() + } else { + options.release = gradle.ext.javaVersions.test.release.asInt() + // Needs add-opens because of https://github.com/gradle/gradle/issues/15538 + options.forkOptions.jvmArgs.addAll( ["--add-opens", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED"] ) + } + } + + // Configure version of Java tools + java { + toolchain { + languageVersion = gradle.ext.javaVersions.main.compiler + } + } + tasks.compileTestJava { + javaCompiler = javaToolchains.compilerFor { + languageVersion = gradle.ext.javaVersions.test.compiler + } + } + + // Configure JVM Options + tasks.withType( JavaCompile ).configureEach { + options.forkOptions.jvmArgs.addAll( getProperty( 'toolchain.compiler.jvmargs' ).toString().split( ' ' ) ) + } + tasks.withType( Javadoc ).configureEach { + options.setJFlags( getProperty( 'toolchain.javadoc.jvmargs' ).toString().split( ' ' ).toList().findAll( { !it.isEmpty() } ) ) + } + + // Display version of Java tools + tasks.withType( JavaCompile ).configureEach { + doFirst { + logger.lifecycle "Compiling with '${javaCompiler.get().metadata.installationPath}'" + } + } + tasks.withType( Javadoc ).configureEach { + doFirst { + logger.lifecycle "Generating javadoc with '${javadocTool.get().metadata.installationPath}'" + } + } +} + task compile(dependsOn: [compileJava, processResources, compileTestJava, processTestResources] ) sourceSets.main { compileClasspath += configurations.provided - compileClasspath += configurations.jbossLoggingTool } convention.findPlugin( JavaPluginConvention.class ).sourceSets.each { sourceSet -> @@ -190,18 +226,35 @@ if ( ext.toolsJar.exists() ) { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Testing +if ( gradle.ext.javaToolchainEnabled ) { + tasks.test { + // Configure version of Java tools + javaLauncher = javaToolchains.launcherFor { + languageVersion = gradle.ext.javaVersions.test.launcher + } + + // Configure JVM Options + jvmArgs( getProperty( 'toolchain.launcher.jvmargs' ).toString().split( ' ' ) ) + + // Display version of Java tools + doFirst { + logger.lifecycle "Testing with '${javaLauncher.get().metadata.installationPath}'" + } + } +} + tasks.withType( Test.class ).all { task -> - if ( JavaVersion.current().isJava9Compatible() ) { + if ( gradle.ext.javaVersions.test.launcher.asInt() >= 9 ) { // Byteman needs this property to be set, https://developer.jboss.org/thread/274997 task.jvmArgs += ["-Djdk.attach.allowAttachSelf=true"] } task.jvmArgs += [ '-XX:+HeapDumpOnOutOfMemoryError', "-XX:HeapDumpPath=${file( "${buildDir}/OOM-dump.hprof" ).absolutePath}", - '-XX:MetaspaceSize=512M' + '-XX:MetaspaceSize=256M' ] - task.maxHeapSize = '4G' + task.maxHeapSize = '3G' task.systemProperties['hibernate.test.validatefailureexpected'] = true task.systemProperties += System.properties.findAll { it.key.startsWith( "hibernate." ) } @@ -231,7 +284,32 @@ processTestResources { } } +// Keep system properties in sync with gradle.properties! +test { + systemProperty 'user.language', 'en' + systemProperty 'user.country', 'US' + systemProperty 'user.timezone', 'UTC' + systemProperty 'file.encoding', 'UTF-8' +} + +// Enable the experimental features of ByteBuddy with JDK 19+ +test { + if ( gradle.ext.javaVersions.test.release.asInt() >= 19 ) { + logger.warn( "The version of Java bytecode that will be tested is not supported by Bytebuddy by default. " + + " Setting 'net.bytebuddy.experimental=true'." ) + systemProperty 'net.bytebuddy.experimental', true + } +} +test { + if ( project.findProperty( 'log-test-progress' )?.toString()?.toBoolean() ) { + // Log a statement for each test. + // Used in the Travis build so that Travis doesn't end up panicking because there's no output for a long time. + testLogging { + events "passed", "skipped", "failed" + } + } +} // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // IDE @@ -301,16 +379,12 @@ task copyResourcesToIntelliJOutFolder { Afterward, you can run any test from the IDE against that particular DB. */ -task setDataBase { - inputs.property( "db", db ) - doLast { - processTestResources.execute() - copyResourcesToIntelliJOutFolder.execute() - - println( 'Setting current database to ' + db ) - } +task setDataBase dependsOn( processTestResources, copyResourcesToIntelliJOutFolder ) { + println( 'Setting current database to ' + db ) } +copyResourcesToIntelliJOutFolder.mustRunAfter processTestResources + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Report configs @@ -341,19 +415,27 @@ task forbiddenApisSystemOut(type: CheckForbiddenApis, dependsOn: compileJava) { targetCompatibility = project.forbiddenAPITargetJDKCompatibility bundledSignatures += 'jdk-system-out' suppressAnnotations += ['org.hibernate.internal.build.AllowSysOut', 'org.hibernate.internal.build.AllowPrintStacktrace'] + + // This slows down the checks a little, but is necessary to avoid the gradle deamon holding on + // to class definitions loaded previously - even possibly in a previous build. + disableClassloadingCache = true } task forbiddenApisUnsafe(type: CheckForbiddenApis, dependsOn: compileJava) { classesDirs = project.sourceSets.main.output.classesDirs classpath = project.sourceSets.main.compileClasspath + project.sourceSets.main.runtimeClasspath targetCompatibility = project.forbiddenAPITargetJDKCompatibility - bundledSignatures += "jdk-unsafe-${baselineJavaVersion}".toString() + bundledSignatures += "jdk-unsafe-${gradle.ext.baselineJavaVersion}".toString() // unfortunately we currently have many uses of default Locale implicitly (~370) which need to be fixed // before we can fully enabled this check // // No idea how findbugs was missing these b4 ignoreFailures = true + + // This slows down the checks a little, but is necessary to avoid the gradle deamon holding on + // to class definitions loaded previously - even possibly in a previous build. + disableClassloadingCache = true } task forbiddenApisNonPortable(type: CheckForbiddenApis, dependsOn: compileJava) { @@ -361,6 +443,10 @@ task forbiddenApisNonPortable(type: CheckForbiddenApis, dependsOn: compileJava) classpath = project.sourceSets.main.compileClasspath + project.sourceSets.main.runtimeClasspath targetCompatibility = project.forbiddenAPITargetJDKCompatibility bundledSignatures += 'jdk-non-portable' + + // This slows down the checks a little, but is necessary to avoid the gradle deamon holding on + // to class definitions loaded previously - even possibly in a previous build. + disableClassloadingCache = true } task forbiddenApis diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 58451aca510b5b6821e42bc0473a0545ab125dec..04c1275b8ee828fa65782ac6069066f290152ebf 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -11,30 +11,36 @@ ext { junitVersion = '4.12' h2Version = '1.4.196' - bytemanVersion = '4.0.3' //Compatible with JDK10 + bytemanVersion = '4.0.16' //Compatible with JDK 17 jnpVersion = '5.0.6.CR1' + assertjVersion = "3.22.0" - hibernateValidatorVersion = '6.0.7.Final' - hibernateCommonsVersion = '5.0.4.Final' + hibernateValidatorVersion = '6.0.22.Final' + hibernateCommonsVersion = '5.0.5.Final' validationApiVersion = '2.0.1.Final' - elVersion = '3.0.1-b08' + elVersion = '3.0.1-b09' cdiVersion = '2.0' - weldVersion = '3.0.0.Final' + weldVersion = '3.1.8.Final' - javassistVersion = '3.22.0-GA' - byteBuddyVersion = '1.8.12' // Now with JDK10 compatibility and preliminary support for JDK11 + javassistVersion = '3.27.0-GA' + byteBuddyVersion = '1.11.12' geolatteVersion = '1.3.0' // Wildfly version targeted by module ZIP; Arquillian/Shrinkwrap versions used for CDI testing and testing the module ZIP - wildflyVersion = '13.0.0.Final' - arquillianVersion = '1.1.11.Final' + wildflyVersion = '17.0.1.Final' + arquillianVersion = '1.4.1.Final' shrinkwrapVersion = '1.2.6' - shrinkwrapDescriptorsVersion = '2.0.0-alpha-8' - wildflyArquillianContainerVersion = '2.0.0.Final' + shrinkwrapDescriptorsVersion = '2.0.0' + wildflyArquillianContainerVersion = '2.2.0.Final' - jodaTimeVersion = '2.3' + jodaTimeVersion = '2.9.7' + + jaxbApiVersion = '2.3.1' + // We can't upgrade JAXB in Karaf (yet), but fortunately everything works fine with the version built in Karaf + jaxbApiVersionOsgiRange = "[2.2,3)" + jaxbRuntimeVersion = '2.3.1' libraries = [ // Ant @@ -46,13 +52,10 @@ ext { // Annotations commons_annotations: "org.hibernate.common:hibernate-commons-annotations:${hibernateCommonsVersion}", jandex: 'org.jboss:jandex:2.0.5.Final', - classmate: 'com.fasterxml:classmate:1.3.4', - - // Woodstox - woodstox: "org.codehaus.woodstox:woodstox-core-asl:4.3.0", + classmate: 'com.fasterxml:classmate:1.5.1', // Dom4J - dom4j: 'dom4j:dom4j:1.6.1@jar', + dom4j: 'org.dom4j:dom4j:2.1.3@jar', // Javassist javassist: "org.javassist:javassist:${javassistVersion}", @@ -70,16 +73,18 @@ ext { activation: 'javax.activation:javax.activation-api:1.2.0', // logging - logging: 'org.jboss.logging:jboss-logging:3.3.2.Final', - logging_annotations: 'org.jboss.logging:jboss-logging-annotations:2.1.0.Final', - logging_processor: 'org.jboss.logging:jboss-logging-processor:2.1.0.Final', + logging: 'org.jboss.logging:jboss-logging:3.4.2.Final', + logging_annotations: 'org.jboss.logging:jboss-logging-annotations:2.2.1.Final', + logging_processor: 'org.jboss.logging:jboss-logging-processor:2.2.1.Final', // jaxb task - jaxb: 'com.sun.xml.bind:jaxb-xjc:2.2.5', - jaxb2_basics: 'org.jvnet.jaxb2_commons:jaxb2-basics:0.6.3', - jaxb2_ant: 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.6.3', - jaxb2_jaxb: 'org.jvnet.jaxb2_commons:jaxb2-basics-jaxb:2.2.4-1', - jaxb2_jaxb_xjc: 'org.jvnet.jaxb2_commons:jaxb2-basics-jaxb-xjc:2.2.4-1', + jaxb_api: "javax.xml.bind:jaxb-api:${jaxbApiVersion}", + jaxb_runtime: "org.glassfish.jaxb:jaxb-runtime:${jaxbRuntimeVersion}", + jaxb_xjc: "org.glassfish.jaxb:jaxb-xjc:${jaxbRuntimeVersion}", + // Note that jaxb2_basics is a set of tools on *top* of JAXB. + // See https://github.com/highsource/jaxb2-basics + jaxb2_basics: 'org.jvnet.jaxb2_commons:jaxb2-basics:0.12.0', + jaxb2_basics_ant: 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.12.0', geolatte: "org.geolatte:geolatte-geom:${geolatteVersion}", @@ -97,8 +102,9 @@ ext { // ~~~~~~~~~~~~~~~~~~~~~~~~~~ testing - log4j: "log4j:log4j:1.2.17", + log4j2: "org.apache.logging.log4j:log4j-core:2.17.1", junit: "junit:junit:${junitVersion}", + assertj: "org.assertj:assertj-core:${assertjVersion}", byteman: "org.jboss.byteman:byteman:${bytemanVersion}", byteman_install: "org.jboss.byteman:byteman-install:${bytemanVersion}", byteman_bmunit: "org.jboss.byteman:byteman-bmunit:${bytemanVersion}", @@ -106,12 +112,11 @@ ext { hsqldb: "org.hsqldb:hsqldb:2.3.2", derby: "org.apache.derby:derby:10.11.1.1", postgresql: 'org.postgresql:postgresql:42.2.2', - //Upgrade MySQL Driver only when this issue gets fixed: https://bugs.mysql.com/bug.php?id=85941 - mysql: 'mysql:mysql-connector-java:5.1.46', + mysql: 'mysql:mysql-connector-java:8.0.12', mariadb: 'org.mariadb.jdbc:mariadb-java-client:2.2.3', oracle: 'com.oracle.jdbc:ojdbc8:12.2.0.1', - mssql: 'com.microsoft.sqlserver:mssql-jdbc:6.4.0.jre8', + mssql: 'com.microsoft.sqlserver:mssql-jdbc:7.2.1.jre8', db2: 'com.ibm.db2:db2jcc:10.5', hana: 'com.sap.cloud.db.jdbc:ngdbc:2.2.16', // for HANA 1 the minimum required client version is 1.120.20 @@ -120,8 +125,8 @@ ext { informix: 'com.ibm.informix:jdbc:4.10.7.20160517', jboss_jta: "org.jboss.jbossts:jbossjta:4.16.4.Final", xapool: "com.experlog:xapool:1.5.0", - mockito: 'org.mockito:mockito-core:2.18.0', - mockito_inline: 'org.mockito:mockito-inline:2.18.0', + mockito: 'org.mockito:mockito-core:2.19.1', + mockito_inline: 'org.mockito:mockito-inline:2.19.1', validator: "org.hibernate.validator:hibernate-validator:${hibernateValidatorVersion}", // EL required by Hibernate Validator at test runtime @@ -159,3 +164,12 @@ ext { jboss_annotation_spec_jar : 'org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec:1.0.0.Final' ] } + +configurations.all { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + //Force the "byte buddy agent" version to match the Byte Buddy version we use, as Mockito might pull in a mismatched version transitively: + if (details.requested.group + ":" + details.requested.name == 'net.bytebuddy:byte-buddy-agent') { + details.useVersion byteBuddyVersion + } + } +} diff --git a/gradle/published-java-module.gradle b/gradle/published-java-module.gradle index 90a3318edad3522b1a40c99945660fa95c73c07d..5378813c8e7d80960d442a401d026e0273ee068a 100644 --- a/gradle/published-java-module.gradle +++ b/gradle/published-java-module.gradle @@ -10,70 +10,91 @@ apply from: rootProject.file( 'gradle/java-module.gradle' ) apply from: rootProject.file( 'gradle/publishing-repos.gradle' ) apply from: rootProject.file( 'gradle/publishing-pom.gradle' ) - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Jar jar { - manifest = osgiManifest { - // GRADLE-1411: Even if we override Imports and Exports - // auto-generation with instructions, classesDir and classpath - // need to be here (temporarily). - - if ( project.pluginManager.hasPlugin( 'groovy' ) ) { - classesDir = sourceSets.main.groovy.outputDir - } - else { - classesDir = sourceSets.main.output.classesDir - } - classpath = configurations.runtime - - - // Java 9 module name - instruction 'Automatic-Module-Name', project.java9ModuleName - - // the OSGi metadata - symbolicName project.java9ModuleName - vendor 'Hibernate.org' - description project.description - docURL "http://www.hibernate.org/orm/${project.ormVersion.family}" - - instruction 'Import-Package', - // Temporarily support JTA 1.1 -- Karaf and other frameworks still - // use it. Without this, the plugin generates [1.2,2). - 'javax.transaction;version="[1.1,2)"', - // Tell Gradle OSGi to still dynamically import the other packages. - // IMPORTANT: Do not include the * in the modules' .gradle files. - // If it exists more than once, the manifest will physically contain a *. - '*' - - // Basic JAR manifest metadata - instruction 'Specification-Title', project.name - instruction 'Specification-Version', project.version - instruction 'Specification-Vendor', 'Hibernate.org' - instruction 'Implementation-Title', project.name - instruction 'Implementation-Version', project.version - instruction 'Implementation-Vendor', 'Hibernate.org' - instruction 'Implementation-Vendor-Id', 'org.hibernate' - instruction 'Implementation-Url', 'http://hibernate.org/orm' - - instruction 'Hibernate-VersionFamily', project.ormVersion.family - instruction 'Hibernate-JpaVersion', project.jpaVersion.name + manifest { + attributes( + // Basic JAR manifest attributes + 'Specification-Title': project.name, + 'Specification-Version': project.version, + 'Specification-Vendor': 'Hibernate.org', + 'Implementation-Title': project.name, + 'Implementation-Version': project.version, + 'Implementation-Vendor': 'Hibernate.org', + 'Implementation-Vendor-Id': 'org.hibernate', + 'Implementation-Url': 'http://hibernate.org/orm', + + // Java 9 module name + 'Automatic-Module-Name': project.java9ModuleName, + + // Hibernate-specific JAR manifest attributes + 'Hibernate-VersionFamily': project.ormVersion.family, + 'Hibernate-JpaVersion': project.jpaVersion.name, + + // BND Plugin instructions (for OSGi): + 'Bundle-Name': project.name, + 'Bundle-SymbolicName': project.java9ModuleName, + 'Bundle-Vendor': 'Hibernate.org', + 'Bundle-DocURL': "http://www.hibernate.org/orm/${project.ormVersion.family}", + // This is overridden in some sub-projects + 'Import-Package': [ + // Temporarily support JTA 1.1 -- Karaf and other frameworks still + // use it. Without this, the plugin generates [1.2,2). + 'javax.transaction;version="[1.1,2)"', + // Also import every package referenced in the code + // (note that '*' is resolved at build time to a list of packages) + '*' + ].join( ',' ), + '-exportcontents': "*;version=${project.version}" + ) } } task sourcesJar(type: Jar) { from project.sourceSets.main.allSource - manifest = project.tasks.jar.manifest - classifier = 'sources' + manifest { + attributes( + // Basic JAR manifest attributes + 'Specification-Title': project.name, + 'Specification-Version': project.version, + 'Specification-Vendor': 'Hibernate.org', + 'Implementation-Title': project.name, + 'Implementation-Version': project.version, + 'Implementation-Vendor': 'Hibernate.org', + 'Implementation-Vendor-Id': 'org.hibernate', + 'Implementation-Url': 'http://hibernate.org/orm', + + // Hibernate-specific JAR manifest attributes + 'Hibernate-VersionFamily': project.ormVersion.family, + 'Hibernate-JpaVersion': project.jpaVersion.name + ) + } + archiveClassifier.set( 'sources' ) } task javadocJar(type: Jar) { from project.tasks.javadoc.outputs - manifest = project.tasks.jar.manifest - classifier = 'javadoc' + manifest { + attributes( + // Basic JAR manifest attributes + 'Specification-Title': project.name, + 'Specification-Version': project.version, + 'Specification-Vendor': 'Hibernate.org', + 'Implementation-Title': project.name, + 'Implementation-Version': project.version, + 'Implementation-Vendor': 'Hibernate.org', + 'Implementation-Vendor-Id': 'org.hibernate', + 'Implementation-Url': 'http://hibernate.org/orm', + + // Hibernate-specific JAR manifest attributes + 'Hibernate-VersionFamily': project.ormVersion.family, + 'Hibernate-JpaVersion': project.jpaVersion.name + ) + } + archiveClassifier.set( 'javadoc' ) } @@ -87,9 +108,6 @@ javadoc { final int currentYear = new GregorianCalendar().get( Calendar.YEAR ) configure( options ) { - docletpath = configurations.asciidoclet.files.asType(List) - doclet = 'org.asciidoctor.Asciidoclet' - windowTitle = "$project.name JavaDocs" docTitle = "$project.name JavaDocs ($project.version)" bottom = "Copyright © 2001-$currentYear Red Hat, Inc. All Rights Reserved." @@ -101,14 +119,22 @@ javadoc { 'http://docs.jboss.org/cdi/api/2.0/', 'https://javaee.github.io/javaee-spec/javadocs/' ] - - if ( JavaVersion.current().isJava8Compatible() ) { - options.addStringOption( 'Xdoclint:none', '-quiet' ) + + if ( gradle.ext.javaVersions.main.compiler.asInt() >= 11 ) { + //The need to set `--source 1.8` applies to all JVMs after 11, and also to 11 + // but after excluding the first two builds; see also specific comments on + // https://bugs.openjdk.java.net/browse/JDK-8212233?focusedCommentId=14245762 + // For now, let's be compatible with JDK 11.0.3+. We can improve on it if people + // complain they cannot build with JDK 11.0.0, 11.0.1 and 11.0.2. + logger.lifecycle "Forcing Javadoc in Java 8 compatible mode" + options.source = gradle.ext.baselineJavaVersion } + options.addStringOption( 'Xdoclint:none', '-quiet' ) + doFirst { // ordering problems if we try to do this during config phase :( - classpath += project.sourceSets.main.output + project.sourceSets.main.compileClasspath + project.configurations.provided + classpath += project.sourceSets.main.output.classesDirs + project.sourceSets.main.compileClasspath + project.configurations.provided } } } @@ -138,5 +164,6 @@ publishing { task ciBuild( dependsOn: [test, publish] ) -task release( dependsOn: [test, bintrayUpload] ) +task release( dependsOn: [test, publishToSonatype] ) +publishToSonatype.mustRunAfter test diff --git a/gradle/publishing-pom.gradle b/gradle/publishing-pom.gradle index 99cb3131376dfce0fe8fc7fd6a937ba8e7c3364c..eb3e94d4c62d864f54804183f5336ac6d96e904f 100644 --- a/gradle/publishing-pom.gradle +++ b/gradle/publishing-pom.gradle @@ -7,6 +7,11 @@ apply plugin: 'maven-publish' +// Disable Gradle module metadata publishing until we know what we want. +// https://docs.gradle.org/6.0.1/userguide/publishing_gradle_module_metadata.html#sub:disabling-gmm-publication +tasks.withType(GenerateModuleMetadata) { + enabled = false +} publishing { diff --git a/gradle/publishing-repos.gradle b/gradle/publishing-repos.gradle index 7a5031b47b9fbea57f1634a240c2c1d2433ab17b..b417e1eba3ca2ddfdec32eafac395b05f450663e 100644 --- a/gradle/publishing-repos.gradle +++ b/gradle/publishing-repos.gradle @@ -5,20 +5,11 @@ * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -apply from: rootProject.file( 'gradle/base-information.gradle' ) - - apply plugin: 'maven-publish' -apply plugin: 'maven-publish-auth' -apply plugin: 'com.jfrog.bintray' - - -ext { - bintrayUser = project.hasProperty( 'PERSONAL_BINTRAY_USER' ) ? project.property( 'PERSONAL_BINTRAY_USER' ) : null - bintrayKey = project.hasProperty( 'PERSONAL_BINTRAY_API_KEY' ) ? project.property( 'PERSONAL_BINTRAY_API_KEY' ) : null +if ( rootProject.ormVersion.isSnapshot ) { + apply plugin: 'org.hibernate.build.maven-repo-auth' } - publishing { publications { publishedArtifacts( MavenPublication ) @@ -32,37 +23,6 @@ publishing { } } -bintray { - user = project.bintrayUser - key = project.bintrayKey - - publications = ['publishedArtifacts'] - - pkg { - userOrg = 'hibernate' - repo = 'artifacts' - name = 'hibernate-orm' - - publish = true - - version { - name = project.version - released = new Date() - vcsTag = project.version - gpg { - sign = true - } - attributes = [ - 'jpa': project.jpaVersion, - 'family': project.ormVersion.family - ] - mavenCentralSync { - sync = true - } - } - } -} - model { tasks.generatePomFileForPublishedArtifactsPublication { destination = file( "${buildDir}/generated-pom.xml" ) diff --git a/gradle/version.properties b/gradle/version.properties new file mode 100644 index 0000000000000000000000000000000000000000..642d0456b2a2e127937d0281a83f8fa5f5bb71af --- /dev/null +++ b/gradle/version.properties @@ -0,0 +1 @@ +hibernateVersion=5.3.37-SNAPSHOT \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 91ca28c8b802289c3a438766657a5e98f20eff03..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4fc03df38ab71c1a069fffe4a652aab79914e10..1f3fdbc52873a4de09002f06ec6ebec8b859749a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index cccdd3d517fc5249beaefa600691cf150f2fa3e6..4f906e0c811fc9e230eb44819f509cd0627f2600 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -66,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -138,19 +156,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index e95643d6a2ca62258464e83c72f5156dc941c609..ac1b06f93825db68fb0c0b5150917f340eaa5d02 100644 Binary files a/gradlew.bat and b/gradlew.bat differ diff --git a/hibernate-agroal/hibernate-agroal.gradle b/hibernate-agroal/hibernate-agroal.gradle index 29bc4b9df8cb147507eabc467e03f1408e5f0b89..86b7a685183c714866a9d26872874352010f850d 100644 --- a/hibernate-agroal/hibernate-agroal.gradle +++ b/hibernate-agroal/hibernate-agroal.gradle @@ -5,10 +5,10 @@ * See the lgpl.txt file in the root directory or . */ -apply from: rootProject.file( 'gradle/published-java-module.gradle' ) - description = 'Integration for Agroal as a ConnectionProvider for Hibernate ORM' +apply from: rootProject.file( 'gradle/published-java-module.gradle' ) + dependencies { compile project( ':hibernate-core' ) compile( libraries.agroal_api ) @@ -16,5 +16,6 @@ dependencies { runtime( libraries.agroal_pool ) testCompile project( ':hibernate-testing' ) - testCompile(libraries.mockito) + testCompile( libraries.mockito ) + testCompile( libraries.mockito_inline ) } diff --git a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalSkipAutoCommitTest.java b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalSkipAutoCommitTest.java index e4025cb29855095e4d0719f3e81ff74a87fd4b7a..d32cc2be75f1d753667604b5e632db2f79992b5f 100644 --- a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalSkipAutoCommitTest.java +++ b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalSkipAutoCommitTest.java @@ -9,6 +9,8 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.test.agroal.util.PreparedStatementSpyConnectionProvider; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -28,6 +30,7 @@ /** * @author Vlad Mihalcea */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class AgroalSkipAutoCommitTest extends BaseCoreFunctionalTestCase { private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(); diff --git a/hibernate-agroal/src/test/resources/log4j.properties b/hibernate-agroal/src/test/resources/log4j.properties deleted file mode 100644 index eb96581a28b596c9439b4ab2e6b500fcd196b562..0000000000000000000000000000000000000000 --- a/hibernate-agroal/src/test/resources/log4j.properties +++ /dev/null @@ -1,60 +0,0 @@ -# -# Hibernate, Relational Persistence for Idiomatic Java -# -# License: GNU Lesser General Public License (LGPL), version 2.1 or later. -# See the lgpl.txt file in the root directory or . -# -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.Target=System.out -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n -#log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L (hibernateLoadPlanWalkPath->%X{hibernateLoadPlanWalkPath}) - %m%n - -#log4j.appender.stdout-mdc=org.apache.log4j.ConsoleAppender -#log4j.appender.stdout-mdc.Target=System.out -#log4j.appender.stdout-mdc.layout=org.apache.log4j.PatternLayout -#log4j.appender.stdout-mdc.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L (walk path -> %X{hibernateLoadPlanWalkPath}) - %m%n - -log4j.appender.unclosedSessionFactoryFile=org.apache.log4j.FileAppender -log4j.appender.unclosedSessionFactoryFile.append=true -log4j.appender.unclosedSessionFactoryFile.file=target/tmp/log/UnclosedSessionFactoryWarnings.log -log4j.appender.unclosedSessionFactoryFile.layout=org.apache.log4j.PatternLayout -log4j.appender.unclosedSessionFactoryFile.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n - -log4j.rootLogger=info, stdout - -#log4j.logger.org.hibernate.loader.plan=trace, stdout-mdc -#log4j.additivity.org.hibernate.loader.plan=false -#log4j.logger.org.hibernate.persister.walking=trace, stdout-mdc -#log4j.additivity.org.hibernate.persister.walking=false - -log4j.logger.org.hibernate.tool.hbm2ddl=trace -log4j.logger.org.hibernate.testing.cache=debug - -# SQL Logging - HHH-6833 -log4j.logger.org.hibernate.SQL=debug - -log4j.logger.org.hibernate.type.descriptor.sql.BasicBinder=trace -log4j.logger.org.hibernate.type.descriptor.sql.BasicExtractor=trace - -log4j.logger.org.hibernate.hql.internal.ast=debug - -log4j.logger.org.hibernate.sql.ordering.antlr=debug - -log4j.logger.org.hibernate.loader.plan2.build.internal.LoadPlanImpl=debug -log4j.logger.org.hibernate.loader.plan2.build.spi.LoadPlanTreePrinter=debug -log4j.logger.org.hibernate.loader.plan2.exec.spi.EntityLoadQueryDetails=debug - -log4j.logger.org.hibernate.engine.internal.StatisticalLoggingSessionEventListener=info - -log4j.logger.org.hibernate.boot.model.source.internal.hbm.ModelBinder=debug -log4j.logger.org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry=debug - - -### When entity copy merge functionality is enabled using: -### hibernate.event.merge.entity_copy_observer=log, the following will -### provide information about merged entity copies. -### log4j.logger.org.hibernate.event.internal.EntityCopyAllowedLoggedObserver=debug - -log4j.logger.org.hibernate.testing.junit4.TestClassMetadata=info, unclosedSessionFactoryFile -log4j.logger.org.hibernate.boot.model.process.internal.ScanningCoordinator=debug diff --git a/hibernate-agroal/src/test/resources/log4j2.properties b/hibernate-agroal/src/test/resources/log4j2.properties new file mode 100644 index 0000000000000000000000000000000000000000..c9a2a7cad77d3ea0518afa469ae4f5feddb8a06c --- /dev/null +++ b/hibernate-agroal/src/test/resources/log4j2.properties @@ -0,0 +1,83 @@ +# +# Hibernate, Relational Persistence for Idiomatic Java +# +# License: GNU Lesser General Public License (LGPL), version 2.1 or later. +# See the lgpl.txt file in the root directory or . +# +appender.stdout.type=Console +appender.stdout.name=STDOUT +appender.stdout.layout.type=PatternLayout +appender.stdout.layout.pattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n +appender.stdout.layout.pattern=%d{ABSOLUTE} %5p %c{1}:%L (hibernateLoadPlanWalkPath->%X{hibernateLoadPlanWalkPath}) - %m%n + +appender.stdout-mdc.type=Console +appender.stdout-mdc.name=stdout-mdc +appender.stdout-mdc.layout.type=PatternLayout +appender.stdout-mdc.layout.pattern=%d{ABSOLUTE} %5p %c{1}:%L (walk path -> %X{hibernateLoadPlanWalkPath}) - %m%n + +appender.unclosedSessionFactoryFile.type=File +appender.unclosedSessionFactoryFile.name=unclosedSessionFactoryFile +appender.unclosedSessionFactoryFile.append=true +appender.unclosedSessionFactoryFile.fileName=target/tmp/log/UnclosedSessionFactoryWarnings.log +appender.unclosedSessionFactoryFile.layout.type=PatternLayout +appender.unclosedSessionFactoryFile.layout.pattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n + +rootLogger.level=info +rootLogger.appenderRef.stdout.ref=STDOUT + +logger.loader-plan.name=org.hibernate.loader.plan +#logger.loader-plan.level=trace +#logger.loader-plan.appenderRef.stdout-mdc.ref=stdout-mdc +#logger.loader-plan.additivity=false +logger.persister-walking.name=org.hibernate.persister.walking +#logger.persister-walking.level=trace +#logger.persister-walking.appenderRef.stdout-mdc.ref=stdout-mdc +#logger.persister-walking.additivity=false + +logger.hbm2ddl.name=org.hibernate.tool.hbm2ddl +logger.hbm2ddl.level=trace +logger.testing-cache.name=org.hibernate.testing.cache +logger.testing-cache.level=debug + +# SQL Logging - HHH-6833 +logger.sql.name=org.hibernate.SQL +logger.sql.level=debug + +logger.type-basic-binder.name=org.hibernate.type.descriptor.sql.BasicBinder +logger.type-basic-binder.level=trace +logger.type-basic-extractor.name=org.hibernate.type.descriptor.sql.BasicExtractor +logger.type-basic-extractor.level=trace + +logger.hql-internal-ast.name=org.hibernate.hql.internal.ast +logger.hql-internal-ast.level=debug + +logger.sql-ordering-antlr.name=org.hibernate.sql.ordering.antlr +logger.sql-ordering-antlr.level=debug + +logger.load-plan-impl.name=org.hibernate.loader.plan2.build.internal.LoadPlanImpl +logger.load-plan-impl.level=debug +logger.load-plan-tree-printer.name=org.hibernate.loader.plan2.build.spi.LoadPlanTreePrinter +logger.load-plan-tree-printer.level=debug +logger.entity-load-query-details.name=org.hibernate.loader.plan2.exec.spi.EntityLoadQueryDetails +logger.entity-load-query-details.level=debug + +logger.statistical-logging-session-event-listener.name=org.hibernate.engine.internal.StatisticalLoggingSessionEventListener +logger.statistical-logging-session-event-listener.level=info + +logger.model-binder.name=org.hibernate.boot.model.source.internal.hbm.ModelBinder +logger.model-binder.level=debug +logger.java-type-descriptor-registry.name=org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry +logger.java-type-descriptor-registry.level=debug + + +logger.merged-entity-copies.name=org.hibernate.event.internal.EntityCopyAllowedLoggedObserver +### When entity copy merge functionality is enabled using: +### hibernate.event.merge.entity_copy_observer=log, the following will +### provide information about merged entity copies. +#logger.merged-entity-copies.level=debug + +logger.test-class-metadata.name=org.hibernate.testing.junit4.TestClassMetadata +logger.test-class-metadata.level=info +logger.test-class-metadata.appenderRef.unclosedSessionFactoryFile.ref=unclosedSessionFactoryFile +logger.scanning-coordinator.name=org.hibernate.boot.model.process.internal.ScanningCoordinator +logger.scanning-coordinator.level=debug diff --git a/hibernate-c3p0/hibernate-c3p0.gradle b/hibernate-c3p0/hibernate-c3p0.gradle index d76872a461ed01ed6518d27353a74345af47a02a..a5ff50748e1e5489ab9c5937cb16a81cb62720b8 100644 --- a/hibernate-c3p0/hibernate-c3p0.gradle +++ b/hibernate-c3p0/hibernate-c3p0.gradle @@ -5,10 +5,10 @@ * See the lgpl.txt file in the root directory or . */ -apply from: rootProject.file( 'gradle/published-java-module.gradle' ) - description = 'Integration for c3p0 Connection pooling into Hibernate ORM' +apply from: rootProject.file( 'gradle/published-java-module.gradle' ) + dependencies { compile project( ':hibernate-core' ) compile( libraries.c3p0 ) diff --git a/hibernate-proxool/src/test/resources/log4j.properties b/hibernate-c3p0/src/test/resources/log4j2.properties similarity index 37% rename from hibernate-proxool/src/test/resources/log4j.properties rename to hibernate-c3p0/src/test/resources/log4j2.properties index 86c5d9be55e69736b44fb09006b75fbf84871d0e..d4da0b21a5e83f884097f1dbb2883e1f2c178e0f 100644 --- a/hibernate-proxool/src/test/resources/log4j.properties +++ b/hibernate-c3p0/src/test/resources/log4j2.properties @@ -4,13 +4,16 @@ # License: GNU Lesser General Public License (LGPL), version 2.1 or later. # See the lgpl.txt file in the root directory or . # -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.Target=System.out -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n +appender.stdout.type=Console +appender.stdout.name=STDOUT +appender.stdout.layout.type=PatternLayout +appender.stdout.layout.pattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n -log4j.rootLogger=info, stdout +rootLogger.level=info +rootLogger.appenderRef.stdout.ref=STDOUT -log4j.logger.org.hibernate.tool.hbm2ddl=debug -log4j.logger.org.hibernate.testing.cache=debug +logger.hbm2ddl.name=org.hibernate.tool.hbm2ddl +logger.hbm2ddl.level=debug +logger.testing-cache.name=org.hibernate.testing.cache +logger.testing-cache.level=debug diff --git a/hibernate-core/hibernate-core.gradle b/hibernate-core/hibernate-core.gradle index c3dbbd47281ea1e0f9e3044409f6a2c985aecbdc..44728f23ebc797b6b39f66ba1b00381115af9fdf 100644 --- a/hibernate-core/hibernate-core.gradle +++ b/hibernate-core/hibernate-core.gradle @@ -6,38 +6,52 @@ */ import org.apache.tools.ant.filters.ReplaceTokens +description = 'Hibernate\'s core ORM functionality' + apply from: rootProject.file( 'gradle/published-java-module.gradle' ) apply plugin: 'antlr' apply plugin: 'hibernate-matrix-testing' apply plugin: 'org.hibernate.build.gradle.xjc' -description = 'Hibernate\'s core ORM functionality' +ext { + jaxbTargetDir = file( "${buildDir}/generated-src/jaxb/main" ) +} -configurations { - hibernateJpaModelGenTool { - description = "Dependencies for running the Hibernate JPA Metamodel Generator AnnotationProcessor tool" +sourceSets.main { + java.srcDir project.jaxbTargetDir +} + +sourceSets { + // resources inherently exclude sources + test { + resources { + setSrcDirs( ['src/test/java','src/test/resources'] ) + } } + + testJavassist { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + } + } +} + +configurations { tests { description = 'Configuration for the produced test jar' } + + //Configures the compile and runtime configurations for our javassist tests + //and includes the dependencies of the test task. + testJavassistCompile.extendsFrom testCompile + testJavassistRuntime.extendsFrom testRuntime } dependencies { - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Java 9 ftw! - if ( JavaVersion.current().isJava9Compatible() ) { - xjc( 'com.sun.xml.bind:jaxb-impl:2.2.11' ) - xjc( 'org.glassfish.jaxb:jaxb-xjc:2.2.11' ) - xjc( 'org.jvnet.jaxb2_commons:jaxb2-basics:0.11.0' ) - xjc( 'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.11.0' ) - xjc( 'javax.activation:javax.activation-api:1.2.0' ) - xjc( 'javax.annotation:jsr250-api:1.0' ) - } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - compile( libraries.jpa ) - // Javassist is no longer the default enhancer but still required for other purposes, e.g. Scanning + // This can now be made provided compile( libraries.javassist ) // Could be made optional? compile( libraries.byteBuddy ) @@ -55,6 +69,15 @@ dependencies { compile( libraries.commons_annotations ) antlr( libraries.antlr ) + // JAXB + compile( libraries.jaxb_api ) + compile( libraries.jaxb_runtime ) + xjc( libraries.jaxb_runtime ) + xjc( libraries.jaxb_xjc ) + xjc( libraries.jaxb2_basics ) + xjc( libraries.jaxb2_basics_ant ) + xjc( libraries.activation ) + provided( libraries.jacc ) provided( libraries.validation ) provided( libraries.ant ) @@ -92,7 +115,7 @@ dependencies { testRuntime( libraries.weld ) testRuntime(libraries.wildfly_transaction_client) - testCompile( project( ':hibernate-jpamodelgen' ) ) + testAnnotationProcessor( project( ':hibernate-jpamodelgen' ) ) testCompile libraries.arquillian_junit_container testCompile libraries.arquillian_protocol_servlet @@ -102,60 +125,70 @@ dependencies { testCompile libraries.jboss_ejb_spec_jar testCompile libraries.jboss_annotation_spec_jar + + // Additional tests requiring Javassist + // folder in src/javassist/java + testJavassistCompile libraries.javassist } jar { manifest { - mainAttributes( 'Main-Class': 'org.hibernate.Version' ) - - instructionFirst 'Import-Package', - 'javax.security.auth;resolution:=optional', - 'javax.security.jacc;resolution:=optional', - 'javax.validation;resolution:=optional', - 'javax.validation.constraints;resolution:=optional', - 'javax.validation.groups;resolution:=optional', - 'javax.validation.metadata;resolution:=optional', - // TODO: Shouldn't have to explicitly list this, but the plugin - // generates it with a [1.0,2) version. - "javax.persistence;version=\"${project.jpaVersion.osgiName}\"", - // Temporarily support JTA 1.1 -- Karaf and other frameworks still - // use it. Without this, the plugin generates [1.2,2). - // build.gradle adds javax.transaction for all modules - 'javax.transaction.xa;version="[1.1,2)"', - // optionals - 'javax.management;resolution:=optional', - 'javax.naming.event;resolution:=optional', - 'javax.naming.spi;resolution:=optional', - 'org.apache.tools.ant;resolution:=optional', - 'org.apache.tools.ant.taskdefs;resolution:=optional', - 'org.apache.tools.ant.types;resolution:=optional', - '!javax.enterprise*', - 'javax.enterprise.context.spi;resolution:=optional', - 'javax.enterprise.inject.spi;resolution:=optional', - 'javax.inject;resolution:=optional', - 'net.bytebuddy.*;resolution:=optional' - -// // TODO: Uncomment once EntityManagerFactoryBuilderImpl no longer -// // uses ClassLoaderServiceImpl. -// instruction 'Export-Package', -// 'org.hibernate.boot.registry.classloading.internal', -// '*' + attributes( + 'Main-Class': 'org.hibernate.Version', + + // BND Plugin instructions (for OSGi): + 'Import-Package': [ + 'javax.security.auth;resolution:=optional', + // Make javax.security.jacc optional and do not reference a version range (because that's what we used to do) + 'javax.security.jacc;resolution:=optional;version=!', + // Make javax.validation optional and do not reference a version range (because that's what we used to do) + 'javax.validation;resolution:=optional;version=!', + 'javax.validation.constraints;resolution:=optional;version=!', + 'javax.validation.groups;resolution:=optional;version=!', + 'javax.validation.metadata;resolution:=optional;version=!', + // Make javax.enterprise optional and do not reference a version range (because that's what we used to do) + '!javax.enterprise*', + 'javax.enterprise.context.spi;resolution:=optional;version=!', + 'javax.enterprise.inject.spi;resolution:=optional;version=!', + // For JPA, we don't want to target the automatically generated range, but a specific version + "javax.persistence;version=\"${project.jpaVersion.osgiName}\"", + // optionals + 'javax.management;resolution:=optional', + 'javax.naming.event;resolution:=optional', + 'javax.naming.spi;resolution:=optional', + 'org.apache.tools.ant;resolution:=optional', + 'org.apache.tools.ant.taskdefs;resolution:=optional', + 'org.apache.tools.ant.types;resolution:=optional', + 'javax.inject;resolution:=optional', + 'net.bytebuddy.*;resolution:=optional', + // Remove JTA-related dependencies that weren't there in 5.3.0, + // but were added after the switch to a different OSGi plugin. + // The dependencies *are* there in 5.4.x, but marked as optional. + // We're only removing them here as an extra cautious step + // to preserve backwards compatibility. + '!org.objectweb.jonas_tm', + '!com.ibm.websphere.jtaextensions', + // We must specify the version explicitly to allow Karaf + // to use an older version of JAXB (the only one we can use in Karaf) + "javax.xml.bind.*;version=\"${project.jaxbApiVersionOsgiRange}\"", + // Temporarily support JTA 1.1 -- Karaf and other frameworks still + // use it. Without this, the plugin generates [1.2,2). + 'javax.transaction;version="[1.1,2)"', + // Also import every package referenced in the code + '*' + ].join( ',' ), + '-exportcontents': [ + // Legacy resource packages containing XSDs that were traditionally not exported + "!org.hibernate.xsd.cfg", + "!org.hibernate.xsd.mapping", + // TODO: Uncomment once EntityManagerFactoryBuilderImpl no longer uses ClassLoaderServiceImpl. + //'org.hibernate.boot.registry.classloading.internal', + "*;version=${project.version}" + ].join( ',' ), + ) } } -ext { - jaxbTargetDir = file( "${buildDir}/generated-src/jaxb/main" ) -} - -sourceSets.main { - java.srcDir project.jaxbTargetDir -} - -// resources inherently exclude sources -sourceSets.test.resources { - setSrcDirs( ['src/test/java','src/test/resources'] ) -} - //idea { // module { // sourceDirs += file( "${buildDir}/generated-src/antlr/main" ) @@ -201,7 +234,7 @@ task copyBundleResources (type: Copy) { processTestResources.dependsOn copyBundleResources task testJar(type: Jar, dependsOn: testClasses) { - classifier = 'test' + archiveClassifier.set( 'test' ) from sourceSets.test.output } @@ -227,9 +260,49 @@ test.dependsOn ':hibernate-orm-modules:prepareWildFlyForTests' test { systemProperty 'file.encoding', 'utf-8' - //Set the port-offset to match the offset in arquillian.xml - systemProperty 'jboss.socket.binding.port-offset', '137' + + if ( gradle.ext.javaVersions.test.launcher.asInt() >= 9 ) { + // See org.hibernate.boot.model.naming.NamingHelperTest.DefaultCharset.set + jvmArgs( ['--add-opens', 'java.base/java.nio.charset=ALL-UNNAMED'] ) + // Weld needs this to generate proxies + jvmArgs( ['--add-opens', 'java.base/java.security=ALL-UNNAMED'] ) + jvmArgs( ['--add-opens', 'java.base/java.lang=ALL-UNNAMED'] ) + } + beforeTest { descriptor -> //println "Starting test: " + descriptor } } + +//Create the task that runs the integration tests found from the +//configured source directory and uses the correct classpath. +task testJavassist(type: Test) { + testClassesDirs = sourceSets.testJavassist.output.classesDirs + classpath = sourceSets.testJavassist.runtimeClasspath + //If you want to ensure that integration tests are run every time when you invoke + //this task, uncomment the following line. + //outputs.upToDateWhen { false } + + if ( gradle.ext.javaToolchainEnabled ) { + // Configure version of Java tools + javaLauncher = javaToolchains.launcherFor { + languageVersion = gradle.ext.javaVersions.test.launcher + } + + // Configure JVM Options + jvmArgs( getProperty( 'toolchain.launcher.jvmargs' ).toString().split( ' ' ) ) + + // Display version of Java tools + doFirst { + logger.lifecycle "Testing javassist with '${javaLauncher.get().metadata.installationPath}'" + } + } + + if ( gradle.ext.javaVersions.test.launcher.asInt() >= 9 ) { + // Javassist needs this to generate proxies + jvmArgs( ['--add-opens', 'java.base/java.lang=ALL-UNNAMED'] ) + } +} + +check.dependsOn testJavassist +testJavassist.mustRunAfter test diff --git a/hibernate-core/src/main/antlr/hql-sql.g b/hibernate-core/src/main/antlr/hql-sql.g index 5265979d5eedb63e417a44e346e28caaa6f7829c..c079184dfbd48c97e95fb54f94bd4cbea48ce44c 100644 --- a/hibernate-core/src/main/antlr/hql-sql.g +++ b/hibernate-core/src/main/antlr/hql-sql.g @@ -244,6 +244,15 @@ tokens return dot; } + protected AST lookupFkRefSource(AST path) throws SemanticException { + if ( path.getType() == DOT ) { + return lookupProperty( path, true, isInSelect() ); + } + else { + return lookupNonQualifiedProperty( path ); + } + } + protected boolean isNonQualifiedPropertyRef(AST ident) { return false; } protected AST lookupNonQualifiedProperty(AST property) throws SemanticException { return property; } @@ -713,12 +722,15 @@ identifier ; addrExpr! [ boolean root ] - : #(d:DOT lhs:addrExprLhs rhs:propertyName ) { + : #(d:DOT lhs:addrExprLhs rhs:propertyName ) { // This gives lookupProperty() a chance to transform the tree // to process collection properties (.elements, etc). #addrExpr = #(#d, #lhs, #rhs); #addrExpr = lookupProperty(#addrExpr,root,false); } + | fk_ref:fkRef { + #addrExpr = #fk_ref; + } | #(i:INDEX_OP lhs2:addrExprLhs rhs2:expr [ null ]) { #addrExpr = #(#i, #lhs2, #rhs2); processIndex(#addrExpr); @@ -743,6 +755,12 @@ addrExpr! [ boolean root ] } ; +fkRef + : #( r:FK_REF p:propertyRef ) { + #p = lookupProperty( #p, false, isInSelect() ); + } + ; + addrExprLhs : addrExpr [ false ] ; @@ -764,8 +782,7 @@ propertyRef! #propertyRef = #(#d, #lhs, #rhs); #propertyRef = lookupProperty(#propertyRef,false,true); } - | - p:identifier { + | p:identifier { // In many cases, things other than property-refs are recognized // by this propertyRef rule. Some of those I have seen: // 1) select-clause from-aliases diff --git a/hibernate-core/src/main/antlr/hql.g b/hibernate-core/src/main/antlr/hql.g index ac9e2a4ac34669ee3f552c47387c2274e661a9c7..136b0ab816a09d4c2df3111faff4e6c739c61c2f 100644 --- a/hibernate-core/src/main/antlr/hql.g +++ b/hibernate-core/src/main/antlr/hql.g @@ -46,6 +46,7 @@ tokens EXISTS="exists"; FALSE="false"; FETCH="fetch"; + FK_REF; FROM="from"; FULL="full"; GROUP="group"; @@ -721,7 +722,8 @@ atom // level 0 - the basic element of an expression primaryExpression - : { validateSoftKeyword("function") && LA(2) == OPEN && LA(3) == QUOTED_STRING }? jpaFunctionSyntax + : { validateSoftKeyword("fk") && LA(2) == OPEN }? fkRefPath + | { validateSoftKeyword("function") && LA(2) == OPEN && LA(3) == QUOTED_STRING }? jpaFunctionSyntax | { validateSoftKeyword("cast") && LA(2) == OPEN }? castFunction | identPrimary ( options {greedy=true;} : DOT^ "class" )? | constant @@ -729,6 +731,12 @@ primaryExpression | OPEN! (expressionOrVector | subQuery) CLOSE! ; +fkRefPath! + : "fk" OPEN p:identPrimary CLOSE { + #fkRefPath = #( [FK_REF], #p ); + } + ; + jpaFunctionSyntax! : i:IDENT OPEN n:QUOTED_STRING (COMMA a:exprList)? CLOSE { final String functionName = unquote( #n.getText() ); diff --git a/hibernate-core/src/main/antlr/sql-gen.g b/hibernate-core/src/main/antlr/sql-gen.g index 40d48f034360bc9b88b5418f618b2d6dd15c097d..1a409c41e3cb3387476eb99182e6d8791184824d 100644 --- a/hibernate-core/src/main/antlr/sql-gen.g +++ b/hibernate-core/src/main/antlr/sql-gen.g @@ -500,6 +500,7 @@ parameter addrExpr : #(r:DOT . .) { out(r); } + | #(fk:FK_REF .) { out(fk); } | i:ALIAS_REF { out(i); } | j:INDEX_OP { out(j); } | v:RESULT_VARIABLE_REF { out(v); } diff --git a/hibernate-core/src/main/java/org/hibernate/Hibernate.java b/hibernate-core/src/main/java/org/hibernate/Hibernate.java index 6644d420f4b55679e84427e848a46a49aa443c69..c46f9d219f9a5303340b748c6f40489efd1f4bf7 100644 --- a/hibernate-core/src/main/java/org/hibernate/Hibernate.java +++ b/hibernate-core/src/main/java/org/hibernate/Hibernate.java @@ -8,6 +8,7 @@ import java.util.Iterator; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.HibernateIterator; @@ -63,6 +64,13 @@ public static void initialize(Object proxy) throws HibernateException { else if ( proxy instanceof PersistentCollection ) { ( (PersistentCollection) proxy ).forceInitialization(); } + else if ( proxy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) proxy; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + ( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( proxy, null ); + } + } } /** @@ -76,6 +84,13 @@ public static boolean isInitialized(Object proxy) { if ( proxy instanceof HibernateProxy ) { return !( (HibernateProxy) proxy ).getHibernateLazyInitializer().isUninitialized(); } + else if ( proxy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) proxy ).$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return false; + } + return true; + } else if ( proxy instanceof PersistentCollection ) { return ( (PersistentCollection) proxy ).wasInitialized(); } @@ -187,7 +202,10 @@ public static boolean isPropertyInitialized(Object proxy, String propertyName) { if ( entity instanceof PersistentAttributeInterceptable ) { PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); - if ( interceptor != null && interceptor instanceof LazyAttributeLoadingInterceptor ) { + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return false; + } + if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { return ( (LazyAttributeLoadingInterceptor) interceptor ).isAttributeLoaded( propertyName ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/Query.java b/hibernate-core/src/main/java/org/hibernate/Query.java index 6c68a3ab54379164da89a1ea72e8635eae8a74bf..5fbf556736e25f32dd7204bbb469077f422a2edd 100644 --- a/hibernate-core/src/main/java/org/hibernate/Query.java +++ b/hibernate-core/src/main/java/org/hibernate/Query.java @@ -135,7 +135,7 @@ default Query setHibernateFirstResult(int firstRow) { * @see #setMaxResults(int) (int) * @see #setHibernateMaxResults(int) (int) * - * @deprecated {@link #setMaxResults(int)} should be used instead. + * @deprecated {@link #getMaxResults()} should be used instead. */ @Deprecated default Integer getHibernateMaxResults() { diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index 36b334b46275a90dd01869b2db7466191b4a4ebf..b758a3e2be5a04232ab44d6a62d1c8813d538c8f 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -1143,9 +1143,6 @@ interface LockRequest { */ void addEventListeners(SessionEventListener... listeners); - @Override - org.hibernate.query.Query createQuery(String queryString); - @Override org.hibernate.query.Query createQuery(String queryString, Class resultType); @@ -1158,8 +1155,6 @@ interface LockRequest { @Override org.hibernate.query.Query createQuery(CriteriaDelete deleteQuery); - @Override - org.hibernate.query.Query getNamedQuery(String queryName); org.hibernate.query.Query createNamedQuery(String name, Class resultType); diff --git a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java index e92d7bdb872535bbb18c756973b0cd3efc90aafd..e5e49f95ea535ed7e1127cfbcd1e9dc635e44420 100644 --- a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java @@ -67,6 +67,12 @@ public interface SharedSessionContract extends QueryProducer, Serializable { */ Transaction getTransaction(); + @Override + org.hibernate.query.Query createQuery(String queryString); + + @Override + org.hibernate.query.Query getNamedQuery(String queryName); + /** * Gets a ProcedureCall based on a named template * diff --git a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java index 2263501c49fc3165853b6cff472c6330f36c36b0..8a15cf1f7577cc6dc9f3d32abc397dde807ae536 100755 --- a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java +++ b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java @@ -10,6 +10,8 @@ import java.io.Serializable; import java.sql.Connection; +import org.hibernate.query.NativeQuery; + /** * A command-oriented API for performing bulk operations against a database. *

@@ -170,4 +172,7 @@ public interface StatelessSession extends SharedSessionContract, AutoCloseable, */ @Deprecated Connection connection(); + + @Override + NativeQuery createSQLQuery(String queryString); } diff --git a/hibernate-core/src/main/java/org/hibernate/SynchronizeableQuery.java b/hibernate-core/src/main/java/org/hibernate/SynchronizeableQuery.java index da1206e8d7ca58bafbd8bbe69a861678bac6813c..fb1d4f7ec1a700692481dd630fcc92cfcb96646c 100644 --- a/hibernate-core/src/main/java/org/hibernate/SynchronizeableQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/SynchronizeableQuery.java @@ -15,10 +15,12 @@ * processed by auto-flush based on the table to which those entities are mapped and which are * determined to have pending state changes. * - * In a similar manner, these query spaces also affect how query result caching can recognize invalidated results. + * In a similar manner, these query spaces also affect how query result caching can recognize + * invalidated results. * * @author Steve Ebersole */ +@SuppressWarnings( { "unused", "UnusedReturnValue", "RedundantSuppression" } ) public interface SynchronizeableQuery { /** * Obtain the list of query spaces the query is synchronized on. @@ -36,6 +38,32 @@ public interface SynchronizeableQuery { */ SynchronizeableQuery addSynchronizedQuerySpace(String querySpace); + /** + * Adds one-or-more synchronized spaces + */ + default SynchronizeableQuery addSynchronizedQuerySpace(String... querySpaces) { + if ( querySpaces != null ) { + for ( int i = 0; i < querySpaces.length; i++ ) { + addSynchronizedQuerySpace( querySpaces[i] ); + } + } + return this; + } + + /** + * Adds a table expression as a query space. + */ + default SynchronizeableQuery addSynchronizedTable(String tableExpression) { + return addSynchronizedQuerySpace( tableExpression ); + } + + /** + * Adds one-or-more synchronized table expressions + */ + default SynchronizeableQuery addSynchronizedTable(String... tableExpressions) { + return addSynchronizedQuerySpace( tableExpressions ); + } + /** * Adds an entity name for (a) auto-flush checking and (b) query result cache invalidation checking. Same as * {@link #addSynchronizedQuerySpace} for all tables associated with the given entity. @@ -48,6 +76,18 @@ public interface SynchronizeableQuery { */ SynchronizeableQuery addSynchronizedEntityName(String entityName) throws MappingException; + /** + * Adds one-or-more entities (by name) whose tables should be added as synchronized spaces + */ + default SynchronizeableQuery addSynchronizedEntityName(String... entityNames) throws MappingException { + if ( entityNames != null ) { + for ( int i = 0; i < entityNames.length; i++ ) { + addSynchronizedEntityName( entityNames[i] ); + } + } + return this; + } + /** * Adds an entity for (a) auto-flush checking and (b) query result cache invalidation checking. Same as * {@link #addSynchronizedQuerySpace} for all tables associated with the given entity. @@ -58,5 +98,18 @@ public interface SynchronizeableQuery { * * @throws MappingException Indicates the given class could not be resolved as an entity */ + @SuppressWarnings( "rawtypes" ) SynchronizeableQuery addSynchronizedEntityClass(Class entityClass) throws MappingException; + + /** + * Adds one-or-more entities (by class) whose tables should be added as synchronized spaces + */ + default SynchronizeableQuery addSynchronizedEntityClass(Class... entityClasses) throws MappingException { + if ( entityClasses != null ) { + for ( int i = 0; i < entityClasses.length; i++ ) { + addSynchronizedEntityClass( entityClasses[i] ); + } + } + return this; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java index ad44c4b447a16ea9d9cc21b386ff34907b725568..e24854dc11f5fc3d81dc40d2ed38498dab7286ff 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java @@ -67,7 +67,7 @@ protected AbstractEntityInsertAction( * entity state. * @return the entity state. * - * @see {@link #nullifyTransientReferencesIfNotAlready} + * @see #nullifyTransientReferencesIfNotAlready */ public Object[] getState() { return state; @@ -106,12 +106,12 @@ public NonNullableTransientDependencies findNonNullableTransientEntities() { * called for a this object, so it can safely be called both when * the entity is made "managed" and when this action is executed. * - * @see {@link #makeEntityManaged() } + * @see #makeEntityManaged() */ protected final void nullifyTransientReferencesIfNotAlready() { if ( ! areTransientReferencesNullified ) { - new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), getSession() ) - .nullifyTransientReferences( getState(), getPersister().getPropertyTypes() ); + new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), getSession(), getPersister() ) + .nullifyTransientReferences( getState() ); new Nullability( getSession() ).checkNullability( getState(), getPersister(), false ); areTransientReferencesNullified = true; } diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/BulkOperationCleanupAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/BulkOperationCleanupAction.java index 97745d87688bc5d4f53d2de7e84366254f8b4a68..f6eca861399a1bdd7fe363f18eac51241091e849 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/BulkOperationCleanupAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/BulkOperationCleanupAction.java @@ -90,7 +90,7 @@ public BulkOperationCleanupAction(SharedSessionContractImplementor session, Quer } } - this.affectedTableSpaces = spacesList.toArray( new String[ spacesList.size() ] ); + this.affectedTableSpaces = spacesList.toArray( new String[ 0 ] ); } /** @@ -105,7 +105,7 @@ public BulkOperationCleanupAction(SharedSessionContractImplementor session, Quer * @param session The session to which this request is tied. * @param tableSpaces The table spaces. */ - @SuppressWarnings({ "unchecked" }) + @SuppressWarnings( { "unchecked", "rawtypes" } ) public BulkOperationCleanupAction(SharedSessionContractImplementor session, Set tableSpaces) { final LinkedHashSet spacesList = new LinkedHashSet<>(); spacesList.addAll( tableSpaces ); @@ -137,23 +137,26 @@ public BulkOperationCleanupAction(SharedSessionContractImplementor session, Set } } - this.affectedTableSpaces = spacesList.toArray( new String[ spacesList.size() ] ); + this.affectedTableSpaces = spacesList.toArray( new String[ 0 ] ); } /** - * Check to determine whether the table spaces reported by an entity - * persister match against the defined affected table spaces. + * Check whether we should consider an entity as affected by the query. This + * defines inclusion of the entity in the clean-up. * * @param affectedTableSpaces The table spaces reported to be affected by * the query. * @param checkTableSpaces The table spaces (from the entity persister) * to check against the affected table spaces. * - * @return True if there are affected table spaces and any of the incoming - * check table spaces occur in that set. + * @return Whether the entity should be considered affected + * + * @implNote An entity is considered to be affected if either (1) the affected table + * spaces are not known or (2) any of the incoming check table spaces occur + * in that set. */ - private boolean affectedEntity(Set affectedTableSpaces, Serializable[] checkTableSpaces) { + private boolean affectedEntity(Set affectedTableSpaces, Serializable[] checkTableSpaces) { if ( affectedTableSpaces == null || affectedTableSpaces.isEmpty() ) { return true; } @@ -178,25 +181,21 @@ public BeforeTransactionCompletionProcess getBeforeTransactionCompletionProcess( @Override public AfterTransactionCompletionProcess getAfterTransactionCompletionProcess() { - return new AfterTransactionCompletionProcess() { - @Override - public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) { - for ( EntityCleanup cleanup : entityCleanups ) { - cleanup.release(); - } - entityCleanups.clear(); - - for ( NaturalIdCleanup cleanup : naturalIdCleanups ) { - cleanup.release(); + return (success, session) -> { + for ( EntityCleanup cleanup : entityCleanups ) { + cleanup.release(); + } + entityCleanups.clear(); - } - entityCleanups.clear(); + for ( NaturalIdCleanup cleanup : naturalIdCleanups ) { + cleanup.release(); + } + naturalIdCleanups.clear(); - for ( CollectionCleanup cleanup : collectionCleanups ) { - cleanup.release(); - } - collectionCleanups.clear(); + for ( CollectionCleanup cleanup : collectionCleanups ) { + cleanup.release(); } + collectionCleanups.clear(); }; } diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionUpdateAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionUpdateAction.java index b8cb3c18ccf3d32d16a7ea16091f66f514d342a0..39264ee6b8582ad747a9b754456bd76849bf941d 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionUpdateAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/CollectionUpdateAction.java @@ -57,8 +57,11 @@ public void execute() throws HibernateException { preUpdate(); if ( !collection.wasInitialized() ) { - if ( !collection.hasQueuedOperations() ) { - throw new AssertionFailure( "no queued adds" ); + // If there were queued operations, they would have been processed + // and cleared by now. + // The collection should still be dirty. + if ( !collection.isDirty() ) { + throw new AssertionFailure( "collection is not dirty" ); } //do nothing - we only need to notify the cache... } diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityAction.java index 59bf7c049e76a569f1957abeb595b770ea4fa0ea..35d2ce984cfe315043711f6e8a775e29b9bf716a 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityAction.java @@ -12,7 +12,6 @@ import org.hibernate.action.spi.AfterTransactionCompletionProcess; import org.hibernate.action.spi.BeforeTransactionCompletionProcess; import org.hibernate.action.spi.Executable; -import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.service.spi.EventListenerRegistry; @@ -101,19 +100,7 @@ public String getEntityName() { */ public final Serializable getId() { if ( id instanceof DelayedPostInsertIdentifier ) { - final EntityEntry entry = session.getPersistenceContext().getEntry( instance ); - if ( entry == null ) { - if ( LOG.isDebugEnabled() ) { - LOG.debugf( - "Skipping action - the persistence context does not contain any entry for the entity [%s]. This may occur if an entity is created and then deleted in the same transaction/flush.", - instance - ); - } - // If an Entity is created and then deleted in the same Transaction, when Action#postDelete() calls this method the persistence context - // does not contain anymore an entry. - return null; - } - final Serializable eeId = entry.getId(); + final Serializable eeId = session.getPersistenceContext().getEntry( instance ).getId(); return eeId instanceof DelayedPostInsertIdentifier ? null : eeId; } return id; diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityIncrementVersionProcess.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityIncrementVersionProcess.java index 77bcaaceb3ba0e77df5d06c7bd9bef8c22dbf905..6db4a6e5b231c18856567a277becadb06447f173 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityIncrementVersionProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityIncrementVersionProcess.java @@ -19,17 +19,14 @@ */ public class EntityIncrementVersionProcess implements BeforeTransactionCompletionProcess { private final Object object; - private final EntityEntry entry; /** * Constructs an EntityIncrementVersionProcess for the given entity. * * @param object The entity instance - * @param entry The entity's EntityEntry reference */ - public EntityIncrementVersionProcess(Object object, EntityEntry entry) { + public EntityIncrementVersionProcess(Object object) { this.object = object; - this.entry = entry; } /** @@ -39,6 +36,12 @@ public EntityIncrementVersionProcess(Object object, EntityEntry entry) { */ @Override public void doBeforeTransactionCompletion(SessionImplementor session) { + final EntityEntry entry = session.getPersistenceContext().getEntry( object ); + // Don't increment version for an entity that is not in the PersistenceContext; + if ( entry == null ) { + return; + } + final EntityPersister persister = entry.getPersister(); final Object nextVersion = persister.forceVersionIncrement( entry.getId(), entry.getVersion(), session ); entry.forceLocked( object, nextVersion ); diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityUpdateAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityUpdateAction.java index fd9655369df89ad0ea203509664d6b2d65a009b6..a0e8c79f442a447dcf2d006a5736e3f06596b149 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityUpdateAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityUpdateAction.java @@ -9,6 +9,7 @@ import java.io.Serializable; import org.hibernate.AssertionFailure; +import org.hibernate.CacheMode; import org.hibernate.HibernateException; import org.hibernate.cache.CacheException; import org.hibernate.cache.spi.access.EntityDataAccess; @@ -186,8 +187,8 @@ public void execute() throws HibernateException { } if ( persister.canWriteToCache() ) { - if ( persister.isCacheInvalidationRequired() || entry.getStatus()!= Status.MANAGED ) { - persister.getCacheAccessStrategy().remove( session, ck); + if ( isCacheInvalidationRequired( persister, session ) || entry.getStatus() != Status.MANAGED ) { + persister.getCacheAccessStrategy().remove( session, ck ); } else if ( session.getCacheMode().isPutEnabled() ) { //TODO: inefficient if that cache is just going to ignore the updated state! @@ -219,6 +220,13 @@ else if ( session.getCacheMode().isPutEnabled() ) { } } + private static boolean isCacheInvalidationRequired( + EntityPersister persister, + SharedSessionContractImplementor session) { + // the cache has to be invalidated when CacheMode is equal to GET or IGNOREgfa + return persister.isCacheInvalidationRequired() || session.getCacheMode() == CacheMode.GET || session.getCacheMode() == CacheMode.IGNORE; + } + private boolean cacheUpdate(EntityPersister persister, Object previousVersion, Object ck) { final SharedSessionContractImplementor session = getSession(); try { diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityVerifyVersionProcess.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityVerifyVersionProcess.java index de813de3fd131ff60e94fa448992a6b82b05c47e..28ba3195e52e5ffa161eff126a38dbfc70b16de3 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityVerifyVersionProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityVerifyVersionProcess.java @@ -21,28 +21,25 @@ */ public class EntityVerifyVersionProcess implements BeforeTransactionCompletionProcess { private final Object object; - private final EntityEntry entry; /** * Constructs an EntityVerifyVersionProcess * * @param object The entity instance - * @param entry The entity's referenced EntityEntry */ - public EntityVerifyVersionProcess(Object object, EntityEntry entry) { + public EntityVerifyVersionProcess(Object object) { this.object = object; - this.entry = entry; } @Override public void doBeforeTransactionCompletion(SessionImplementor session) { - final EntityPersister persister = entry.getPersister(); - - if ( !entry.isExistsInDatabase() ) { - // HHH-9419: We cannot check for a version of an entry we ourselves deleted + final EntityEntry entry = session.getPersistenceContext().getEntry( object ); + // Don't check version for an entity that is not in the PersistenceContext; + if ( entry == null ) { return; } + final EntityPersister persister = entry.getPersister(); final Object latestVersion = persister.getCurrentVersion( entry.getId(), session ); if ( !entry.getVersion().equals( latestVersion ) ) { throw new OptimisticLockException( diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/QueuedOperationCollectionAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/QueuedOperationCollectionAction.java index 4abf3efb24d398e069239ac2d8f38b57cfe465c3..532b12e377c1397915245bf60b7c0c2c24283c17 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/QueuedOperationCollectionAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/QueuedOperationCollectionAction.java @@ -9,7 +9,9 @@ import java.io.Serializable; import org.hibernate.HibernateException; +import org.hibernate.collection.internal.AbstractPersistentCollection; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.collection.CollectionPersister; @@ -40,6 +42,21 @@ public QueuedOperationCollectionAction( @Override public void execute() throws HibernateException { + // this QueuedOperationCollectionAction has to be executed before any other + // CollectionAction involving the same collection. + getPersister().processQueuedOps( getCollection(), getKey(), getSession() ); + + // TODO: It would be nice if this could be done safely by CollectionPersister#processQueuedOps; + // Can't change the SPI to do this though. + ((AbstractPersistentCollection) getCollection() ).clearOperationQueue(); + + // The other CollectionAction types call CollectionEntry#afterAction, which + // clears the dirty flag. We don't want to call CollectionEntry#afterAction unless + // there is no other CollectionAction that will be executed on the same collection. + final CollectionEntry ce = getSession().getPersistenceContext().getCollectionEntry( getCollection() ); + if ( !ce.isDoremove() && !ce.isDoupdate() && !ce.isDorecreate() ) { + ce.afterAction( getCollection() ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/BatchSize.java b/hibernate-core/src/main/java/org/hibernate/annotations/BatchSize.java index 56683f72168038c2451b655c1b3d3477a6a33f68..164ddc36aef6fad9be4903b404b831a6d4335bdd 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/BatchSize.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/BatchSize.java @@ -16,20 +16,20 @@ /** * Defines size for batch loading of collections or lazy entities. For example... - *

- *     @Entity
- *     @BatchSize(size=100)
- *     class Product {
- *         ...
- *     }
- * 
+ *
{@code
+ * @Entity
+ * @BatchSize(size=100)
+ * class Product {
+ *     ...
+ * }
+ * }
* will initialize up to 100 lazy Product entity proxies at a time. * - *
- *     	@OneToMany
- *     	@BatchSize(size = 5) /
- *     	Set getProducts() { ... };
- * 
+ *
{@code
+ * @OneToMany
+ * @BatchSize(size = 5) /
+ * Set getProducts() { ... };
+ * }
* will initialize up to 5 lazy collections of products at a time * * @author Emmanuel Bernard diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/CreationTimestamp.java b/hibernate-core/src/main/java/org/hibernate/annotations/CreationTimestamp.java index 9b5405f1de9202e226f9be1b26419db688d4451f..b780103ee0734e7f86186d521349d621414a3bab 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/CreationTimestamp.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/CreationTimestamp.java @@ -6,10 +6,14 @@ */ package org.hibernate.annotations; -import org.hibernate.tuple.CreationTimestampGeneration; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.tuple.CreationTimestampGeneration; /** * Marks a property as the creation timestamp of the containing entity. The property value will be set to the current @@ -38,5 +42,6 @@ */ @ValueGenerationType(generatedBy = CreationTimestampGeneration.class) @Retention(RetentionPolicy.RUNTIME) +@Target({ FIELD, METHOD }) public @interface CreationTimestamp { } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/NamedNativeQuery.java b/hibernate-core/src/main/java/org/hibernate/annotations/NamedNativeQuery.java index 8d495c63ec82fe636decd876ac99ba5f082bfabc..6fb65dd5c9426e50e89d6c9f4ea20c9d419a50cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/NamedNativeQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/NamedNativeQuery.java @@ -89,4 +89,11 @@ * Whether the results should be read-only. Default is {@code false}. */ boolean readOnly() default false; + + /** + * The query spaces to apply for the query. + * + * @see org.hibernate.SynchronizeableQuery + */ + String[] querySpaces() default {}; } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/QueryHints.java b/hibernate-core/src/main/java/org/hibernate/annotations/QueryHints.java index 4032b8829214a94d255cc6d0af86659e4dd1c770..e7ceb14f895695710f102914c3f9273aad30334d 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/QueryHints.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/QueryHints.java @@ -137,4 +137,17 @@ private QueryHints() { */ public static final String PASS_DISTINCT_THROUGH = "hibernate.query.passDistinctThrough"; + /** + * Hint for specifying query spaces to be applied to a native (SQL) query. + * + * Passed value can be any of:
    + *
  • List of the spaces
  • + *
  • array of the spaces
  • + *
  • String "whitespace"-separated list of the spaces
  • + *
+ * + * @see org.hibernate.SynchronizeableQuery + */ + public static final String NATIVE_SPACES = "org.hibernate.query.native.spaces"; + } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Sort.java b/hibernate-core/src/main/java/org/hibernate/annotations/Sort.java index 5e700d472a51bdbc8c2ed578836c75ac6e0d0b53..71d908724a6738ce4536343c8cdfaac0dad1d8bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Sort.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Sort.java @@ -34,7 +34,7 @@ /** * Specifies the comparator to use. Only valid when {@link #type} specifies {@link SortType#COMPARATOR}. * - * TODO find a way to use Class -> see HHH-8164 + * TODO find a way to use {@code Class} -> see HHH-8164 */ Class comparator() default void.class; } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/UpdateTimestamp.java b/hibernate-core/src/main/java/org/hibernate/annotations/UpdateTimestamp.java index c1635886da956488c613592fa5a9bfdced176424..7e442f9e1c14cdecb102e1f60c0d8783d114d450 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/UpdateTimestamp.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/UpdateTimestamp.java @@ -6,10 +6,14 @@ */ package org.hibernate.annotations; -import org.hibernate.tuple.UpdateTimestampGeneration; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.tuple.UpdateTimestampGeneration; /** * Marks a property as the update timestamp of the containing entity. The property value will be set to the current VM @@ -38,5 +42,6 @@ */ @ValueGenerationType(generatedBy = UpdateTimestampGeneration.class) @Retention(RetentionPolicy.RUNTIME) +@Target({ FIELD, METHOD }) public @interface UpdateTimestamp { } 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 f2e4f2f696b27b86e5ff33b30e9dfd3bcfbbafaf..f94171998161ffcddacf1b64ce9cbd330397eea6 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 @@ -24,6 +24,7 @@ import org.hibernate.boot.jaxb.cfg.spi.JaxbCfgHibernateConfiguration; import org.hibernate.boot.jaxb.cfg.spi.JaxbCfgMappingReferenceType; import org.hibernate.event.spi.EventType; +import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.secure.spi.GrantedPermission; import org.hibernate.secure.spi.JaccPermissionDeclarations; @@ -47,7 +48,7 @@ public class LoadedConfig { private List mappingReferences; private Map> eventListenerMap; - private LoadedConfig(String sessionFactoryName) { + public LoadedConfig(String sessionFactoryName) { this.sessionFactoryName = sessionFactoryName; } @@ -59,10 +60,18 @@ public Map getConfigurationValues() { return configurationValues; } + /** + * @deprecated Support for JACC will be removed in 6.0 + */ + @Deprecated public Map getJaccPermissionsByContextId() { return jaccPermissionsByContextId; } + /** + * @deprecated Support for JACC will be removed in 6.0 + */ + @Deprecated public JaccPermissionDeclarations getJaccPermissions(String jaccContextId) { return jaccPermissionsByContextId.get( jaccContextId ); } @@ -102,11 +111,11 @@ public static LoadedConfig consume(JaxbCfgHibernateConfiguration jaxbCfg) { cfg.addCacheRegionDefinition( parseCacheRegionDefinition( cacheDeclaration ) ); } - if ( jaxbCfg.getSecurity() != null ) { + if ( jaxbCfg.getSecurity() != null && ! jaxbCfg.getSecurity().getGrant().isEmpty() ) { + DeprecationLogger.DEPRECATION_LOGGER.deprecatedJaccCfgXmlSettings(); for ( JaxbCfgHibernateConfiguration.JaxbCfgSecurity.JaxbCfgGrant grant : jaxbCfg.getSecurity().getGrant() ) { final JaccPermissionDeclarations jaccPermissions = cfg.getOrCreateJaccPermissions( - jaxbCfg.getSecurity() - .getContext() + jaxbCfg.getSecurity().getContext() ); jaccPermissions.addPermissionDeclaration( new GrantedPermission( @@ -259,7 +268,7 @@ public void merge(LoadedConfig incoming) { } @SuppressWarnings("unchecked") - private void addConfigurationValues(Map configurationValues) { + protected void addConfigurationValues(Map configurationValues) { if ( configurationValues == null ) { return; } 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 815eaa3808557fcdd96295053c6f1b6d6f75c6a9..4295508fa88bf61dd4748d201ff57e0955144573 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 @@ -1780,7 +1780,7 @@ private void processFkSecondPassesInOrder() { *

* See ANN-722 and ANN-730 * - * @param orderedFkSecondPasses The list containing the FkSecondPass instances ready + * @param orderedFkSecondPasses The list containing the FkSecondPass instances ready * for processing. * @param isADependencyOf Our lookup data structure to determine dependencies between tables * @param startTable Table name to start recursive algorithm. 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 2bae14fb356d6d077ace5634238154fdde609604..de2b94b645735d84b01f2e07091ef6881d6abf50 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 @@ -47,6 +47,7 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; import org.hibernate.id.uuid.LocalObjectUuidHelper; +import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.jpa.spi.JpaCompliance; @@ -62,9 +63,8 @@ import org.hibernate.tuple.entity.EntityTuplizer; import org.hibernate.tuple.entity.EntityTuplizerFactory; -import org.jboss.logging.Logger; - import static org.hibernate.cfg.AvailableSettings.ACQUIRE_CONNECTIONS; +import static org.hibernate.cfg.AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY; import static org.hibernate.cfg.AvailableSettings.ALLOW_JTA_TRANSACTION_ACCESS; import static org.hibernate.cfg.AvailableSettings.ALLOW_REFRESH_DETACHED_ENTITY; import static org.hibernate.cfg.AvailableSettings.ALLOW_UPDATE_OUTSIDE_TRANSACTION; @@ -97,6 +97,7 @@ import static org.hibernate.cfg.AvailableSettings.LOG_SESSION_METRICS; import static org.hibernate.cfg.AvailableSettings.MAX_FETCH_DEPTH; import static org.hibernate.cfg.AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER; +import static org.hibernate.cfg.AvailableSettings.NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE; import static org.hibernate.cfg.AvailableSettings.ORDER_INSERTS; import static org.hibernate.cfg.AvailableSettings.ORDER_UPDATES; import static org.hibernate.cfg.AvailableSettings.PREFER_USER_TRANSACTION; @@ -123,6 +124,7 @@ import static org.hibernate.cfg.AvailableSettings.VALIDATE_QUERY_PARAMETERS; import static org.hibernate.cfg.AvailableSettings.WRAP_RESULT_SETS; import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN; +import static org.hibernate.internal.CoreLogging.messageLogger; import static org.hibernate.jpa.AvailableSettings.DISCARD_PC_ON_CLOSE; /** @@ -137,7 +139,7 @@ */ @SuppressWarnings("WeakerAccess") public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { - private static final Logger log = Logger.getLogger( SessionFactoryOptionsBuilder.class ); + private static final CoreMessageLogger log = messageLogger( SessionFactoryOptionsBuilder.class ); private final String uuid = LocalObjectUuidHelper.generateLocalObjectUuid(); private final StandardServiceRegistry serviceRegistry; @@ -190,6 +192,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private NullPrecedence defaultNullPrecedence; private boolean orderUpdatesEnabled; private boolean orderInsertsEnabled; + private boolean enhancementAsProxyEnabled; // multi-tenancy @@ -239,6 +242,9 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private boolean failOnPaginationOverCollectionFetchEnabled; private boolean inClauseParameterPaddingEnabled; + private boolean nativeExceptionHandling51Compliance; + + @SuppressWarnings({"WeakerAccess", "deprecation"}) public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, BootstrapContext context) { this.serviceRegistry = serviceRegistry; @@ -335,6 +341,7 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo this.defaultNullPrecedence = NullPrecedence.parse( defaultNullPrecedence ); this.orderUpdatesEnabled = ConfigurationHelper.getBoolean( ORDER_UPDATES, configurationSettings ); this.orderInsertsEnabled = ConfigurationHelper.getBoolean( ORDER_INSERTS, configurationSettings ); + this.enhancementAsProxyEnabled = ConfigurationHelper.getBoolean( ALLOW_ENHANCEMENT_AS_PROXY, configurationSettings ); this.jtaTrackByThread = cfgService.getSetting( JTA_TRACK_BY_THREAD, BOOLEAN, true ); @@ -490,7 +497,7 @@ else if ( jdbcTimeZoneValue != null ) { ); this.immutableEntityUpdateQueryHandlingMode = ImmutableEntityUpdateQueryHandlingMode.interpret( - configurationSettings.get( IMMUTABLE_ENTITY_UPDATE_QUERY_HANDLING_MODE ) + configurationSettings.get( IMMUTABLE_ENTITY_UPDATE_QUERY_HANDLING_MODE ) ); this.inClauseParameterPaddingEnabled = ConfigurationHelper.getBoolean( @@ -498,6 +505,16 @@ else if ( jdbcTimeZoneValue != null ) { configurationSettings, false ); + + this.nativeExceptionHandling51Compliance = ConfigurationHelper.getBoolean( + NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE, + configurationSettings, + false + ); + if ( context.isJpaBootstrap() && nativeExceptionHandling51Compliance ) { + log.nativeExceptionHandling51ComplianceJpaBootstrapping(); + this.nativeExceptionHandling51Compliance = false; + } } @SuppressWarnings("deprecation") @@ -1011,6 +1028,16 @@ public JpaCompliance getJpaCompliance() { return jpaCompliance; } + @Override + public boolean nativeExceptionHandling51Compliance() { + return nativeExceptionHandling51Compliance; + } + + @Override + public boolean isEnhancementAsProxyEnabled() { + return enhancementAsProxyEnabled; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // In-flight mutation access diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java index 537c348ffccbca6b98af9c9857a0d6584cfca1d5..89a749a2d1546262aaf3f190949b66b0185778c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java @@ -42,6 +42,12 @@ public Object resolveEntity(String publicID, String systemID, String baseURI, St else if ( JPA_XSD_MAPPING.matches( namespace ) ) { return openUrlStream( JPA_XSD_MAPPING.getMappedLocalUrl() ); } + else if ( PERSISTENCE_ORM_XSD_MAPPING.matches( namespace ) ) { + return openUrlStream( PERSISTENCE_ORM_XSD_MAPPING.getMappedLocalUrl() ); + } + else if ( PERSISTENCE_ORM_XSD_MAPPING2.matches( namespace ) ) { + return openUrlStream( PERSISTENCE_ORM_XSD_MAPPING2.getMappedLocalUrl() ); + } else if ( HBM_XSD_MAPPING.matches( namespace ) ) { return openUrlStream( HBM_XSD_MAPPING.getMappedLocalUrl() ); } @@ -152,7 +158,23 @@ private InputStream resolveInLocalNamespace(String path) { "http://xmlns.jcp.org/xml/ns/persistence/orm", "org/hibernate/jpa/orm_2_1.xsd" ); + + /** + * Maps the namespace for the orm.xml xsd for Jakarta Persistence 2.2 + */ + public static final NamespaceSchemaMapping PERSISTENCE_ORM_XSD_MAPPING = new NamespaceSchemaMapping( + "http://xmlns.jcp.org/xml/ns/persistence/orm", + "org/hibernate/jpa/orm_2_2.xsd" + ); + /** + * Maps the namespace for the orm.xml xsd for Jakarta Persistence 3.0 + */ + public static final NamespaceSchemaMapping PERSISTENCE_ORM_XSD_MAPPING2 = new NamespaceSchemaMapping( + "https://jakarta.ee/xml/ns/persistence/orm", + "org/hibernate/jpa/orm_3_0.xsd" + ); + public static final NamespaceSchemaMapping HBM_XSD_MAPPING = new NamespaceSchemaMapping( "http://www.hibernate.org/xsd/orm/hbm", "org/hibernate/xsd/mapping/legacy-mapping-4.0.xsd" diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/NamedAuxiliaryDatabaseObject.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/NamedAuxiliaryDatabaseObject.java index 887300e3d6a2e4813ce54a48981eeea51f729a8e..9d9eb11c575c86d3dbe7cba487a1b136da56d43a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/NamedAuxiliaryDatabaseObject.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/NamedAuxiliaryDatabaseObject.java @@ -8,6 +8,8 @@ import java.util.Set; +import org.hibernate.boot.model.naming.Identifier; + /** * Mainly this is used to support legacy sequence exporting. * @@ -42,6 +44,10 @@ public NamedAuxiliaryDatabaseObject( @Override public String getExportIdentifier() { - return name; + return new QualifiedNameImpl( + Identifier.toIdentifier( getCatalogName() ), + Identifier.toIdentifier( getSchemaName() ), + Identifier.toIdentifier( name ) + ).render(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java index edcaa565fc556da4436229eb44f57fdab61dacd8..df59ef7466f753a09ab22f22a48385a6148697b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java @@ -93,6 +93,14 @@ public String[] sqlDropStrings(Dialect dialect) { return copy; } + protected String getCatalogName() { + return catalogName; + } + + protected String getSchemaName() { + return schemaName; + } + private String injectCatalogAndSchema(String ddlString) { String rtn = StringHelper.replace( ddlString, CATALOG_NAME_PLACEHOLDER, catalogName == null ? "" : catalogName ); rtn = StringHelper.replace( rtn, SCHEMA_NAME_PLACEHOLDER, schemaName == null ? "" : schemaName ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/FilterSourceImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/FilterSourceImpl.java index 6569d928abae37d5c7e4fbc3969d064dedd0dfb6..ecdbe1d3f5f2bb122538063c41f0e37a48ec9072 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/FilterSourceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/FilterSourceImpl.java @@ -14,6 +14,7 @@ import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmFilterAliasMappingType; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmFilterType; import org.hibernate.boot.model.source.spi.FilterSource; +import org.hibernate.internal.util.NullnessHelper; import org.hibernate.internal.util.StringHelper; /** @@ -63,7 +64,7 @@ else if ( StringHelper.isNotEmpty( aliasMapping.getEntity() ) ) { } } - this.condition = Helper.coalesce( conditionContent, conditionAttribute ); + this.condition = NullnessHelper.coalesce( conditionContent, conditionAttribute ); this.autoAliasInjection = StringHelper.isNotEmpty( explicitAutoAliasInjectionSetting ) ? Boolean.valueOf( explicitAutoAliasInjectionSetting ) : true; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/Helper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/Helper.java index a4249a86ab467a3b6ffc7686e894c2876a01b00b..50a55e4e5266b333f93c800c8bd3d0d890cb21b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/Helper.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/Helper.java @@ -123,33 +123,6 @@ public static Map extractParameters(List Generic type of values to coalesce - * - * @return The first non-empty value, or null if all values were empty - */ - public static T coalesce(T... values) { - if ( values == null ) { - return null; - } - for ( T value : values ) { - if ( value != null ) { - if ( String.class.isInstance( value ) ) { - if ( StringHelper.isNotEmpty( (String) value ) ) { - return value; - } - } - else { - return value; - } - } - } - return null; - } - static ToolingHintContext collectToolingHints( ToolingHintContext baseline, ToolingHintContainer toolingHintContainer) { 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 b79be28cbbf178dfa5e9e019849e54d8fca0001b..22409edd8bbfc814c9fa912640451b73fb28cec1 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 @@ -92,10 +92,12 @@ import org.hibernate.boot.spi.InFlightMetadataCollector.EntityTableXref; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.NaturalIdUniqueKeyBinder; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.FkSecondPass; import org.hibernate.cfg.SecondPass; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; +import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.FilterDefinition; import org.hibernate.id.PersistentIdentifierGenerator; @@ -104,6 +106,7 @@ import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.loader.PropertyPath; import org.hibernate.mapping.Any; import org.hibernate.mapping.Array; @@ -521,6 +524,7 @@ private void bindDiscriminatorSubclassEntities( PersistentClass superEntityDescriptor) { for ( IdentifiableTypeSource subType : entitySource.getSubTypes() ) { final SingleTableSubclass subEntityDescriptor = new SingleTableSubclass( superEntityDescriptor, metadataBuildingContext ); + subEntityDescriptor.setCached( superEntityDescriptor.isCached() ); bindDiscriminatorSubclassEntity( (SubclassEntitySourceImpl) subType, subEntityDescriptor ); superEntityDescriptor.addSubclass( subEntityDescriptor ); entitySource.getLocalMetadataBuildingContext().getMetadataCollector().addEntityBinding( subEntityDescriptor ); @@ -579,6 +583,7 @@ private void bindJoinedSubclassEntities( PersistentClass superEntityDescriptor) { for ( IdentifiableTypeSource subType : entitySource.getSubTypes() ) { final JoinedSubclass subEntityDescriptor = new JoinedSubclass( superEntityDescriptor, metadataBuildingContext ); + subEntityDescriptor.setCached( superEntityDescriptor.isCached() ); bindJoinedSubclassEntity( (JoinedSubclassEntitySourceImpl) subType, subEntityDescriptor ); superEntityDescriptor.addSubclass( subEntityDescriptor ); entitySource.getLocalMetadataBuildingContext().getMetadataCollector().addEntityBinding( subEntityDescriptor ); @@ -655,6 +660,7 @@ private void bindUnionSubclassEntities( PersistentClass superEntityDescriptor) { for ( IdentifiableTypeSource subType : entitySource.getSubTypes() ) { final UnionSubclass subEntityDescriptor = new UnionSubclass( superEntityDescriptor, metadataBuildingContext ); + subEntityDescriptor.setCached( superEntityDescriptor.isCached() ); bindUnionSubclassEntity( (SubclassEntitySourceImpl) subType, subEntityDescriptor ); superEntityDescriptor.addSubclass( subEntityDescriptor ); entitySource.getLocalMetadataBuildingContext().getMetadataCollector().addEntityBinding( subEntityDescriptor ); @@ -1527,7 +1533,6 @@ private void bindCollectionMetadata(MappingDocument mappingDocument, PluralAttri binding.getSynchronizedTables().add( name ); } - binding.setWhere( source.getWhere() ); binding.setLoaderName( source.getCustomLoaderName() ); if ( source.getCustomSqlInsert() != null ) { binding.setCustomSQLInsert( @@ -3387,6 +3392,11 @@ protected void bindCollectionIndex() { } protected void bindCollectionElement() { + log.debugf( + "Binding [%s] element type for a [%s]", + getPluralAttributeSource().getElementSource().getNature(), + getPluralAttributeSource().getNature() + ); if ( getPluralAttributeSource().getElementSource() instanceof PluralAttributeElementSourceBasic ) { final PluralAttributeElementSourceBasic elementSource = (PluralAttributeElementSourceBasic) getPluralAttributeSource().getElementSource(); @@ -3415,6 +3425,10 @@ public Identifier determineImplicitName(LocalMetadataBuildingContext context) { ); getCollectionBinding().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., + getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); } else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttributeElementSourceEmbedded ) { final PluralAttributeElementSourceEmbedded elementSource = @@ -3436,6 +3450,10 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu ); getCollectionBinding().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., + getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); } else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttributeElementSourceOneToMany ) { final PluralAttributeElementSourceOneToMany elementSource = @@ -3448,9 +3466,28 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu final PersistentClass referencedEntityBinding = mappingDocument.getMetadataCollector() .getEntityBinding( elementSource.getReferencedEntityName() ); + + if ( useEntityWhereClauseForCollections() ) { + // For a one-to-many association, there are 2 possible sources of "where" clauses that apply + // to the associated entity table: + // 1) from the associated entity mapping; i.e., + // 2) from the collection mapping; e.g., + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (which is the associated entity table for a one-to-many association). + collectionBinding.setWhere( + StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty( + referencedEntityBinding.getWhere(), + getPluralAttributeSource().getWhere() + ) + ); + } + else { + // ignore entity's where clause + collectionBinding.setWhere( getPluralAttributeSource().getWhere() ); + } + elementBinding.setReferencedEntityName( referencedEntityBinding.getEntityName() ); elementBinding.setAssociatedClass( referencedEntityBinding ); - elementBinding.setIgnoreNotFound( elementSource.isIgnoreNotFound() ); } else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttributeElementSourceManyToMany ) { @@ -3469,12 +3506,17 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu new RelationalObjectBinder.ColumnNamingDelegate() { @Override public Identifier determineImplicitName(final LocalMetadataBuildingContext context) { - return context.getMetadataCollector().getDatabase().toIdentifier( Collection.DEFAULT_ELEMENT_COLUMN_NAME ); + return context.getMetadataCollector() + .getDatabase() + .toIdentifier( Collection.DEFAULT_ELEMENT_COLUMN_NAME ); } } ); - elementBinding.setLazy( elementSource.getFetchCharacteristics().getFetchTiming() != FetchTiming.IMMEDIATE ); + elementBinding.setLazy( + elementSource.getFetchCharacteristics() + .getFetchTiming() != FetchTiming.IMMEDIATE + ); elementBinding.setFetchMode( elementSource.getFetchCharacteristics().getFetchStyle() == FetchStyle.SELECT ? FetchMode.SELECT @@ -3494,7 +3536,34 @@ public Identifier determineImplicitName(final LocalMetadataBuildingContext conte getCollectionBinding().setElement( elementBinding ); - getCollectionBinding().setManyToManyWhere( elementSource.getWhere() ); + final PersistentClass referencedEntityBinding = mappingDocument.getMetadataCollector().getEntityBinding( + elementSource.getReferencedEntityName() + ); + + // 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., + getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); + + if ( useEntityWhereClauseForCollections() ) { + // For a many-to-many association, there are 2 possible sources of "where" clauses that apply + // to the associated entity table (not the join table): + // 1) from the associated entity mapping; i.e., + // 2) from the many-to-many mapping; i.e + // Collection#setManytoManyWhere is used to set the "where" clause that applies to + // to the many-to-many associated entity table (not the join table). + getCollectionBinding().setManyToManyWhere( + StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty( + referencedEntityBinding.getWhere(), + elementSource.getWhere() + ) + ); + } + else { + // ignore entity's where clause + getCollectionBinding().setManyToManyWhere( elementSource.getWhere() ); + } + getCollectionBinding().setManyToManyOrdering( elementSource.getOrder() ); if ( !CollectionHelper.isEmpty( elementSource.getFilterSources() ) @@ -3568,10 +3637,26 @@ else if ( getPluralAttributeSource().getElementSource() instanceof PluralAttribu ); getCollectionBinding().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., + getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() ); } } } + private boolean useEntityWhereClauseForCollections() { + return ConfigurationHelper.getBoolean( + AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, + metadataBuildingContext + .getBuildingOptions() + .getServiceRegistry() + .getService( ConfigurationService.class ) + .getSettings(), + true + ); + } + private class PluralAttributeListSecondPass extends AbstractPluralAttributeSecondPass { public PluralAttributeListSecondPass( MappingDocument sourceDocument, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ResultSetMappingBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ResultSetMappingBinder.java index eb88399059b7265c5258124ccfa8f957f37845a4..b33e1438a42dd218580606256d1f193e582676fb 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ResultSetMappingBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ResultSetMappingBinder.java @@ -214,20 +214,23 @@ public static NativeSQLQueryReturn extractReturnDescription( JaxbHbmNativeQueryCollectionLoadReturnType rtnSource, HbmLocalMetadataBuildingContext context, int queryReturnPosition) { - final int dot = rtnSource.getRole().lastIndexOf( '.' ); - if ( dot == -1 ) { - throw new MappingException( - String.format( - Locale.ENGLISH, - "Collection attribute for sql query return [%s] not formatted correctly {OwnerClassName.propertyName}", - rtnSource.getAlias() - ), - context.getOrigin() - ); + PersistentClass entityBinding = null; + int dot = rtnSource.getRole().length(); + while ( entityBinding == null ) { + dot = rtnSource.getRole().lastIndexOf( '.', dot - 1); + if ( dot == -1 ) { + throw new MappingException( + String.format( + Locale.ENGLISH, + "Collection attribute for sql query return [%s] not formatted correctly {OwnerClassName.propertyName}", + rtnSource.getAlias() + ), + context.getOrigin() + ); + } + entityBinding = context.findEntityBinding( null, rtnSource.getRole().substring( 0, dot ) ); } - - String ownerClassName = context.findEntityBinding( null, rtnSource.getRole().substring( 0, dot ) ) - .getClassName(); + String ownerClassName = entityBinding.getClassName(); String ownerPropertyName = rtnSource.getRole().substring( dot + 1 ); return new NativeSQLQueryCollectionReturn( diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/spi/FilterSource.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/spi/FilterSource.java index 77d2316477fad5dfba6de89daaf1c72671ed590b..062676ccb289a339961f945e97d64b1648db922d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/spi/FilterSource.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/spi/FilterSource.java @@ -9,7 +9,7 @@ import java.util.Map; /** - * Defines the source of filter information. May have an associated {@link FilterDefinitionSource}. + * Defines the source of filter information. May have an associated {@link org.hibernate.engine.spi.FilterDefinition}. * Relates to both {@code } and {@link org.hibernate.annotations.Filter @Filter} * * @author Steve Ebersole @@ -28,12 +28,12 @@ public interface FilterSource { * * @return The condition defined on the filter. * - * @see {@link FilterDefinitionSource#getCondition()} + * @see org.hibernate.boot.model.source.internal.hbm.FilterSourceImpl#getCondition() */ public String getCondition(); /** - * Should Hibernate perform automatic alias injection into the supplied condition string? The default it to + * Should Hibernate perform automatic alias injection into the supplied condition string? The default is to * perform auto injection *unless* explicit alias(es) are supplied. * * @return {@code true} indicates auto injection should occur; {@code false} that it should not diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/spi/SingularAttributeSourceAny.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/spi/SingularAttributeSourceAny.java index 02f0da7a44e519f87497462ae3dac29b0c776873..c644b62c3a5cb7f05223e532745c1d6a3491383e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/spi/SingularAttributeSourceAny.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/spi/SingularAttributeSourceAny.java @@ -7,7 +7,7 @@ package org.hibernate.boot.model.source.spi; /** - * Describes an {@link } mapping + * Describes an {@code } mapping * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/StandardServiceRegistryBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/StandardServiceRegistryBuilder.java index fe11cc09858ccd097c51b6242b6e5804a5b7744b..62c7393452078171ad3dc0c49f86c93effd3f217 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/StandardServiceRegistryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/StandardServiceRegistryBuilder.java @@ -36,6 +36,35 @@ * @see org.hibernate.boot.registry.BootstrapServiceRegistryBuilder */ public class StandardServiceRegistryBuilder { + /** + * Intended only for use from {@link org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl}. + * + * Creates a StandardServiceRegistryBuilder specific to the needs of JPA bootstrapping. + * Specifically we ignore properties found in `cfg.xml` files in terms of adding them to + * the builder immediately. EntityManagerFactoryBuilderImpl handles collecting these + * properties itself. + */ + public static StandardServiceRegistryBuilder forJpa(BootstrapServiceRegistry bootstrapServiceRegistry) { + final LoadedConfig loadedConfig = new LoadedConfig( null ) { + @Override + protected void addConfigurationValues(Map configurationValues) { + // here, do nothing + } + }; + return new StandardServiceRegistryBuilder( + bootstrapServiceRegistry, + new HashMap(), + loadedConfig + ) { + @Override + public StandardServiceRegistryBuilder configure(LoadedConfig loadedConfig) { + getAggregatedCfgXml().merge( loadedConfig ); + // super also collects the properties - here we skip that part + return this; + } + }; + } + /** * The default resource name for a hibernate configuration xml file. */ @@ -43,7 +72,7 @@ public class StandardServiceRegistryBuilder { private final Map settings; private final List initiators = standardInitiatorList(); - private final List providedServices = new ArrayList(); + private final List providedServices = new ArrayList<>(); private boolean autoCloseRegistry = true; @@ -67,6 +96,21 @@ public StandardServiceRegistryBuilder(BootstrapServiceRegistry bootstrapServiceR this( bootstrapServiceRegistry, LoadedConfig.baseline() ); } + /** + * Intended for use exclusively from JPA boot-strapping. + * + * @see #forJpa + */ + private StandardServiceRegistryBuilder( + BootstrapServiceRegistry bootstrapServiceRegistry, + Map settings, + LoadedConfig loadedConfig) { + this.bootstrapServiceRegistry = bootstrapServiceRegistry; + this.configLoader = new ConfigLoader( bootstrapServiceRegistry ); + this.settings = settings; + this.aggregatedCfgXml = loadedConfig; + } + /** * Create a builder with the specified bootstrap services. * @@ -81,6 +125,10 @@ public StandardServiceRegistryBuilder( this.aggregatedCfgXml = loadedConfigBaseline; } + public ConfigLoader getConfigLoader() { + return configLoader; + } + /** * Intended for internal testing use only!! */ @@ -94,11 +142,12 @@ public LoadedConfig getAggregatedCfgXml() { * @return List of standard initiators */ private static List standardInitiatorList() { - final List initiators = new ArrayList(); + final List initiators = new ArrayList<>( StandardServiceInitiators.LIST.size() ); initiators.addAll( StandardServiceInitiators.LIST ); return initiators; } + @SuppressWarnings("unused") public BootstrapServiceRegistry getBootstrapServiceRegistry() { return bootstrapServiceRegistry; } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/AggregatedClassLoader.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/AggregatedClassLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..e92ae1b9b4f4ed326d2d5731c2678b5c607ad4a2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/AggregatedClassLoader.java @@ -0,0 +1,231 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.boot.registry.classloading.internal; + +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedHashSet; + +public class AggregatedClassLoader extends ClassLoader { + private final ClassLoader[] individualClassLoaders; + private final TcclLookupPrecedence tcclLookupPrecedence; + + public AggregatedClassLoader(final LinkedHashSet orderedClassLoaderSet, TcclLookupPrecedence precedence) { + super( null ); + individualClassLoaders = orderedClassLoaderSet.toArray( new ClassLoader[orderedClassLoaderSet.size()] ); + tcclLookupPrecedence = precedence; + } + + private Iterator newClassLoaderIterator() { + final ClassLoader threadClassLoader = locateTCCL(); + if ( tcclLookupPrecedence == TcclLookupPrecedence.NEVER || threadClassLoader == null ) { + return newTcclNeverIterator(); + } + else if ( tcclLookupPrecedence == TcclLookupPrecedence.AFTER ) { + return newTcclAfterIterator(threadClassLoader); + } + else if ( tcclLookupPrecedence == TcclLookupPrecedence.BEFORE ) { + return newTcclBeforeIterator(threadClassLoader); + } + else { + throw new RuntimeException( "Unknown precedence: "+tcclLookupPrecedence ); + } + } + + private Iterator newTcclBeforeIterator(final ClassLoader threadContextClassLoader) { + final ClassLoader systemClassLoader = locateSystemClassLoader(); + return new Iterator() { + private int currentIndex = 0; + private boolean tcCLReturned = false; + private boolean sysCLReturned = false; + + @Override + public boolean hasNext() { + if ( !tcCLReturned ) { + return true; + } + else if ( currentIndex < individualClassLoaders.length ) { + return true; + } + else if ( !sysCLReturned && systemClassLoader != null ) { + return true; + } + + return false; + } + + @Override + public ClassLoader next() { + if ( !tcCLReturned ) { + tcCLReturned = true; + return threadContextClassLoader; + } + else if ( currentIndex < individualClassLoaders.length ) { + currentIndex += 1; + return individualClassLoaders[ currentIndex - 1 ]; + } + else if ( !sysCLReturned && systemClassLoader != null ) { + sysCLReturned = true; + return systemClassLoader; + } + throw new IllegalStateException( "No more item" ); + } + }; + } + + private Iterator newTcclAfterIterator(final ClassLoader threadContextClassLoader) { + final ClassLoader systemClassLoader = locateSystemClassLoader(); + return new Iterator() { + private int currentIndex = 0; + private boolean tcCLReturned = false; + private boolean sysCLReturned = false; + + @Override + public boolean hasNext() { + if ( currentIndex < individualClassLoaders.length ) { + return true; + } + else if ( !tcCLReturned ) { + return true; + } + else if ( !sysCLReturned && systemClassLoader != null ) { + return true; + } + + return false; + } + + @Override + public ClassLoader next() { + if ( currentIndex < individualClassLoaders.length ) { + currentIndex += 1; + return individualClassLoaders[ currentIndex - 1 ]; + } + else if ( !tcCLReturned ) { + tcCLReturned = true; + return threadContextClassLoader; + } + else if ( !sysCLReturned && systemClassLoader != null ) { + sysCLReturned = true; + return systemClassLoader; + } + throw new IllegalStateException( "No more item" ); + } + }; + } + + private Iterator newTcclNeverIterator() { + final ClassLoader systemClassLoader = locateSystemClassLoader(); + return new Iterator() { + private int currentIndex = 0; + private boolean sysCLReturned = false; + + @Override + public boolean hasNext() { + if ( currentIndex < individualClassLoaders.length ) { + return true; + } + else if ( !sysCLReturned && systemClassLoader != null ) { + return true; + } + + return false; + } + + @Override + public ClassLoader next() { + if ( currentIndex < individualClassLoaders.length ) { + currentIndex += 1; + return individualClassLoaders[ currentIndex - 1 ]; + } + else if ( !sysCLReturned && systemClassLoader != null ) { + sysCLReturned = true; + return systemClassLoader; + } + throw new IllegalStateException( "No more item" ); + } + }; + } + + @Override + public Enumeration getResources(String name) throws IOException { + final LinkedHashSet resourceUrls = new LinkedHashSet(); + final Iterator clIterator = newClassLoaderIterator(); + while ( clIterator.hasNext() ) { + final ClassLoader classLoader = clIterator.next(); + final Enumeration urls = classLoader.getResources( name ); + while ( urls.hasMoreElements() ) { + resourceUrls.add( urls.nextElement() ); + } + } + + return new Enumeration() { + final Iterator resourceUrlIterator = resourceUrls.iterator(); + + @Override + public boolean hasMoreElements() { + return resourceUrlIterator.hasNext(); + } + + @Override + public URL nextElement() { + return resourceUrlIterator.next(); + } + }; + } + + @Override + protected URL findResource(String name) { + final Iterator clIterator = newClassLoaderIterator(); + while ( clIterator.hasNext() ) { + final ClassLoader classLoader = clIterator.next(); + final URL resource = classLoader.getResource( name ); + if ( resource != null ) { + return resource; + } + } + return super.findResource( name ); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + final Iterator clIterator = newClassLoaderIterator(); + while ( clIterator.hasNext() ) { + final ClassLoader classLoader = clIterator.next(); + try { + return classLoader.loadClass( name ); + } + catch (Exception ignore) { + } + catch (LinkageError ignore) { + } + } + + throw new ClassNotFoundException( "Could not load requested class : " + name ); + } + + private static ClassLoader locateSystemClassLoader() { + try { + return ClassLoader.getSystemClassLoader(); + } + catch (Exception e) { + return null; + } + } + + private static ClassLoader locateTCCL() { + try { + return Thread.currentThread().getContextClassLoader(); + } + catch (Exception e) { + return null; + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java index fd4deff3f3e517e6f03edccacc52126427f7d320..7208caabd4ff44673cc430f076a997fa26b3115e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java @@ -6,7 +6,6 @@ */ package org.hibernate.boot.registry.classloading.internal; -import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; @@ -17,7 +16,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Enumeration; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -126,224 +124,6 @@ private static void addIfSet(List providedClassLoaders, String name } } - private static ClassLoader locateSystemClassLoader() { - try { - return ClassLoader.getSystemClassLoader(); - } - catch (Exception e) { - return null; - } - } - - private static ClassLoader locateTCCL() { - try { - return Thread.currentThread().getContextClassLoader(); - } - catch (Exception e) { - return null; - } - } - - private static class AggregatedClassLoader extends ClassLoader { - private final ClassLoader[] individualClassLoaders; - private final TcclLookupPrecedence tcclLookupPrecedence; - - private AggregatedClassLoader(final LinkedHashSet orderedClassLoaderSet, TcclLookupPrecedence precedence) { - super( null ); - individualClassLoaders = orderedClassLoaderSet.toArray( new ClassLoader[orderedClassLoaderSet.size()] ); - tcclLookupPrecedence = precedence; - } - - private Iterator newClassLoaderIterator() { - final ClassLoader threadClassLoader = locateTCCL(); - if ( tcclLookupPrecedence == TcclLookupPrecedence.NEVER || threadClassLoader == null ) { - return newTcclNeverIterator(); - } - else if ( tcclLookupPrecedence == TcclLookupPrecedence.AFTER ) { - return newTcclAfterIterator(threadClassLoader); - } - else if ( tcclLookupPrecedence == TcclLookupPrecedence.BEFORE ) { - return newTcclBeforeIterator(threadClassLoader); - } - else { - throw new RuntimeException( "Unknown precedence: "+tcclLookupPrecedence ); - } - } - - private Iterator newTcclBeforeIterator(final ClassLoader threadContextClassLoader) { - final ClassLoader systemClassLoader = locateSystemClassLoader(); - return new Iterator() { - private int currentIndex = 0; - private boolean tcCLReturned = false; - private boolean sysCLReturned = false; - - @Override - public boolean hasNext() { - if ( !tcCLReturned ) { - return true; - } - else if ( currentIndex < individualClassLoaders.length ) { - return true; - } - else if ( !sysCLReturned && systemClassLoader != null ) { - return true; - } - - return false; - } - - @Override - public ClassLoader next() { - if ( !tcCLReturned ) { - tcCLReturned = true; - return threadContextClassLoader; - } - else if ( currentIndex < individualClassLoaders.length ) { - currentIndex += 1; - return individualClassLoaders[ currentIndex - 1 ]; - } - else if ( !sysCLReturned && systemClassLoader != null ) { - sysCLReturned = true; - return systemClassLoader; - } - throw new IllegalStateException( "No more item" ); - } - }; - } - - private Iterator newTcclAfterIterator(final ClassLoader threadContextClassLoader) { - final ClassLoader systemClassLoader = locateSystemClassLoader(); - return new Iterator() { - private int currentIndex = 0; - private boolean tcCLReturned = false; - private boolean sysCLReturned = false; - - @Override - public boolean hasNext() { - if ( currentIndex < individualClassLoaders.length ) { - return true; - } - else if ( !tcCLReturned ) { - return true; - } - else if ( !sysCLReturned && systemClassLoader != null ) { - return true; - } - - return false; - } - - @Override - public ClassLoader next() { - if ( currentIndex < individualClassLoaders.length ) { - currentIndex += 1; - return individualClassLoaders[ currentIndex - 1 ]; - } - else if ( !tcCLReturned ) { - tcCLReturned = true; - return threadContextClassLoader; - } - else if ( !sysCLReturned && systemClassLoader != null ) { - sysCLReturned = true; - return systemClassLoader; - } - throw new IllegalStateException( "No more item" ); - } - }; - } - - private Iterator newTcclNeverIterator() { - final ClassLoader systemClassLoader = locateSystemClassLoader(); - return new Iterator() { - private int currentIndex = 0; - private boolean sysCLReturned = false; - - @Override - public boolean hasNext() { - if ( currentIndex < individualClassLoaders.length ) { - return true; - } - else if ( !sysCLReturned && systemClassLoader != null ) { - return true; - } - - return false; - } - - @Override - public ClassLoader next() { - if ( currentIndex < individualClassLoaders.length ) { - currentIndex += 1; - return individualClassLoaders[ currentIndex - 1 ]; - } - else if ( !sysCLReturned && systemClassLoader != null ) { - sysCLReturned = true; - return systemClassLoader; - } - throw new IllegalStateException( "No more item" ); - } - }; - } - - @Override - public Enumeration getResources(String name) throws IOException { - final LinkedHashSet resourceUrls = new LinkedHashSet(); - final Iterator clIterator = newClassLoaderIterator(); - while ( clIterator.hasNext() ) { - final ClassLoader classLoader = clIterator.next(); - final Enumeration urls = classLoader.getResources( name ); - while ( urls.hasMoreElements() ) { - resourceUrls.add( urls.nextElement() ); - } - } - - return new Enumeration() { - final Iterator resourceUrlIterator = resourceUrls.iterator(); - - @Override - public boolean hasMoreElements() { - return resourceUrlIterator.hasNext(); - } - - @Override - public URL nextElement() { - return resourceUrlIterator.next(); - } - }; - } - - @Override - protected URL findResource(String name) { - final Iterator clIterator = newClassLoaderIterator(); - while ( clIterator.hasNext() ) { - final ClassLoader classLoader = clIterator.next(); - final URL resource = classLoader.getResource( name ); - if ( resource != null ) { - return resource; - } - } - return super.findResource( name ); - } - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - final Iterator clIterator = newClassLoaderIterator(); - while ( clIterator.hasNext() ) { - final ClassLoader classLoader = clIterator.next(); - try { - return classLoader.loadClass( name ); - } - catch (Exception ignore) { - } - catch (LinkageError ignore) { - } - } - - throw new ClassNotFoundException( "Could not load requested class : " + name ); - } - - } - @Override @SuppressWarnings({"unchecked"}) public Class classForName(String className) { 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 8d2e581f98e6f2444c5e2b7405c0eb22297b2b98..086a792042860de3a28883a016b8dd41cabade83 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 @@ -49,6 +49,7 @@ import org.hibernate.dialect.MimerSQLDialect; import org.hibernate.dialect.MySQL57Dialect; import org.hibernate.dialect.MySQL57InnoDBDialect; +import org.hibernate.dialect.MySQL8Dialect; import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.MySQL5InnoDBDialect; import org.hibernate.dialect.Oracle10gDialect; @@ -222,6 +223,7 @@ private void addDialects(StrategySelectorImpl strategySelector) { addDialect( strategySelector, MySQL5InnoDBDialect.class ); addDialect( strategySelector, MySQL57InnoDBDialect.class ); addDialect( strategySelector, MySQL57Dialect.class ); + addDialect( strategySelector, MySQL8Dialect.class ); addDialect( strategySelector, Oracle8iDialect.class ); addDialect( strategySelector, Oracle9iDialect.class ); addDialect( strategySelector, Oracle10gDialect.class ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index 793379c75a0fa1bd895255b39d4979444bcb0a8a..f1f9acd23566f2d94a8c0101c72458a4dfdddb74 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -427,4 +427,14 @@ public ImmutableEntityUpdateQueryHandlingMode getImmutableEntityUpdateQueryHandl public boolean inClauseParameterPaddingEnabled() { return delegate.inClauseParameterPaddingEnabled(); } + + @Override + public boolean nativeExceptionHandling51Compliance() { + return delegate.nativeExceptionHandling51Compliance(); + } + + @Override + public boolean isEnhancementAsProxyEnabled() { + return delegate.isEnhancementAsProxyEnabled(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index 62c0a4b39f2ece8b002001917a06169e89c77ab5..8e92af0def0690b13a7d71d308bbf0dc79ed2a5e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -286,4 +286,15 @@ default ImmutableEntityUpdateQueryHandlingMode getImmutableEntityUpdateQueryHand default boolean inClauseParameterPaddingEnabled() { return false; } + + default boolean nativeExceptionHandling51Compliance() { + return false; + } + + /** + * Can bytecode-enhanced entity classes be used as a "proxy"? + */ + default boolean isEnhancementAsProxyEnabled() { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/xsd/ConfigXsdSupport.java b/hibernate-core/src/main/java/org/hibernate/boot/xsd/ConfigXsdSupport.java index 8d1b4f4207072ccf9160baee1a81cdac5e5b0b9f..69c5214f87fc9b03987ee1953381d01e7e36b0bd 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/xsd/ConfigXsdSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/xsd/ConfigXsdSupport.java @@ -51,6 +51,12 @@ private ConfigXsdSupport() { "http://xmlns.jcp.org/xml/ns/persistence" ); + private final XsdDescriptor jpa30 = LocalXsdResolver.buildXsdDescriptor( + "org/hibernate/jpa/persistence_3_0.xsd", + "3.0", + "https://jakarta.ee/xml/ns/persistence" + ); + private final XsdDescriptor cfgXml = LocalXsdResolver.buildXsdDescriptor( "org/hibernate/xsd/cfg/legacy-configuration-4.0.xsd", "4.0" , @@ -75,6 +81,9 @@ public XsdDescriptor jpaXsd(String version) { case "2.2": { return jpa22; } + case "3.0": { + return jpa30; + } default: { throw new IllegalArgumentException( "Unrecognized JPA persistence.xml XSD version : `" + version + "`" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/xsd/LocalXsdResolver.java b/hibernate-core/src/main/java/org/hibernate/boot/xsd/LocalXsdResolver.java index ea1acefe3ec41be06bbcf987a9b99bfd3dc10b06..333bb475f2a166d077d7eeb8ed4cef29e265ad43 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/xsd/LocalXsdResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/xsd/LocalXsdResolver.java @@ -34,7 +34,7 @@ public class LocalXsdResolver { private static final Logger log = Logger.getLogger( LocalXsdResolver.class ); - private static final List VALID_JPA_VERSIONS = Arrays.asList( "1.0", "2.0", "2.1", "2.2" ); + private static final List VALID_JPA_VERSIONS = Arrays.asList( "1.0", "2.0", "2.1", "2.2", "3.0" ); public static String latestJpaVerison() { return "2.2"; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/xsd/MappingXsdSupport.java b/hibernate-core/src/main/java/org/hibernate/boot/xsd/MappingXsdSupport.java index 7ed14531106f58d624c2d2bb02f3bca6a3cb16d3..33a371927088226f1279f1c2e8d0986c51b68237 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/xsd/MappingXsdSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/xsd/MappingXsdSupport.java @@ -44,6 +44,12 @@ public class MappingXsdSupport { "http://xmlns.jcp.org/xml/ns/persistence" ); + private final XsdDescriptor jpa30 = LocalXsdResolver.buildXsdDescriptor( + "org/hibernate/jpa/orm_3_0.xsd", + "3.0", + "https://jakarta.ee/xml/ns/persistence/orm" + ); + private final XsdDescriptor hbmXml = LocalXsdResolver.buildXsdDescriptor( "org/hibernate/xsd/mapping/legacy-mapping-4.0.xsd", "4.0", @@ -72,6 +78,9 @@ public XsdDescriptor jpaXsd(String version) { case "2.2": { return jpa22; } + case "3.0:": { + return jpa30; + } default: { throw new IllegalArgumentException( "Unrecognized JPA orm.xml XSD version : `" + version + "`" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/BytecodeLogger.java b/hibernate-core/src/main/java/org/hibernate/bytecode/BytecodeLogger.java new file mode 100644 index 0000000000000000000000000000000000000000..203d7d2929bfa41244a1ebd390b6e8755008f44a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/BytecodeLogger.java @@ -0,0 +1,22 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode; + +import org.jboss.logging.BasicLogger; +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +public interface BytecodeLogger extends BasicLogger { + String NAME = "org.hibernate.orm.bytecode"; + + Logger LOGGER = Logger.getLogger( NAME ); + + boolean TRACE_ENABLED = LOGGER.isTraceEnabled(); + boolean DEBUG_ENABLED = LOGGER.isDebugEnabled(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java index 8792cac4a181243684e756a2e0dfe78e5c34cdf0..59506c65e59623a6284cbee4ffb6a0243fe8bf3f 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java @@ -8,6 +8,9 @@ import java.util.Collection; import java.util.Map; +import java.util.Objects; +import java.util.Optional; + import javax.persistence.Access; import javax.persistence.AccessType; import javax.persistence.ManyToMany; @@ -15,6 +18,7 @@ import javax.persistence.OneToMany; import javax.persistence.OneToOne; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancementException; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.internal.CoreLogging; @@ -30,18 +34,19 @@ import net.bytebuddy.dynamic.scaffold.InstrumentedType; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.bytecode.ByteCodeAppender; +import net.bytebuddy.implementation.bytecode.assign.Assigner; import net.bytebuddy.jar.asm.MethodVisitor; import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Type; -class BiDirectionalAssociationHandler implements Implementation { +final class BiDirectionalAssociationHandler implements Implementation { private static final CoreMessageLogger log = CoreLogging.messageLogger( BiDirectionalAssociationHandler.class ); static Implementation wrap( TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext, - FieldDescription persistentField, + AnnotatedFieldDescription persistentField, Implementation implementation) { if ( !enhancementContext.doBiDirectionalAssociationManagement( persistentField ) ) { return implementation; @@ -51,8 +56,15 @@ static Implementation wrap( if ( targetEntity == null ) { return implementation; } - String mappedBy = getMappedBy( persistentField, targetEntity, enhancementContext ); - if ( mappedBy == null || mappedBy.isEmpty() ) { + String mappedBy = getMappedBy( persistentField ); + String bidirectionalAttributeName; + if ( mappedBy == null ) { + bidirectionalAttributeName = getMappedByManyToMany( persistentField, targetEntity, enhancementContext ); + } + else { + bidirectionalAttributeName = mappedBy; + } + if ( bidirectionalAttributeName == null || bidirectionalAttributeName.isEmpty() ) { log.infof( "Could not find bi-directional association for field [%s#%s]", managedCtClass.getName(), @@ -62,38 +74,47 @@ static Implementation wrap( } TypeDescription targetType = FieldLocator.ForClassHierarchy.Factory.INSTANCE.make( targetEntity ) - .locate( mappedBy ) + .locate( bidirectionalAttributeName ) .getField() .getType() .asErasure(); - if ( EnhancerImpl.isAnnotationPresent( persistentField, OneToOne.class ) ) { + if ( persistentField.hasAnnotation( OneToOne.class ) ) { implementation = Advice.withCustomMapping() - .bind( CodeTemplates.FieldValue.class, persistentField ) - .bind( CodeTemplates.MappedBy.class, mappedBy ) + .bind( + // We need to make the fieldValue writable for one-to-one to avoid stack overflows + // when unsetting the inverse field + new Advice.OffsetMapping.ForField.Resolved.Factory<>( + CodeTemplates.FieldValue.class, + persistentField.getFieldDescription(), + false, + Assigner.Typing.DYNAMIC + ) + ) + .bind( CodeTemplates.InverseSide.class, mappedBy != null ) .to( CodeTemplates.OneToOneHandler.class ) .wrap( implementation ); } - if ( EnhancerImpl.isAnnotationPresent( persistentField, OneToMany.class ) ) { + if ( persistentField.hasAnnotation( OneToMany.class ) ) { implementation = Advice.withCustomMapping() - .bind( CodeTemplates.FieldValue.class, persistentField ) - .bind( CodeTemplates.MappedBy.class, mappedBy ) + .bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() ) + .bind( CodeTemplates.InverseSide.class, mappedBy != null ) .to( persistentField.getType().asErasure().isAssignableTo( Map.class ) ? CodeTemplates.OneToManyOnMapHandler.class : CodeTemplates.OneToManyOnCollectionHandler.class ) .wrap( implementation ); } - if ( EnhancerImpl.isAnnotationPresent( persistentField, ManyToOne.class ) ) { + if ( persistentField.hasAnnotation( ManyToOne.class ) ) { implementation = Advice.withCustomMapping() - .bind( CodeTemplates.FieldValue.class, persistentField ) - .bind( CodeTemplates.MappedBy.class, mappedBy ) + .bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() ) + .bind( CodeTemplates.BidirectionalAttribute.class, bidirectionalAttributeName ) .to( CodeTemplates.ManyToOneHandler.class ) .wrap( implementation ); } - if ( EnhancerImpl.isAnnotationPresent( persistentField, ManyToMany.class ) ) { + if ( persistentField.hasAnnotation( ManyToMany.class ) ) { if ( persistentField.getType().asErasure().isAssignableTo( Map.class ) || targetType.isAssignableTo( Map.class ) ) { log.infof( @@ -105,21 +126,22 @@ static Implementation wrap( } implementation = Advice.withCustomMapping() - .bind( CodeTemplates.FieldValue.class, persistentField ) - .bind( CodeTemplates.MappedBy.class, mappedBy ) + .bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() ) + .bind( CodeTemplates.InverseSide.class, mappedBy != null ) + .bind( CodeTemplates.BidirectionalAttribute.class, bidirectionalAttributeName ) .to( CodeTemplates.ManyToManyHandler.class ) .wrap( implementation ); } - return new BiDirectionalAssociationHandler( implementation, targetEntity, targetType, mappedBy ); + return new BiDirectionalAssociationHandler( implementation, managedCtClass, persistentField, targetEntity, targetType, bidirectionalAttributeName ); } - public static TypeDescription getTargetEntityClass(TypeDescription managedCtClass, FieldDescription persistentField) { + public static TypeDescription getTargetEntityClass(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { try { - AnnotationDescription.Loadable oto = EnhancerImpl.getAnnotation( persistentField, OneToOne.class ); - AnnotationDescription.Loadable otm = EnhancerImpl.getAnnotation( persistentField, OneToMany.class ); - AnnotationDescription.Loadable mto = EnhancerImpl.getAnnotation( persistentField, ManyToOne.class ); - AnnotationDescription.Loadable mtm = EnhancerImpl.getAnnotation( persistentField, ManyToMany.class ); + AnnotationDescription.Loadable oto = persistentField.getAnnotation( OneToOne.class ); + AnnotationDescription.Loadable otm = persistentField.getAnnotation( OneToMany.class ); + AnnotationDescription.Loadable mto = persistentField.getAnnotation( ManyToOne.class ); + AnnotationDescription.Loadable mtm = persistentField.getAnnotation( ManyToMany.class ); if ( oto == null && otm == null && mto == null && mtm == null ) { return null; @@ -157,45 +179,42 @@ else if ( !targetClass.resolve( TypeDescription.class ).represents( void.class ) return entityType( target( persistentField ) ); } - private static TypeDescription.Generic target(FieldDescription persistentField) { + private static TypeDescription.Generic target(AnnotatedFieldDescription persistentField) { AnnotationDescription.Loadable access = persistentField.getDeclaringType().asErasure().getDeclaredAnnotations().ofType( Access.class ); - if ( access != null && access.loadSilent().value() == AccessType.FIELD ) { + if ( access != null && access.load().value() == AccessType.FIELD ) { return persistentField.getType(); } else { - MethodDescription getter = EnhancerImpl.getterOf( persistentField ); - if ( getter == null ) { - return persistentField.getType(); + Optional getter = persistentField.getGetter(); + if ( getter.isPresent() ) { + return getter.get().getReturnType(); } else { - return getter.getReturnType(); + return persistentField.getType(); } } } - private static String getMappedBy(FieldDescription target, TypeDescription targetEntity, ByteBuddyEnhancementContext context) { - String mappedBy = getMappedByNotManyToMany( target ); + private static String getMappedBy(AnnotatedFieldDescription target) { + final String mappedBy = getMappedByFromAnnotation( target ); if ( mappedBy == null || mappedBy.isEmpty() ) { - return getMappedByManyToMany( target, targetEntity, context ); - } - else { - return mappedBy; + return null; } + return mappedBy; } - - private static String getMappedByNotManyToMany(FieldDescription target) { + private static String getMappedByFromAnnotation(AnnotatedFieldDescription target) { try { - AnnotationDescription.Loadable oto = EnhancerImpl.getAnnotation( target, OneToOne.class ); + AnnotationDescription.Loadable oto = target.getAnnotation( OneToOne.class ); if ( oto != null ) { return oto.getValue( new MethodDescription.ForLoadedMethod( OneToOne.class.getDeclaredMethod( "mappedBy" ) ) ).resolve( String.class ); } - AnnotationDescription.Loadable otm = EnhancerImpl.getAnnotation( target, OneToMany.class ); + AnnotationDescription.Loadable otm = target.getAnnotation( OneToMany.class ); if ( otm != null ) { return otm.getValue( new MethodDescription.ForLoadedMethod( OneToMany.class.getDeclaredMethod( "mappedBy" ) ) ).resolve( String.class ); } - AnnotationDescription.Loadable mtm = EnhancerImpl.getAnnotation( target, ManyToMany.class ); + AnnotationDescription.Loadable mtm = target.getAnnotation( ManyToMany.class ); if ( mtm != null ) { return mtm.getValue( new MethodDescription.ForLoadedMethod( ManyToMany.class.getDeclaredMethod( "mappedBy" ) ) ).resolve( String.class ); } @@ -206,11 +225,12 @@ private static String getMappedByNotManyToMany(FieldDescription target) { return null; } - private static String getMappedByManyToMany(FieldDescription target, TypeDescription targetEntity, ByteBuddyEnhancementContext context) { + private static String getMappedByManyToMany(AnnotatedFieldDescription target, TypeDescription targetEntity, ByteBuddyEnhancementContext context) { for ( FieldDescription f : targetEntity.getDeclaredFields() ) { - if ( context.isPersistentField( f ) - && target.getName().equals( getMappedByNotManyToMany( f ) ) - && target.getDeclaringType().asErasure().isAssignableTo( entityType( f.getType() ) ) ) { + AnnotatedFieldDescription annotatedF = new AnnotatedFieldDescription( context, f ); + if ( context.isPersistentField( annotatedF ) + && target.getName().equals( getMappedBy( annotatedF ) ) + && target.getDeclaringType().asErasure().isAssignableTo( entityType( annotatedF.getType() ) ) ) { log.debugf( "mappedBy association for field [%s#%s] is [%s#%s]", target.getDeclaringType().asErasure().getName(), @@ -239,21 +259,28 @@ private static TypeDescription entityType(TypeDescription.Generic type) { private final Implementation delegate; + private final TypeDescription entity; + private final AnnotatedFieldDescription field; + private final TypeDescription targetEntity; private final TypeDescription targetType; - private final String mappedBy; + private final String bidirectionalAttributeName; private BiDirectionalAssociationHandler( Implementation delegate, + TypeDescription entity, + AnnotatedFieldDescription field, TypeDescription targetEntity, TypeDescription targetType, - String mappedBy) { + String bidirectionalAttributeName) { this.delegate = delegate; + this.entity = entity; + this.field = field; this.targetEntity = targetEntity; this.targetType = targetType; - this.mappedBy = mappedBy; + this.bidirectionalAttributeName = bidirectionalAttributeName; } @Override @@ -287,11 +314,21 @@ public void visitMethodInsn(int opcode, String owner, String name, String desc, super.visitMethodInsn( Opcodes.INVOKEVIRTUAL, targetEntity.getInternalName(), - EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + mappedBy, + EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + bidirectionalAttributeName, Type.getMethodDescriptor( Type.getType( targetType.getDescriptor() ) ), false ); } + else if ( name.equals( "getterSelf" ) ) { + super.visitVarInsn( Opcodes.ALOAD, 0 ); + super.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + entity.getInternalName(), + EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + field.getName(), + Type.getMethodDescriptor( Type.getType( field.getDescriptor() ) ), + false + ); + } else if ( name.equals( "setterSelf" ) ) { super.visitInsn( Opcodes.POP ); super.visitTypeInsn( Opcodes.CHECKCAST, targetEntity.getInternalName() ); @@ -299,7 +336,7 @@ else if ( name.equals( "setterSelf" ) ) { super.visitMethodInsn( Opcodes.INVOKEVIRTUAL, targetEntity.getInternalName(), - EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + mappedBy, + EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + bidirectionalAttributeName, Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( targetType.getDescriptor() ) ), false ); @@ -311,7 +348,7 @@ else if ( name.equals( "setterNull" ) ) { super.visitMethodInsn( Opcodes.INVOKEVIRTUAL, targetEntity.getInternalName(), - EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + mappedBy, + EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + bidirectionalAttributeName, Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( targetType.getDescriptor() ) ), false ); @@ -327,4 +364,24 @@ else if ( name.equals( "setterNull" ) ) { }, implementationContext, instrumentedMethod ); } } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if ( o == null || BiDirectionalAssociationHandler.class != o.getClass() ) { + return false; + } + final BiDirectionalAssociationHandler that = (BiDirectionalAssociationHandler) o; + return Objects.equals( delegate, that.delegate ) && + Objects.equals( targetEntity, that.targetEntity ) && + Objects.equals( targetType, that.targetType ) && + Objects.equals( bidirectionalAttributeName, that.bidirectionalAttributeName ); + } + + @Override + public int hashCode() { + return Objects.hash( delegate, targetEntity, targetType, bidirectionalAttributeName ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java index 09a6e30620d6c21cae8ac05639ddd23812472789..082e73adc4a5ca17fd878936b556314397fb1e1e 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java @@ -6,16 +6,33 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import static net.bytebuddy.matcher.ElementMatchers.isGetter; + +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.hibernate.MappingException; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancementContext; -import org.hibernate.bytecode.enhance.spi.UnloadedField; import net.bytebuddy.description.field.FieldDescription; +import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.scaffold.MethodGraph; +import net.bytebuddy.matcher.ElementMatcher; class ByteBuddyEnhancementContext { + private static final ElementMatcher.Junction IS_GETTER = isGetter(); + private final EnhancementContext enhancementContext; + private final ConcurrentHashMap> getterByTypeMap = new ConcurrentHashMap<>(); + ByteBuddyEnhancementContext(EnhancementContext enhancementContext) { this.enhancementContext = enhancementContext; } @@ -36,10 +53,6 @@ public boolean isMappedSuperclassClass(TypeDescription classDescriptor) { return enhancementContext.isMappedSuperclassClass( new UnloadedTypeDescription( classDescriptor ) ); } - public boolean doBiDirectionalAssociationManagement(FieldDescription field) { - return enhancementContext.doBiDirectionalAssociationManagement( new UnloadedFieldDescription( field ) ); - } - public boolean doDirtyCheckingInline(TypeDescription classDescriptor) { return enhancementContext.doDirtyCheckingInline( new UnloadedTypeDescription( classDescriptor ) ); } @@ -52,28 +65,54 @@ public boolean hasLazyLoadableAttributes(TypeDescription classDescriptor) { return enhancementContext.hasLazyLoadableAttributes( new UnloadedTypeDescription( classDescriptor ) ); } - public boolean isPersistentField(FieldDescription ctField) { - return enhancementContext.isPersistentField( new UnloadedFieldDescription( ctField ) ); + public boolean isPersistentField(AnnotatedFieldDescription field) { + return enhancementContext.isPersistentField( field ); } - public FieldDescription[] order(FieldDescription[] persistentFields) { - UnloadedField[] unloadedFields = new UnloadedField[persistentFields.length]; - for ( int i = 0; i < unloadedFields.length; i++ ) { - unloadedFields[i] = new UnloadedFieldDescription( persistentFields[i] ); - } - UnloadedField[] ordered = enhancementContext.order( unloadedFields ); - FieldDescription[] orderedFields = new FieldDescription[persistentFields.length]; - for ( int i = 0; i < orderedFields.length; i++ ) { - orderedFields[i] = ( (UnloadedFieldDescription) ordered[i] ).fieldDescription; - } - return orderedFields; + public AnnotatedFieldDescription[] order(AnnotatedFieldDescription[] persistentFields) { + return (AnnotatedFieldDescription[]) enhancementContext.order( persistentFields ); + } + + public boolean isLazyLoadable(AnnotatedFieldDescription field) { + return enhancementContext.isLazyLoadable( field ); } - public boolean isLazyLoadable(FieldDescription field) { - return enhancementContext.isLazyLoadable( new UnloadedFieldDescription( field ) ); + public boolean isMappedCollection(AnnotatedFieldDescription field) { + return enhancementContext.isMappedCollection( field ); } - public boolean isMappedCollection(FieldDescription field) { - return enhancementContext.isMappedCollection( new UnloadedFieldDescription( field ) ); + public boolean doBiDirectionalAssociationManagement(AnnotatedFieldDescription field) { + return enhancementContext.doBiDirectionalAssociationManagement( field ); + } + + Optional resolveGetter(FieldDescription fieldDescription) { + Map getters = getterByTypeMap + .computeIfAbsent( fieldDescription.getDeclaringType().asErasure(), declaringType -> { + return MethodGraph.Compiler.DEFAULT.compile( declaringType ) + .listNodes() + .asMethodList() + .filter( IS_GETTER ) + .stream() + .collect( Collectors.toMap( MethodDescription::getActualName, Function.identity() ) ); + } ); + + String capitalizedFieldName = Character.toUpperCase( fieldDescription.getName().charAt( 0 ) ) + + fieldDescription.getName().substring( 1 ); + + MethodDescription getCandidate = getters.get( "get" + capitalizedFieldName ); + MethodDescription isCandidate = getters.get( "is" + capitalizedFieldName ); + + if ( getCandidate != null ) { + if ( isCandidate != null ) { + // if there are two candidates, the existing code considered there was no getter. + // not sure it's such a good idea but throwing an exception apparently throws exception + // in cases where Hibernate does not usually throw a mapping error. + return Optional.empty(); + } + + return Optional.of( getCandidate ); + } + + return Optional.ofNullable( isCandidate ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java index 996d7b3befb422833681c8cca9fb310d6724f441..31d5a756c597317e268548dd364a7e5aabb99396 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java @@ -323,15 +323,24 @@ static class CompositeOwnerDirtyCheckingHandler { static class OneToOneHandler { @Advice.OnMethodEnter - static void enter(@FieldValue Object field, @Advice.Argument(0) Object argument, @MappedBy String mappedBy) { - if ( field != null && Hibernate.isPropertyInitialized( field, mappedBy ) && argument != null ) { - setterNull( field, null ); + static void enter(@FieldValue Object field, @Advice.Argument(0) Object argument, @InverseSide boolean inverseSide) { + // Unset the inverse attribute, which possibly initializes the old value, + // only if this is the inverse side, or the old value is already initialized + if ( ( inverseSide || Hibernate.isInitialized( field ) ) && getterSelf() != null ) { + // We copy the old value, then set the field to null which we must do before + // unsetting the inverse attribute, as we'd otherwise run into a stack overflow situation + // The field is writable, so setting it to null here is actually a field write. + Object fieldCopy = field; + field = null; + setterNull( fieldCopy, null ); } } @Advice.OnMethodExit - static void exit(@Advice.This Object self, @Advice.Argument(0) Object argument, @MappedBy String mappedBy) { - if ( argument != null && Hibernate.isPropertyInitialized( argument, mappedBy ) && getter( argument ) != self ) { + static void exit(@Advice.This Object self, @Advice.Argument(0) Object argument, @InverseSide boolean inverseSide) { + // Update the inverse attribute, which possibly initializes the argument value, + // only if this is the inverse side, or the argument value is already initialized + if ( argument != null && ( inverseSide || Hibernate.isInitialized( argument ) ) && getter( argument ) != self ) { setterSelf( argument, self ); } } @@ -341,6 +350,11 @@ static Object getter(Object target) { throw new AssertionError(); } + static Object getterSelf() { + // is replaced by the actual method call + throw new AssertionError(); + } + static void setterNull(Object target, Object argument) { // is replaced by the actual method call throw new AssertionError(); @@ -354,11 +368,14 @@ static void setterSelf(Object target, Object argument) { static class OneToManyOnCollectionHandler { @Advice.OnMethodEnter - static void enter(@FieldValue Collection field, @Advice.Argument(0) Collection argument, @MappedBy String mappedBy) { - if ( field != null && Hibernate.isPropertyInitialized( field, mappedBy ) ) { + static void enter(@FieldValue Collection field, @Advice.Argument(0) Collection argument, @InverseSide boolean inverseSide) { + // If this is the inverse side or the old collection is already initialized, + // we must unset the respective ManyToOne of the old collection elements, + // because only the owning side is responsible for persisting the state. + if ( ( inverseSide || Hibernate.isInitialized( field ) ) && getterSelf() != null ) { Object[] array = field.toArray(); for ( int i = 0; i < array.length; i++ ) { - if ( argument == null || !argument.contains( array[i] ) ) { + if ( ( inverseSide || Hibernate.isInitialized( array[i] ) ) && ( argument == null || !argument.contains( array[i] ) ) ) { setterNull( array[i], null ); } } @@ -366,11 +383,14 @@ static void enter(@FieldValue Collection field, @Advice.Argument(0) Collectio } @Advice.OnMethodExit - static void exit(@Advice.This Object self, @Advice.Argument(0) Collection argument, @MappedBy String mappedBy) { - if ( argument != null && Hibernate.isPropertyInitialized( argument, mappedBy ) ) { + static void exit(@Advice.This Object self, @Advice.Argument(0) Collection argument, @InverseSide boolean inverseSide) { + // If this is the inverse side or the new collection is already initialized, + // we must set the respective ManyToOne on the new collection elements, + // because only the owning side is responsible for persisting the state. + if ( argument != null && ( inverseSide || Hibernate.isInitialized( argument ) ) ) { Object[] array = argument.toArray(); for ( int i = 0; i < array.length; i++ ) { - if ( Hibernate.isPropertyInitialized( array[i], mappedBy ) && getter( array[i] ) != self ) { + if ( ( inverseSide || Hibernate.isInitialized( array[i] ) ) && getter( array[i] ) != self ) { setterSelf( array[i], self ); } } @@ -382,6 +402,11 @@ static Object getter(Object target) { throw new AssertionError(); } + static Object getterSelf() { + // is replaced by the actual method call + throw new AssertionError(); + } + static void setterNull(Object target, Object argument) { // is replaced by the actual method call throw new AssertionError(); @@ -395,11 +420,14 @@ static void setterSelf(Object target, Object argument) { static class OneToManyOnMapHandler { @Advice.OnMethodEnter - static void enter(@FieldValue Map field, @Advice.Argument(0) Map argument, @MappedBy String mappedBy) { - if ( field != null && Hibernate.isPropertyInitialized( field, mappedBy ) ) { + static void enter(@FieldValue Map field, @Advice.Argument(0) Map argument, @InverseSide boolean inverseSide) { + // If this is the inverse side or the old collection is already initialized, + // we must unset the respective ManyToOne of the old collection elements, + // because only the owning side is responsible for persisting the state. + if ( ( inverseSide || Hibernate.isInitialized( field ) ) && getterSelf() != null ) { Object[] array = field.values().toArray(); for ( int i = 0; i < array.length; i++ ) { - if ( argument == null || !argument.values().contains( array[i] ) ) { + if ( ( inverseSide || Hibernate.isInitialized( array[i] ) ) && ( argument == null || !argument.containsValue( array[i] ) ) ) { setterNull( array[i], null ); } } @@ -407,11 +435,14 @@ static void enter(@FieldValue Map field, @Advice.Argument(0) Map arg } @Advice.OnMethodExit - static void exit(@Advice.This Object self, @Advice.Argument(0) Map argument, @MappedBy String mappedBy) { - if ( argument != null && Hibernate.isPropertyInitialized( argument, mappedBy ) ) { + static void exit(@Advice.This Object self, @Advice.Argument(0) Map argument, @InverseSide boolean inverseSide) { + // If this is the inverse side or the new collection is already initialized, + // we must set the respective ManyToOne on the new collection elements, + // because only the owning side is responsible for persisting the state. + if ( argument != null && ( inverseSide || Hibernate.isInitialized( argument ) ) ) { Object[] array = argument.values().toArray(); for ( int i = 0; i < array.length; i++ ) { - if ( Hibernate.isPropertyInitialized( array[i], mappedBy ) && getter( array[i] ) != self ) { + if ( ( inverseSide || Hibernate.isInitialized( array[i] ) ) && getter( array[i] ) != self ) { setterSelf( array[i], self ); } } @@ -423,6 +454,11 @@ static Object getter(Object target) { throw new AssertionError(); } + static Object getterSelf() { + // is replaced by the actual method call + throw new AssertionError(); + } + static void setterNull(Object target, Object argument) { // is replaced with the actual setter call during instrumentation. throw new AssertionError(); @@ -436,8 +472,9 @@ static void setterSelf(Object target, Object argument) { static class ManyToOneHandler { @Advice.OnMethodEnter - static void enter(@Advice.This Object self, @FieldValue Object field, @MappedBy String mappedBy) { - if ( field != null && Hibernate.isPropertyInitialized( field, mappedBy ) ) { + static void enter(@Advice.This Object self, @FieldValue Object field, @BidirectionalAttribute String inverseAttribute) { + // This is always the owning side, so we only need to update the inverse side if the collection is initialized + if ( getterSelf() != null && Hibernate.isPropertyInitialized( field, inverseAttribute ) ) { Collection c = getter( field ); if ( c != null ) { c.remove( self ); @@ -446,8 +483,9 @@ static void enter(@Advice.This Object self, @FieldValue Object field, @MappedBy } @Advice.OnMethodExit - static void exit(@Advice.This Object self, @Advice.Argument(0) Object argument, @MappedBy String mappedBy) { - if ( argument != null && Hibernate.isPropertyInitialized( argument, mappedBy ) ) { + static void exit(@Advice.This Object self, @Advice.Argument(0) Object argument, @BidirectionalAttribute String inverseAttribute) { + // This is always the owning side, so we only need to update the inverse side if the collection is initialized + if ( argument != null && Hibernate.isPropertyInitialized( argument, inverseAttribute ) ) { Collection c = getter( argument ); if ( c != null && !c.contains( self ) ) { c.add( self ); @@ -459,15 +497,23 @@ static Collection getter(Object target) { // is replaced by the actual method call throw new AssertionError(); } + + static Object getterSelf() { + // is replaced by the actual method call + throw new AssertionError(); + } } static class ManyToManyHandler { @Advice.OnMethodEnter - static void enter(@Advice.This Object self, @FieldValue Collection field, @Advice.Argument(0) Collection argument, @MappedBy String mappedBy) { - if ( field != null && Hibernate.isPropertyInitialized( field, mappedBy ) ) { + static void enter(@Advice.This Object self, @FieldValue Collection field, @Advice.Argument(0) Collection argument, @InverseSide boolean inverseSide, @BidirectionalAttribute String bidirectionalAttribute) { + // If this is the inverse side or the old collection is already initialized, + // we must remove self from the respective old collection elements inverse collections, + // because only the owning side is responsible for persisting the state. + if ( ( inverseSide || Hibernate.isInitialized( field ) ) && getterSelf() != null ) { Object[] array = field.toArray(); for ( int i = 0; i < array.length; i++ ) { - if ( argument == null || !argument.contains( array[i] ) ) { + if ( ( inverseSide || Hibernate.isPropertyInitialized( array[i], bidirectionalAttribute ) ) && ( argument == null || !argument.contains( array[i] ) ) ) { getter( array[i] ).remove( self ); } } @@ -475,13 +521,16 @@ static void enter(@Advice.This Object self, @FieldValue Collection field, @Ad } @Advice.OnMethodExit - static void exit(@Advice.This Object self, @Advice.Argument(0) Collection argument, @MappedBy String mappedBy) { - if ( argument != null && Hibernate.isPropertyInitialized( argument, mappedBy ) ) { + static void exit(@Advice.This Object self, @Advice.Argument(0) Collection argument, @InverseSide boolean inverseSide, @BidirectionalAttribute String bidirectionalAttribute) { + // If this is the inverse side or the new collection is already initialized, + // we must add self to the respective new collection elements inverse collections, + // because only the owning side is responsible for persisting the state. + if ( argument != null && ( inverseSide || Hibernate.isInitialized( argument ) ) ) { Object[] array = argument.toArray(); for ( int i = 0; i < array.length; i++ ) { - if ( Hibernate.isPropertyInitialized( array[i], mappedBy ) ) { + if ( inverseSide || Hibernate.isPropertyInitialized( array[i], bidirectionalAttribute ) ) { Collection c = getter( array[i] ); - if ( c != self && c != null ) { + if ( c != null && !c.contains( self ) ) { c.add( self ); } } @@ -493,6 +542,11 @@ static Collection getter(Object self) { // is replaced by the actual method call throw new AssertionError(); } + + static Object getterSelf() { + // is replaced by the actual method call + throw new AssertionError(); + } } @Retention(RetentionPolicy.RUNTIME) @@ -506,7 +560,12 @@ static Collection getter(Object self) { } @Retention(RetentionPolicy.RUNTIME) - @interface MappedBy { + @interface InverseSide { + + } + + @Retention(RetentionPolicy.RUNTIME) + @interface BidirectionalAttribute { } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index 0c4d7b641e560b1fbd7634203ed3bdc1265b8324..6cd46fd2c258755d65c21f0b0a37b166fc2cb070 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -6,6 +6,9 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer; + +import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -13,6 +16,8 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; + import javax.persistence.Access; import javax.persistence.AccessType; import javax.persistence.Transient; @@ -24,12 +29,13 @@ import org.hibernate.bytecode.enhance.spi.EnhancementException; import org.hibernate.bytecode.enhance.spi.Enhancer; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; +import org.hibernate.bytecode.enhance.spi.UnloadedField; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; -import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker; import org.hibernate.engine.spi.CompositeOwner; import org.hibernate.engine.spi.CompositeTracker; import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker; import org.hibernate.engine.spi.Managed; import org.hibernate.engine.spi.ManagedComposite; import org.hibernate.engine.spi.ManagedEntity; @@ -40,49 +46,68 @@ import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import net.bytebuddy.ByteBuddy; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.annotation.AnnotationDescription; +import net.bytebuddy.description.annotation.AnnotationList; import net.bytebuddy.description.field.FieldDescription; +import net.bytebuddy.description.field.FieldDescription.InDefinedShape; import net.bytebuddy.description.method.MethodDescription; -import net.bytebuddy.description.method.MethodList; import net.bytebuddy.description.modifier.FieldPersistence; import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.description.type.TypeDefinition; import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.description.type.TypeDescription.Generic; import net.bytebuddy.dynamic.ClassFileLocator; import net.bytebuddy.dynamic.DynamicType; -import net.bytebuddy.dynamic.scaffold.MethodGraph; -import net.bytebuddy.dynamic.scaffold.TypeValidation; import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.implementation.FixedValue; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.StubMethod; import net.bytebuddy.pool.TypePool; -import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer; -import static net.bytebuddy.matcher.ElementMatchers.isGetter; - public class EnhancerImpl implements Enhancer { private static final CoreMessageLogger log = CoreLogging.messageLogger( Enhancer.class ); protected final ByteBuddyEnhancementContext enhancementContext; - private final ByteBuddyState bytebuddy; + private final ByteBuddyState byteBuddyState; + + private final EnhancerClassFileLocator classFileLocator; + private final TypePool typePool; - private final TypePool classPool; + /** + * Extract the following constants so that enhancement on large projects + * can be done efficiently: otherwise each instance use will trigger a + * resource load on the ClassLoader tree, triggering allocation of + * several streams to unzip each JAR file each time. + */ + private final ClassFileLocator adviceLocator = ClassFileLocator.ForClassLoader.of(CodeTemplates.class.getClassLoader()); + private final Implementation implementationTrackChange = Advice.to( CodeTemplates.TrackChange.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationGetDirtyAttributesWithoutCollections = Advice.to( CodeTemplates.GetDirtyAttributesWithoutCollections.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationAreFieldsDirtyWithoutCollections = Advice.to( CodeTemplates.AreFieldsDirtyWithoutCollections.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationClearDirtyAttributesWithoutCollections = Advice.to( CodeTemplates.ClearDirtyAttributesWithoutCollections.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationSuspendDirtyTracking = Advice.to( CodeTemplates.SuspendDirtyTracking.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationGetDirtyAttributes = Advice.to( CodeTemplates.GetDirtyAttributes.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationAreFieldsDirty = Advice.to( CodeTemplates.AreFieldsDirty.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationGetCollectionTrackerWithoutCollections = Advice.to( CodeTemplates.GetCollectionTrackerWithoutCollections.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationClearDirtyAttributes = Advice.to( CodeTemplates.ClearDirtyAttributes.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + //In this case we just extract the Advice: + private final Advice adviceInitializeLazyAttributeLoadingInterceptor = Advice.to( CodeTemplates.InitializeLazyAttributeLoadingInterceptor.class, adviceLocator ); + private final Implementation implementationSetOwner = Advice.to( CodeTemplates.SetOwner.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final Implementation implementationClearOwner = Advice.to( CodeTemplates.ClearOwner.class, adviceLocator ).wrap( StubMethod.INSTANCE ); /** * Constructs the Enhancer, using the given context. * * @param enhancementContext Describes the context in which enhancement will occur so as to give access * to contextual/environmental information. - * @param bytebuddy refers to the ByteBuddy instance to use + * @param byteBuddyState refers to the ByteBuddy instance to use */ - public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddyState bytebuddy) { + public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddyState byteBuddyState) { this.enhancementContext = new ByteBuddyEnhancementContext( enhancementContext ); - this.bytebuddy = bytebuddy; - this.classPool = buildClassPool( this.enhancementContext ); + this.byteBuddyState = byteBuddyState; + this.classFileLocator = new EnhancerClassFileLocator( enhancementContext.getLoadingClassLoader() ); + this.typePool = buildTypePool( classFileLocator ); } /** @@ -100,26 +125,22 @@ public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddy public synchronized byte[] enhance(String className, byte[] originalBytes) throws EnhancementException { //Classpool#describe does not accept '/' in the description name as it expects a class name. See HHH-12545 final String safeClassName = className.replace( '/', '.' ); + classFileLocator.setClassNameAndBytes( safeClassName, originalBytes ); try { - final TypeDescription managedCtClass = classPool.describe( safeClassName ).resolve(); - DynamicType.Builder builder = doEnhance( - bytebuddy.getCurrentyByteBuddy().ignore( isDefaultFinalizer() ).redefine( managedCtClass, ClassFileLocator.Simple.of( safeClassName, originalBytes ) ), - managedCtClass - ); - if ( builder == null ) { - return originalBytes; - } - else { - return builder.make().getBytes(); - } + final TypeDescription typeDescription = typePool.describe( safeClassName ).resolve(); + + return byteBuddyState.rewrite( typePool, safeClassName, byteBuddy -> doEnhance( + byteBuddy.ignore( isDefaultFinalizer() ).redefine( typeDescription, ClassFileLocator.Simple.of( safeClassName, originalBytes ) ), + typeDescription + ) ); } catch (RuntimeException e) { throw new EnhancementException( "Failed to enhance class " + className, e ); } } - private TypePool buildClassPool(final ByteBuddyEnhancementContext enhancementContext) { - return TypePool.Default.WithLazyResolution.of( enhancementContext.getLoadingClassLoader() ); + private TypePool buildTypePool(final ClassFileLocator classFileLocator) { + return TypePool.Default.WithLazyResolution.of( classFileLocator ); } private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDescription managedCtClass) { @@ -134,8 +155,6 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes return null; } - PersistentAttributeTransformer transformer = PersistentAttributeTransformer.collectPersistentFields( managedCtClass, enhancementContext, classPool ); - if ( enhancementContext.isEntityClass( managedCtClass ) ) { log.infof( "Enhancing [%s] as Entity", managedCtClass.getName() ); builder = builder.implement( ManagedEntity.class ) @@ -167,102 +186,106 @@ private DynamicType.Builder doEnhance(DynamicType.Builder builder, TypeDes builder = addInterceptorHandling( builder, managedCtClass ); if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { - if ( collectCollectionFields( managedCtClass ).isEmpty() ) { + List collectionFields = collectCollectionFields( managedCtClass ); + + if ( collectionFields.isEmpty() ) { builder = builder.implement( SelfDirtinessTracker.class ) .defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC ) - .withParameters( String.class ) - .intercept( Advice.to( CodeTemplates.TrackChange.class ).wrap( StubMethod.INSTANCE ) ) + .withParameters( String.class ) + .intercept( implementationTrackChange ) .defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.GetDirtyAttributesWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationGetDirtyAttributesWithoutCollections ) .defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.AreFieldsDirtyWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationAreFieldsDirtyWithoutCollections ) .defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.ClearDirtyAttributesWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationClearDirtyAttributesWithoutCollections ) .defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC ) - .withParameters( boolean.class ) - .intercept( Advice.to( CodeTemplates.SuspendDirtyTracking.class ).wrap( StubMethod.INSTANCE ) ) + .withParameters( boolean.class ) + .intercept( implementationSuspendDirtyTracking ) .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.GetCollectionTrackerWithoutCollections.class ).wrap( StubMethod.INSTANCE ) ); + .intercept( implementationGetCollectionTrackerWithoutCollections ); } else { builder = builder.implement( ExtendedSelfDirtinessTracker.class ) .defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) .defineField( EnhancerConstants.TRACKER_COLLECTION_NAME, CollectionTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC ) - .withParameters( String.class ) - .intercept( Advice.to( CodeTemplates.TrackChange.class ).wrap( StubMethod.INSTANCE ) ) + .withParameters( String.class ) + .intercept( implementationTrackChange ) .defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.GetDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationGetDirtyAttributes ) .defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.AreFieldsDirty.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationAreFieldsDirty ) .defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC ) - .intercept( Advice.to( CodeTemplates.ClearDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( implementationClearDirtyAttributes ) .defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC ) - .withParameters( boolean.class ) - .intercept( Advice.to( CodeTemplates.SuspendDirtyTracking.class ).wrap( StubMethod.INSTANCE ) ) + .withParameters( boolean.class ) + .intercept( implementationSuspendDirtyTracking ) .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC ) - .intercept( FieldAccessor.ofField( EnhancerConstants.TRACKER_COLLECTION_NAME ) ); + .intercept( FieldAccessor.ofField( EnhancerConstants.TRACKER_COLLECTION_NAME ) ); Implementation isDirty = StubMethod.INSTANCE, getDirtyNames = StubMethod.INSTANCE, clearDirtyNames = StubMethod.INSTANCE; - for ( FieldDescription collectionField : collectCollectionFields( managedCtClass ) ) { + for ( AnnotatedFieldDescription collectionField : collectionFields ) { if ( collectionField.getType().asErasure().isAssignableTo( Map.class ) ) { isDirty = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.MapAreCollectionFieldsDirty.class ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) + .to( CodeTemplates.MapAreCollectionFieldsDirty.class, adviceLocator ) .wrap( isDirty ); getDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.MapGetCollectionFieldDirtyNames.class ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) + .to( CodeTemplates.MapGetCollectionFieldDirtyNames.class, adviceLocator ) .wrap( getDirtyNames ); clearDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.MapGetCollectionClearDirtyNames.class ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) + .to( CodeTemplates.MapGetCollectionClearDirtyNames.class, adviceLocator ) .wrap( clearDirtyNames ); } else { isDirty = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.CollectionAreCollectionFieldsDirty.class ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) + .to( CodeTemplates.CollectionAreCollectionFieldsDirty.class, adviceLocator ) .wrap( isDirty ); getDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.CollectionGetCollectionFieldDirtyNames.class ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) + .to( CodeTemplates.CollectionGetCollectionFieldDirtyNames.class, adviceLocator ) .wrap( getDirtyNames ); clearDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionField.getName() ) - .bind( CodeTemplates.FieldValue.class, collectionField ) - .to( CodeTemplates.CollectionGetCollectionClearDirtyNames.class ) + .bind( CodeTemplates.FieldValue.class, collectionField.getFieldDescription() ) + .to( CodeTemplates.CollectionGetCollectionClearDirtyNames.class, adviceLocator ) .wrap( clearDirtyNames ); } } if ( enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) { - clearDirtyNames = Advice.to( CodeTemplates.InitializeLazyAttributeLoadingInterceptor.class ).wrap( clearDirtyNames ); + clearDirtyNames = adviceInitializeLazyAttributeLoadingInterceptor.wrap( clearDirtyNames ); } builder = builder.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) .intercept( isDirty ) .defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME, void.class, Visibility.PUBLIC ) - .withParameters( DirtyTracker.class ) - .intercept( getDirtyNames ) + .withParameters( DirtyTracker.class ) + .intercept( getDirtyNames ) .defineMethod( EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME, void.class, Visibility.PUBLIC ) - .intercept( Advice.withCustomMapping().to( CodeTemplates.ClearDirtyCollectionNames.class ).wrap( StubMethod.INSTANCE ) ) + .intercept( Advice.withCustomMapping() + .to( CodeTemplates.ClearDirtyCollectionNames.class, adviceLocator ) + .wrap( StubMethod.INSTANCE ) ) .defineMethod( ExtendedSelfDirtinessTracker.REMOVE_DIRTY_FIELDS_NAME, void.class, Visibility.PUBLIC ) - .withParameters( LazyAttributeLoadingInterceptor.class ) - .intercept( clearDirtyNames ); + .withParameters( LazyAttributeLoadingInterceptor.class ) + .intercept( clearDirtyNames ); } } - return transformer.applyTo( builder, false ); + return createTransformer( managedCtClass ).applyTo( builder, false ); } else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { log.infof( "Enhancing [%s] as Composite", managedCtClass.getName() ); @@ -278,34 +301,34 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { FieldPersistence.TRANSIENT, Visibility.PRIVATE ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) .defineMethod( EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER, void.class, Visibility.PUBLIC ) - .withParameters( String.class, CompositeOwner.class ) - .intercept( Advice.to( CodeTemplates.SetOwner.class ).wrap( StubMethod.INSTANCE ) ) + .withParameters( String.class, CompositeOwner.class ) + .intercept( implementationSetOwner ) .defineMethod( EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER, void.class, Visibility.PUBLIC ) - .withParameters( String.class ) - .intercept( Advice.to( CodeTemplates.ClearOwner.class ).wrap( StubMethod.INSTANCE ) ); + .withParameters( String.class ) + .intercept( implementationClearOwner ); } - return transformer.applyTo( builder, false ); + return createTransformer( managedCtClass ).applyTo( builder, false ); } else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) { log.infof( "Enhancing [%s] as MappedSuperclass", managedCtClass.getName() ); builder = builder.implement( ManagedMappedSuperclass.class ); - return transformer.applyTo( builder, true ); + return createTransformer( managedCtClass ).applyTo( builder, true ); } else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) { log.infof( "Extended enhancement of [%s]", managedCtClass.getName() ); - return transformer.applyExtended( builder ); + return createTransformer( managedCtClass ).applyExtended( builder ); } else { log.debugf( "Skipping enhancement of [%s]: not entity or composite", managedCtClass.getName() ); @@ -313,6 +336,10 @@ else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) { } } + private PersistentAttributeTransformer createTransformer(TypeDescription typeDescription) { + return PersistentAttributeTransformer.collectPersistentFields( typeDescription, enhancementContext, typePool ); + } + // See HHH-10977 HHH-11284 HHH-11404 --- check for declaration of Managed interface on the class, not inherited private boolean alreadyEnhanced(TypeDescription managedCtClass) { for ( TypeDescription.Generic declaredInterface : managedCtClass.getInterfaces() ) { @@ -348,26 +375,28 @@ private static DynamicType.Builder addFieldWithGetterAndSetter( String fieldName, String getterName, String setterName) { - return builder.defineField( fieldName, type, Visibility.PRIVATE, FieldPersistence.TRANSIENT ) - .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) + return builder + .defineField( fieldName, type, Visibility.PRIVATE, FieldPersistence.TRANSIENT ) + .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() ) .defineMethod( getterName, type, Visibility.PUBLIC ) - .intercept( FieldAccessor.ofField( fieldName ) ) + .intercept( FieldAccessor.ofField( fieldName ) ) .defineMethod( setterName, void.class, Visibility.PUBLIC ) - .withParameters( type ) - .intercept( FieldAccessor.ofField( fieldName ) ); + .withParameters( type ) + .intercept( FieldAccessor.ofField( fieldName ) ); } - private List collectCollectionFields(TypeDescription managedCtClass) { - List collectionList = new ArrayList<>(); + private List collectCollectionFields(TypeDescription managedCtClass) { + List collectionList = new ArrayList<>(); for ( FieldDescription ctField : managedCtClass.getDeclaredFields() ) { // skip static fields and skip fields added by enhancement if ( Modifier.isStatic( ctField.getModifiers() ) || ctField.getName().startsWith( "$$_hibernate_" ) ) { continue; } - if ( enhancementContext.isPersistentField( ctField ) && !enhancementContext.isMappedCollection( ctField ) ) { + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( enhancementContext, ctField ); + if ( enhancementContext.isPersistentField( annotatedField ) && !enhancementContext.isMappedCollection( annotatedField ) ) { if ( ctField.getType().asErasure().isAssignableTo( Collection.class ) || ctField.getType().asErasure().isAssignableTo( Map.class ) ) { - collectionList.add( ctField ); + collectionList.add( annotatedField ); } } } @@ -381,7 +410,7 @@ private List collectCollectionFields(TypeDescription managedCt return collectionList; } - private Collection collectInheritCollectionFields(TypeDefinition managedCtClass) { + private Collection collectInheritCollectionFields(TypeDefinition managedCtClass) { TypeDefinition managedCtSuperclass = managedCtClass.getSuperClass(); if ( managedCtSuperclass == null || managedCtSuperclass.represents( Object.class ) ) { return Collections.emptyList(); @@ -390,13 +419,14 @@ private Collection collectInheritCollectionFields(TypeDefiniti if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass.asErasure() ) ) { return collectInheritCollectionFields( managedCtSuperclass.asErasure() ); } - List collectionList = new ArrayList(); + List collectionList = new ArrayList<>(); for ( FieldDescription ctField : managedCtSuperclass.getDeclaredFields() ) { if ( !Modifier.isStatic( ctField.getModifiers() ) ) { - if ( enhancementContext.isPersistentField( ctField ) && !enhancementContext.isMappedCollection( ctField ) ) { + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( enhancementContext, ctField ); + if ( enhancementContext.isPersistentField( annotatedField ) && !enhancementContext.isMappedCollection( annotatedField ) ) { if ( ctField.getType().asErasure().isAssignableTo( Collection.class ) || ctField.getType().asErasure().isAssignableTo( Map.class ) ) { - collectionList.add( ctField ); + collectionList.add( annotatedField ); } } } @@ -409,46 +439,140 @@ static String capitalize(String value) { return Character.toUpperCase( value.charAt( 0 ) ) + value.substring( 1 ); } - static boolean isAnnotationPresent(FieldDescription fieldDescription, Class type) { - return getAnnotation( fieldDescription, type ) != null; - } + static class AnnotatedFieldDescription implements UnloadedField { + + private final ByteBuddyEnhancementContext context; + + private final FieldDescription fieldDescription; + + private AnnotationList annotations; + + private Optional getter; + + AnnotatedFieldDescription(ByteBuddyEnhancementContext context, FieldDescription fieldDescription) { + this.context = context; + this.fieldDescription = fieldDescription; + } - static AnnotationDescription.Loadable getAnnotation(FieldDescription fieldDescription, Class type) { - AnnotationDescription.Loadable access = fieldDescription.getDeclaringType().asErasure().getDeclaredAnnotations().ofType( Access.class ); - if ( access != null && access.loadSilent().value() == AccessType.PROPERTY ) { - MethodDescription getter = getterOf( fieldDescription ); + @Override + public boolean hasAnnotation(Class annotationType) { + return getAnnotations().isAnnotationPresent( annotationType ); + } + + @Override + public String toString() { + return fieldDescription.toString(); + } + + AnnotationDescription.Loadable getAnnotation(Class annotationType) { + return getAnnotations().ofType( annotationType ); + } + + String getName() { + return fieldDescription.getName(); + } + + TypeDefinition getDeclaringType() { + return fieldDescription.getDeclaringType(); + } + + Generic getType() { + return fieldDescription.getType(); + } + + InDefinedShape asDefined() { + return fieldDescription.asDefined(); + } + + String getDescriptor() { + return fieldDescription.getDescriptor(); + } + + boolean isVisibleTo(TypeDescription typeDescription) { + return fieldDescription.isVisibleTo( typeDescription ); + } + + FieldDescription getFieldDescription() { + return fieldDescription; + } + + Optional getGetter() { if ( getter == null ) { - return fieldDescription.getDeclaredAnnotations().ofType( type ); - } - else { - return getter.getDeclaredAnnotations().ofType( type ); + getter = context.resolveGetter( fieldDescription ); } + + return getter; } - else if ( access != null && access.loadSilent().value() == AccessType.FIELD ) { - return fieldDescription.getDeclaredAnnotations().ofType( type ); + + private AnnotationList getAnnotations() { + if ( annotations == null ) { + annotations = doGetAnnotations(); + } + return annotations; } - else { - MethodDescription getter = getterOf( fieldDescription ); - if ( getter != null ) { - AnnotationDescription.Loadable annotationDescription = getter.getDeclaredAnnotations().ofType( type ); - if ( annotationDescription != null ) { - return annotationDescription; + + private AnnotationList doGetAnnotations() { + AnnotationDescription.Loadable access = fieldDescription.getDeclaringType().asErasure() + .getDeclaredAnnotations().ofType( Access.class ); + if ( access != null && access.load().value() == AccessType.PROPERTY ) { + Optional getter = getGetter(); + if ( getter.isPresent() ) { + return getter.get().getDeclaredAnnotations(); } + else { + return fieldDescription.getDeclaredAnnotations(); + } + } + else if ( access != null && access.load().value() == AccessType.FIELD ) { + return fieldDescription.getDeclaredAnnotations(); + } + else { + Optional getter = getGetter(); + + // Note that the order here is important + List annotationDescriptions = new ArrayList<>(); + if ( getter.isPresent() ) { + annotationDescriptions.addAll( getter.get().getDeclaredAnnotations() ); + } + annotationDescriptions.addAll( fieldDescription.getDeclaredAnnotations() ); + + return fieldDescription.getDeclaredAnnotations(); } - return fieldDescription.getDeclaredAnnotations().ofType( type ); } } - static MethodDescription getterOf(FieldDescription persistentField) { - MethodList methodList = MethodGraph.Compiler.DEFAULT.compile( persistentField.getDeclaringType().asErasure() ) - .listNodes() - .asMethodList() - .filter( isGetter(persistentField.getName() ) ); - if ( methodList.size() == 1 ) { - return methodList.getOnly(); + private class EnhancerClassFileLocator extends ClassFileLocator.ForClassLoader { + + // The name of the class to (possibly be) transformed. + private String className; + // The explicitly resolved Resolution for the class to (possibly be) transformed. + private Resolution resolution; + + /** + * Creates a new class file locator for the given class loader. + * + * @param classLoader The class loader to query which must not be the bootstrap class loader, i.e. {@code null}. + */ + protected EnhancerClassFileLocator(ClassLoader classLoader) { + super( classLoader ); } - else { - return null; + + @Override + public Resolution locate(String className) throws IOException { + assert className != null; + if ( className.equals( this.className ) ) { + return resolution; + } + else { + return super.locate( className ); + } + } + + void setClassNameAndBytes(String className, byte[] bytes) { + assert className != null; + assert bytes != null; + this.className = className; + this.resolution = new Resolution.Explicit( bytes); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java index f543f6f87082493206ca2a9cf04fb85edc1ab2fd..3fff802b1257e1ac9d1da1accf998129a1112527 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldAccessEnhancer.java @@ -6,16 +6,18 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import static net.bytebuddy.matcher.ElementMatchers.hasDescriptor; +import static net.bytebuddy.matcher.ElementMatchers.named; + import javax.persistence.Id; -import net.bytebuddy.description.method.MethodList; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancementException; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import net.bytebuddy.asm.AsmVisitorWrapper; -import net.bytebuddy.description.field.FieldDescription; import net.bytebuddy.description.field.FieldList; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; @@ -25,10 +27,9 @@ import net.bytebuddy.jar.asm.Type; import net.bytebuddy.pool.TypePool; -import static net.bytebuddy.matcher.ElementMatchers.hasDescriptor; -import static net.bytebuddy.matcher.ElementMatchers.named; +import java.util.Objects; -class FieldAccessEnhancer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { +final class FieldAccessEnhancer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { private static final CoreMessageLogger log = CoreLogging.messageLogger( FieldAccessEnhancer.class ); @@ -61,13 +62,13 @@ public void visitFieldInsn(int opcode, String owner, String name, String desc) { return; } - FieldDescription field = findField( owner, name, desc ); + AnnotatedFieldDescription field = findField( owner, name, desc ); if ( ( enhancementContext.isEntityClass( field.getDeclaringType().asErasure() ) || enhancementContext.isCompositeClass( field.getDeclaringType().asErasure() ) ) && !field.getType().asErasure().equals( managedCtClass ) && enhancementContext.isPersistentField( field ) - && !EnhancerImpl.isAnnotationPresent( field, Id.class ) + && !field.hasAnnotation( Id.class ) && !field.getName().equals( "this$0" ) ) { log.debugf( @@ -108,7 +109,7 @@ public void visitFieldInsn(int opcode, String owner, String name, String desc) { }; } - private FieldDescription findField(String owner, String name, String desc) { + private AnnotatedFieldDescription findField(String owner, String name, String desc) { //Classpool#describe does not accept '/' in the description name as it expects a class name final String cleanedOwner = owner.replace( '/', '.' ); final TypePool.Resolution resolution = classPool.describe( cleanedOwner ); @@ -128,6 +129,24 @@ private FieldDescription findField(String owner, String name, String desc) { ); throw new EnhancementException( msg ); } - return fields.getOnly(); + return new AnnotatedFieldDescription( enhancementContext, fields.getOnly() ); + } + + @Override + public boolean equals(final Object o) { + if ( this == o ) { + return true; + } + if ( o == null || FieldAccessEnhancer.class != o.getClass() ) { + return false; + } + final FieldAccessEnhancer that = (FieldAccessEnhancer) o; + return Objects.equals( managedCtClass, that.managedCtClass ); } + + @Override + public int hashCode() { + return managedCtClass.hashCode(); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java index 5098c04290f6f4193963ed9fb0221706e28d4586..56b12d8eac5237b47179e66e5a67c0e69a0f16c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java @@ -6,6 +6,9 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import java.util.Objects; + +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.engine.spi.PersistentAttributeInterceptor; @@ -24,17 +27,17 @@ abstract class FieldReaderAppender implements ByteCodeAppender { protected final TypeDescription managedCtClass; - protected final FieldDescription persistentField; + protected final AnnotatedFieldDescription persistentField; protected final FieldDescription.InDefinedShape persistentFieldAsDefined; - private FieldReaderAppender(TypeDescription managedCtClass, FieldDescription persistentField) { + private FieldReaderAppender(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { this.managedCtClass = managedCtClass; this.persistentField = persistentField; this.persistentFieldAsDefined = persistentField.asDefined(); } - static ByteCodeAppender of(TypeDescription managedCtClass, FieldDescription persistentField) { + static ByteCodeAppender of(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { if ( !persistentField.isVisibleTo( managedCtClass ) ) { return new MethodDispatching( managedCtClass, persistentField ); } @@ -117,7 +120,7 @@ public Size apply( private static class FieldWriting extends FieldReaderAppender { - private FieldWriting(TypeDescription managedCtClass, FieldDescription persistentField) { + private FieldWriting(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { super( managedCtClass, persistentField ); } @@ -144,7 +147,7 @@ protected void fieldWrite(MethodVisitor methodVisitor) { private static class MethodDispatching extends FieldReaderAppender { - private MethodDispatching(TypeDescription managedCtClass, FieldDescription persistentField) { + private MethodDispatching(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { super( managedCtClass, persistentField ); } @@ -170,4 +173,24 @@ protected void fieldWrite(MethodVisitor methodVisitor) { ); } } + + @Override + public boolean equals(final Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + final FieldReaderAppender that = (FieldReaderAppender) o; + return Objects.equals( managedCtClass, that.managedCtClass ) && + Objects.equals( persistentField, that.persistentField ) && + Objects.equals( persistentFieldAsDefined, that.persistentFieldAsDefined ); + } + + @Override + public int hashCode() { + return Objects.hash( managedCtClass, persistentField, persistentFieldAsDefined ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java index 9ab725d62fdd7ae31f0a7f80d555abcb7a5b7ef3..d666ae43b10de9ccb8328cff39bf8149056472c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java @@ -6,6 +6,7 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.engine.spi.PersistentAttributeInterceptor; @@ -31,7 +32,7 @@ private FieldWriterAppender(TypeDescription managedCtClass, FieldDescription.InD this.persistentFieldAsDefined = persistentFieldAsDefined; } - static ByteCodeAppender of(TypeDescription managedCtClass, FieldDescription persistentField) { + static ByteCodeAppender of(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { if ( !persistentField.isVisibleTo( managedCtClass ) ) { return new MethodDispatching( managedCtClass, persistentField.asDefined() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java index 28b3a83480f05dd4dd830f3203d035987b473e23..34e2bb30cf50c1026fc45315a2880f2d6063a41e 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java @@ -8,10 +8,12 @@ import java.util.Collection; import java.util.Objects; + import javax.persistence.Embedded; import javax.persistence.EmbeddedId; import javax.persistence.Id; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import net.bytebuddy.ClassFileVersion; @@ -27,7 +29,7 @@ import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Type; -class InlineDirtyCheckingHandler implements Implementation, ByteCodeAppender { +final class InlineDirtyCheckingHandler implements Implementation, ByteCodeAppender { private final Implementation delegate; @@ -44,25 +46,26 @@ private InlineDirtyCheckingHandler(Implementation delegate, TypeDescription mana static Implementation wrap( TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext, - FieldDescription persistentField, + AnnotatedFieldDescription persistentField, Implementation implementation) { if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { if ( enhancementContext.isCompositeClass( managedCtClass ) ) { implementation = Advice.to( CodeTemplates.CompositeDirtyCheckingHandler.class ).wrap( implementation ); } - else if ( !EnhancerImpl.isAnnotationPresent( persistentField, Id.class ) - && !EnhancerImpl.isAnnotationPresent( persistentField, EmbeddedId.class ) + else if ( !persistentField.hasAnnotation( Id.class ) + && !persistentField.hasAnnotation( EmbeddedId.class ) && !( persistentField.getType().asErasure().isAssignableTo( Collection.class ) && enhancementContext.isMappedCollection( persistentField ) ) ) { - implementation = new InlineDirtyCheckingHandler( implementation, managedCtClass, persistentField.asDefined() ); + implementation = new InlineDirtyCheckingHandler( implementation, managedCtClass, + persistentField.asDefined() ); } if ( enhancementContext.isCompositeClass( persistentField.getType().asErasure() ) - && EnhancerImpl.isAnnotationPresent( persistentField, Embedded.class ) ) { + && persistentField.hasAnnotation( Embedded.class ) ) { implementation = Advice.withCustomMapping() - .bind( CodeTemplates.FieldValue.class, persistentField ) + .bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() ) .bind( CodeTemplates.FieldName.class, persistentField.getName() ) .to( CodeTemplates.CompositeFieldDirtyCheckingHandler.class ) .wrap( implementation ); @@ -151,4 +154,23 @@ else if ( persistentField.getType().represents( double.class ) ) { } return new Size( 1 + 2 * persistentField.getType().asErasure().getStackSize().getSize(), instrumentedMethod.getStackSize() ); } + + @Override + public boolean equals(final Object o) { + if ( this == o ) { + return true; + } + if ( o == null || InlineDirtyCheckingHandler.class != o.getClass() ) { + return false; + } + final InlineDirtyCheckingHandler that = (InlineDirtyCheckingHandler) o; + return Objects.equals( delegate, that.delegate ) && + Objects.equals( managedCtClass, that.managedCtClass ) && + Objects.equals( persistentField, that.persistentField ); + } + + @Override + public int hashCode() { + return Objects.hash( delegate, managedCtClass, persistentField ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java index 69f5da9caac416134daf099a6bc694896cb58fa6..3b9eb4114b020d52e0d266803394dd6f17fbaba2 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java @@ -6,21 +6,24 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.not; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; + import javax.persistence.Embedded; -import net.bytebuddy.description.field.FieldList; -import net.bytebuddy.description.method.MethodList; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.engine.spi.CompositeOwner; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import net.bytebuddy.ClassFileVersion; import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.AsmVisitorWrapper; import net.bytebuddy.description.field.FieldDescription; @@ -36,28 +39,28 @@ import net.bytebuddy.jar.asm.MethodVisitor; import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Type; +import net.bytebuddy.matcher.ElementMatcher.Junction; import net.bytebuddy.pool.TypePool; -import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; -import static net.bytebuddy.matcher.ElementMatchers.not; - -class PersistentAttributeTransformer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { +final class PersistentAttributeTransformer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { private static final CoreMessageLogger log = CoreLogging.messageLogger( PersistentAttributeTransformer.class ); + private static final Junction NOT_HIBERNATE_GENERATED = not( nameStartsWith( "$$_hibernate_" ) ); + private final TypeDescription managedCtClass; private final ByteBuddyEnhancementContext enhancementContext; private final TypePool classPool; - private final FieldDescription[] enhancedFields; + private final AnnotatedFieldDescription[] enhancedFields; private PersistentAttributeTransformer( TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext, TypePool classPool, - FieldDescription[] enhancedFields) { + AnnotatedFieldDescription[] enhancedFields) { this.managedCtClass = managedCtClass; this.enhancementContext = enhancementContext; this.classPool = classPool; @@ -68,14 +71,15 @@ public static PersistentAttributeTransformer collectPersistentFields( TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext, TypePool classPool) { - List persistentFieldList = new ArrayList(); + List persistentFieldList = new ArrayList<>(); for ( FieldDescription ctField : managedCtClass.getDeclaredFields() ) { // skip static fields and skip fields added by enhancement and outer reference in inner classes if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) { continue; } - if ( !ctField.isStatic() && enhancementContext.isPersistentField( ctField ) ) { - persistentFieldList.add( ctField ); + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( enhancementContext, ctField ); + if ( !ctField.isStatic() && enhancementContext.isPersistentField( annotatedField ) ) { + persistentFieldList.add( annotatedField ); } } // HHH-10646 Add fields inherited from @MappedSuperclass @@ -84,12 +88,12 @@ public static PersistentAttributeTransformer collectPersistentFields( persistentFieldList.addAll( collectInheritPersistentFields( managedCtClass, enhancementContext ) ); } - FieldDescription[] orderedFields = enhancementContext.order( persistentFieldList.toArray( new FieldDescription[0] ) ); + AnnotatedFieldDescription[] orderedFields = enhancementContext.order( persistentFieldList.toArray( new AnnotatedFieldDescription[0] ) ); log.debugf( "Persistent fields for entity %s: %s", managedCtClass.getName(), Arrays.toString( orderedFields ) ); return new PersistentAttributeTransformer( managedCtClass, enhancementContext, classPool, orderedFields ); } - private static Collection collectInheritPersistentFields( + private static Collection collectInheritPersistentFields( TypeDefinition managedCtClass, ByteBuddyEnhancementContext enhancementContext) { if ( managedCtClass == null || managedCtClass.represents( Object.class ) ) { @@ -97,18 +101,24 @@ private static Collection collectInheritPersistentFields( } TypeDefinition managedCtSuperclass = managedCtClass.getSuperClass(); - if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass.asErasure() ) ) { + if ( enhancementContext.isEntityClass( managedCtSuperclass.asErasure() ) ) { + return Collections.emptyList(); + } + else if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass.asErasure() ) ) { return collectInheritPersistentFields( managedCtSuperclass, enhancementContext ); } + log.debugf( "Found @MappedSuperclass %s to collectPersistenceFields", managedCtSuperclass ); - List persistentFieldList = new ArrayList(); + + List persistentFieldList = new ArrayList<>(); for ( FieldDescription ctField : managedCtSuperclass.getDeclaredFields() ) { if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) { continue; } - if ( !ctField.isStatic() && enhancementContext.isPersistentField( ctField ) ) { - persistentFieldList.add( ctField ); + AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( enhancementContext, ctField ); + if ( !ctField.isStatic() && enhancementContext.isPersistentField( annotatedField ) ) { + persistentFieldList.add( annotatedField ); } } persistentFieldList.addAll( collectInheritPersistentFields( managedCtSuperclass, enhancementContext ) ); @@ -155,7 +165,7 @@ public void visitFieldInsn(int opcode, String owner, String name, String desc) { } private boolean isEnhanced(String owner, String name, String desc) { - for ( FieldDescription enhancedField : enhancedFields ) { + for ( AnnotatedFieldDescription enhancedField : enhancedFields ) { if ( enhancedField.getName().equals( name ) && enhancedField.getDescriptor().equals( desc ) && enhancedField.getDeclaringType().asErasure().getInternalName().equals( owner ) ) { @@ -168,8 +178,8 @@ private boolean isEnhanced(String owner, String name, String desc) { DynamicType.Builder applyTo(DynamicType.Builder builder, boolean accessor) { boolean compositeOwner = false; - builder = builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().method( not( nameStartsWith( "$$_hibernate_" ) ), this ) ); - for ( FieldDescription enhancedField : enhancedFields ) { + builder = builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().invokable( NOT_HIBERNATE_GENERATED, this ) ); + for ( AnnotatedFieldDescription enhancedField : enhancedFields ) { builder = builder .defineMethod( EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + enhancedField.getName(), @@ -193,7 +203,7 @@ DynamicType.Builder applyTo(DynamicType.Builder builder, boolean accessor) if ( !compositeOwner && !accessor - && EnhancerImpl.isAnnotationPresent( enhancedField, Embedded.class ) + && enhancedField.hasAnnotation( Embedded.class ) && enhancementContext.isCompositeClass( enhancedField.getType().asErasure() ) && enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { compositeOwner = true; @@ -217,7 +227,7 @@ DynamicType.Builder applyTo(DynamicType.Builder builder, boolean accessor) return builder; } - private Implementation fieldReader(FieldDescription enhancedField) { + private Implementation fieldReader(AnnotatedFieldDescription enhancedField) { if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) || !enhancementContext.isLazyLoadable( enhancedField ) ) { if ( enhancedField.getDeclaringType().asErasure().equals( managedCtClass ) ) { return FieldAccessor.ofField( enhancedField.getName() ).in( enhancedField.getDeclaringType().asErasure() ); @@ -231,7 +241,7 @@ private Implementation fieldReader(FieldDescription enhancedField) { } } - private Implementation fieldWriter(FieldDescription enhancedField) { + private Implementation fieldWriter(AnnotatedFieldDescription enhancedField) { Implementation implementation; if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) || !enhancementContext.isLazyLoadable( enhancedField ) ) { if ( enhancedField.getDeclaringType().asErasure().equals( managedCtClass ) ) { @@ -250,16 +260,16 @@ private Implementation fieldWriter(FieldDescription enhancedField) { DynamicType.Builder applyExtended(DynamicType.Builder builder) { AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper enhancer = new FieldAccessEnhancer( managedCtClass, enhancementContext, classPool ); - return builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().method( not( nameStartsWith( "$$_hibernate_" ) ), enhancer ) ); + return builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().invokable( NOT_HIBERNATE_GENERATED, enhancer ) ); } private static class FieldMethodReader implements ByteCodeAppender { private final TypeDescription managedCtClass; - private final FieldDescription persistentField; + private final AnnotatedFieldDescription persistentField; - private FieldMethodReader(TypeDescription managedCtClass, FieldDescription persistentField) { + private FieldMethodReader(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { this.managedCtClass = managedCtClass; this.persistentField = persistentField; } @@ -287,9 +297,9 @@ private static class FieldMethodWriter implements ByteCodeAppender { private final TypeDescription managedCtClass; - private final FieldDescription persistentField; + private final AnnotatedFieldDescription persistentField; - private FieldMethodWriter(TypeDescription managedCtClass, FieldDescription persistentField) { + private FieldMethodWriter(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { this.managedCtClass = managedCtClass; this.persistentField = persistentField; } @@ -313,4 +323,21 @@ public Size apply( return new Size( 1 + persistentField.getType().getStackSize().getSize(), instrumentedMethod.getStackSize() ); } } + + @Override + public boolean equals(final Object o) { + if ( this == o ) { + return true; + } + if ( o == null || PersistentAttributeTransformer.class != o.getClass() ) { + return false; + } + final PersistentAttributeTransformer that = (PersistentAttributeTransformer) o; + return Objects.equals( managedCtClass, that.managedCtClass ); + } + + @Override + public int hashCode() { + return managedCtClass.hashCode(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedTypeDescription.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedTypeDescription.java index ef462326912de53e8665812ac1e7333261c3e965..e2ca1bde11e4b31e017398982122bd1d7c2cd448 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedTypeDescription.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/UnloadedTypeDescription.java @@ -9,7 +9,6 @@ import java.lang.annotation.Annotation; import org.hibernate.bytecode.enhance.spi.UnloadedClass; -import org.hibernate.bytecode.enhance.spi.UnloadedField; import net.bytebuddy.description.type.TypeDescription; diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/AttributeTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/AttributeTypeDescriptor.java index ab912c6c946bd0a4a0fa761f79cdf411b9c910c7..4c4d7462bc7a28b54ba140e1afe1bdf157a04a8b 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/AttributeTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/AttributeTypeDescriptor.java @@ -8,6 +8,8 @@ import java.util.Collection; import java.util.Locale; +import java.util.Objects; + import javax.persistence.EmbeddedId; import javax.persistence.Id; @@ -20,7 +22,7 @@ /** * utility class to generate interceptor methods * @see org.hibernate.engine.spi.PersistentAttributeInterceptor - * + * * @author Luis Barreiro */ public abstract class AttributeTypeDescriptor { @@ -64,7 +66,8 @@ public String buildInLineDirtyCheckingBodyFragment(JavassistEnhancementContext c } builder.append( String.format( - " if ( !Objects.deepEquals( %s, $1 ) )", + " if ( !%s.deepEquals( %s, $1 ) )", + Objects.class.getName(), readFragment ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/PersistentAttributesEnhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/PersistentAttributesEnhancer.java index 9bba897b917c460cf497a8d45156d2becd254b7a..93988e341da7a04991ae425e7b5b9c10923e1a4e 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/PersistentAttributesEnhancer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/PersistentAttributesEnhancer.java @@ -105,7 +105,10 @@ private Collection collectInheritPersistentFields(CtClass managedCtClas try { CtClass managedCtSuperclass = managedCtClass.getSuperclass(); - if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass ) ) { + if ( enhancementContext.isEntityClass( managedCtSuperclass ) ) { + return Collections.emptyList(); + } + else if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass ) ) { return collectInheritPersistentFields( managedCtSuperclass ); } log.debugf( "Found @MappedSuperclass %s to collectPersistenceFields", managedCtSuperclass.getName() ); @@ -329,7 +332,15 @@ private void handleBiDirectionalAssociation(CtClass managedCtClass, CtField pers return; } final String mappedBy = PersistentAttributesHelper.getMappedBy( persistentField, targetEntity, enhancementContext ); - if ( mappedBy == null || mappedBy.isEmpty() ) { + String bidirectionalAttributeName; + if ( mappedBy == null ) { + bidirectionalAttributeName = PersistentAttributesHelper.getMappedByFromTargetEntity( persistentField, targetEntity, enhancementContext ); + } + else { + bidirectionalAttributeName = mappedBy; + } + + if ( bidirectionalAttributeName == null || bidirectionalAttributeName.isEmpty() ) { log.infof( "Could not find bi-directional association for field [%s#%s]", managedCtClass.getName(), @@ -339,57 +350,97 @@ private void handleBiDirectionalAssociation(CtClass managedCtClass, CtField pers } // create a temporary getter and setter on the target entity to be able to compile our code - final String mappedByGetterName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + mappedBy; - final String mappedBySetterName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + mappedBy; + final String getterName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + persistentField.getName(); + final String bidirectionalGetterName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + bidirectionalAttributeName; + final String bidirectionalSetterName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + bidirectionalAttributeName; CtMethod getter; CtMethod setter; boolean tmpTargetMethods = false; try { - getter = targetEntity.getDeclaredMethod( mappedByGetterName ); - setter = targetEntity.getDeclaredMethod( mappedByGetterName ); + getter = targetEntity.getDeclaredMethod( bidirectionalGetterName ); + setter = targetEntity.getDeclaredMethod( bidirectionalGetterName ); } catch ( NotFoundException nfe ) { - getter = MethodWriter.addGetter( targetEntity, mappedBy, mappedByGetterName ); - setter = MethodWriter.addSetter( targetEntity, mappedBy, mappedBySetterName ); + getter = MethodWriter.addGetter( targetEntity, bidirectionalAttributeName, bidirectionalGetterName ); + setter = MethodWriter.addSetter( targetEntity, bidirectionalAttributeName, bidirectionalSetterName ); tmpTargetMethods = true; } // code fragments to check loaded state. We don't want to trigger lazy loading in association management code + String getterSelfNotNull = String.format( + "this.%s() != null", + getterName + ); String currentAssociationLoaded = String.format( + "%s.isInitialized(this.%s)", + Hibernate.class.getName(), + persistentField.getName() + ); + String targetElementLoaded = String.format( + "%s.isInitialized(target)", + Hibernate.class.getName() + ); + String newAssociationLoaded = String.format( + "%s.isInitialized($1)", + Hibernate.class.getName() + ); + String currentAssociationInverseLoaded = String.format( "%s.isPropertyInitialized(this.%s, \"%s\")", Hibernate.class.getName(), persistentField.getName(), - mappedBy + bidirectionalAttributeName ); - String targetElementLoaded = String.format( + String targetElementInverseLoaded = String.format( "%s.isPropertyInitialized(target, \"%s\")", Hibernate.class.getName(), - mappedBy + bidirectionalAttributeName ); - String newAssociationLoaded = String.format( + String newAssociationInverseLoaded = String.format( "%s.isPropertyInitialized($1, \"%s\")", Hibernate.class.getName(), - mappedBy + bidirectionalAttributeName ); + final boolean inverseSide = mappedBy != null; if ( PersistentAttributesHelper.hasAnnotation( persistentField, OneToOne.class ) ) { - // only unset when $1 != null to avoid recursion - fieldWriter.insertBefore( - String.format( - " if (this.%1$s != null && %2$s && $1 != null) { this.%1$s.%3$s(null); }%n", - persistentField.getName(), - currentAssociationLoaded, - mappedBySetterName - ) - ); - fieldWriter.insertAfter( - String.format( - " if ($1 != null && %s && $1.%s() != this) { $1.%s(this); }%n", - newAssociationLoaded, - mappedByGetterName, - mappedBySetterName - ) - ); + if ( inverseSide ) { + fieldWriter.insertBefore( + String.format( + " if (%1$s) { %2$s copy = this.%3$s; this.%3$s = null; copy.%4$s(null); }%n", + getterSelfNotNull, + persistentField.getType().getName(), + persistentField.getName(), + bidirectionalSetterName + ) + ); + fieldWriter.insertAfter( + String.format( + " if ($1 != null && $1.%s() != this) { $1.%s(this); }%n", + bidirectionalGetterName, + bidirectionalSetterName + ) + ); + } + else { + fieldWriter.insertBefore( + String.format( + " if (%1$s && %2$s) { %3$s copy = this.%4$s; this.%4$s = null; copy.%5$s(null); }%n", + currentAssociationLoaded, + getterSelfNotNull, + persistentField.getType().getName(), + persistentField.getName(), + bidirectionalSetterName + ) + ); + fieldWriter.insertAfter( + String.format( + " if ($1 != null && %s.isInitialized($1) && $1.%s() != this) { $1.%s(this); }%n", + Hibernate.class.getName(), + bidirectionalGetterName, + bidirectionalSetterName + ) + ); + } } if ( PersistentAttributesHelper.hasAnnotation( persistentField, OneToMany.class ) ) { boolean isMap = PersistentAttributesHelper.isAssignable( persistentField.getType(), Map.class.getName() ); @@ -397,47 +448,87 @@ private void handleBiDirectionalAssociation(CtClass managedCtClass, CtField pers // only remove elements not in the new collection or else we would loose those elements // don't use iterator to avoid ConcurrentModException - fieldWriter.insertBefore( - String.format( - " if (this.%3$s != null && %1$s) {%n" + - " Object[] array = this.%3$s.%2$s;%n" + - " for (int i = 0; i < array.length; i++) {%n" + - " %4$s target = (%4$s) array[i];%n" + - " if ($1 == null || !$1.contains(target)) { target.%5$s(null); }%n" + - " }%n" + - " }%n", - currentAssociationLoaded, - toArrayMethod, - persistentField.getName(), - targetEntity.getName(), - mappedBySetterName - ) - ); - fieldWriter.insertAfter( - String.format( - " if ($1 != null && %1$s) {%n" + - " Object[] array = $1.%2$s;%n" + - " for (int i = 0; i < array.length; i++) {%n" + - " %4$s target = (%4$s) array[i];%n" + - " if (%3$s && target.%5$s() != this) { target.%6$s(this); }%n" + - " }%n" + - " }%n", - newAssociationLoaded, - toArrayMethod, - targetElementLoaded, - targetEntity.getName(), - mappedByGetterName, - mappedBySetterName - ) - ); + if ( inverseSide ) { + fieldWriter.insertBefore( + String.format( + " if (%1$s) {%n" + + " Object[] array = this.%3$s.%2$s;%n" + + " for (int i = 0; i < array.length; i++) {%n" + + " %4$s target = (%4$s) array[i];%n" + + " if ($1 == null || !$1.%6$s(target)) { target.%5$s(null); }%n" + + " }%n" + + " }%n", + getterSelfNotNull, + toArrayMethod, + persistentField.getName(), + targetEntity.getName(), + bidirectionalSetterName, + isMap ? "containsValue" : "contains" + ) + ); + fieldWriter.insertAfter( + String.format( + " if ($1 != null) {%n" + + " Object[] array = $1.%1$s;%n" + + " for (int i = 0; i < array.length; i++) {%n" + + " %2$s target = (%2$s) array[i];%n" + + " if (target.%3$s() != this) { target.%4$s(this); }%n" + + " }%n" + + " }%n", + toArrayMethod, + targetEntity.getName(), + bidirectionalGetterName, + bidirectionalSetterName + ) + ); + } + else { + fieldWriter.insertBefore( + String.format( + " if (%1$s && %2$s) {%n" + + " Object[] array = this.%4$s.%3$s;%n" + + " for (int i = 0; i < array.length; i++) {%n" + + " %5$s target = (%5$s) array[i];%n" + + " if (%7$s.isInitialized(target) && ($1 == null || !$1.%8$s(target))) { target.%6$s(null); }%n" + + " }%n" + + " }%n", + currentAssociationLoaded, + getterSelfNotNull, + toArrayMethod, + persistentField.getName(), + targetEntity.getName(), + bidirectionalSetterName, + Hibernate.class.getName(), + isMap ? "containsValue" : "contains" + ) + ); + fieldWriter.insertAfter( + String.format( + " if ($1 != null && %1$s) {%n" + + " Object[] array = $1.%2$s;%n" + + " for (int i = 0; i < array.length; i++) {%n" + + " %4$s target = (%4$s) array[i];%n" + + " if (%3$s && target.%5$s() != this) { target.%6$s(this); }%n" + + " }%n" + + " }%n", + newAssociationLoaded, + toArrayMethod, + targetElementLoaded, + targetEntity.getName(), + bidirectionalGetterName, + bidirectionalSetterName + ) + ); + } } if ( PersistentAttributesHelper.hasAnnotation( persistentField, ManyToOne.class ) ) { fieldWriter.insertBefore( String.format( - " if (this.%2$s != null && %1$s && this.%2$s.%3$s() != null) { this.%2$s.%3$s().remove(this); }%n", - currentAssociationLoaded, + " if (%1$s && %2$s) { java.util.Collection c = this.%3$s.%4$s(); if (c != null) { c.remove(this); } }%n", + getterSelfNotNull, + currentAssociationInverseLoaded, persistentField.getName(), - mappedByGetterName + bidirectionalGetterName ) ); // check .contains(this) to avoid double inserts (but preventing duplicates) @@ -447,14 +538,14 @@ private void handleBiDirectionalAssociation(CtClass managedCtClass, CtField pers " java.util.Collection c = $1.%s();%n" + " if (c != null && !c.contains(this)) { c.add(this); }%n" + " }%n", - newAssociationLoaded, - mappedByGetterName + newAssociationInverseLoaded, + bidirectionalGetterName ) ); } if ( PersistentAttributesHelper.hasAnnotation( persistentField, ManyToMany.class ) ) { if ( PersistentAttributesHelper.isAssignable( persistentField.getType(), Map.class.getName() ) || - PersistentAttributesHelper.isAssignable( targetEntity.getField( mappedBy ).getType(), Map.class.getName() ) ) { + PersistentAttributesHelper.isAssignable( targetEntity.getField( bidirectionalAttributeName ).getType(), Map.class.getName() ) ) { log.infof( "Bi-directional association for field [%s#%s] not managed: @ManyToMany in java.util.Map attribute not supported ", managedCtClass.getName(), @@ -462,40 +553,76 @@ private void handleBiDirectionalAssociation(CtClass managedCtClass, CtField pers ); return; } - fieldWriter.insertBefore( - String.format( - " if (this.%2$s != null && %1$s) {%n" + - " Object[] array = this.%2$s.toArray();%n" + - " for (int i = 0; i < array.length; i++) {%n" + - " %3$s target = (%3$s) array[i];%n" + - " if ($1 == null || !$1.contains(target)) { target.%4$s().remove(this); }%n" + - " }%n" + - " }%n", - currentAssociationLoaded, - persistentField.getName(), - targetEntity.getName(), - mappedByGetterName - ) - ); - fieldWriter.insertAfter( - String.format( - " if ($1 != null && %s) {%n" + - " Object[] array = $1.toArray();%n" + - " for (int i = 0; i < array.length; i++) {%n" + - " %s target = (%s) array[i];%n" + - " if (%s) {%n" + - " java.util.Collection c = target.%s();%n" + - " if (c != this && c != null) { c.add(this); }%n" + - " }%n" + - " }%n" + - " }%n", - newAssociationLoaded, - targetEntity.getName(), - targetEntity.getName(), - targetElementLoaded, - mappedByGetterName - ) - ); + if ( inverseSide ) { + fieldWriter.insertBefore( + String.format( + " if (%1$s) {%n" + + " Object[] array = this.%2$s.toArray();%n" + + " for (int i = 0; i < array.length; i++) {%n" + + " %3$s target = (%3$s) array[i];%n" + + " if ($1 == null || !$1.contains(target)) { target.%4$s().remove(this); }%n" + + " }%n" + + " }%n", + getterSelfNotNull, + persistentField.getName(), + targetEntity.getName(), + bidirectionalGetterName + ) + ); + fieldWriter.insertAfter( + String.format( + " if ($1 != null) {%n" + + " Object[] array = $1.toArray();%n" + + " for (int i = 0; i < array.length; i++) {%n" + + " %s target = (%s) array[i];%n" + + " java.util.Collection c = target.%s();%n" + + " if (c != null && !c.contains(this)) { c.add(this); }%n" + + " }%n" + + " }%n", + targetEntity.getName(), + targetEntity.getName(), + bidirectionalGetterName + ) + ); + } + else { + fieldWriter.insertBefore( + String.format( + " if (%1$s && %2$s) {%n" + + " Object[] array = this.%3$s.toArray();%n" + + " for (int i = 0; i < array.length; i++) {%n" + + " %4$s target = (%4$s) array[i];%n" + + " if (%5$s && ($1 == null || !$1.contains(target))) { target.%6$s().remove(this); }%n" + + " }%n" + + " }%n", + currentAssociationLoaded, + getterSelfNotNull, + persistentField.getName(), + targetEntity.getName(), + targetElementInverseLoaded, + bidirectionalGetterName + ) + ); + fieldWriter.insertAfter( + String.format( + " if ($1 != null && %s) {%n" + + " Object[] array = $1.toArray();%n" + + " for (int i = 0; i < array.length; i++) {%n" + + " %s target = (%s) array[i];%n" + + " if (%s) {%n" + + " java.util.Collection c = target.%s();%n" + + " if (c != null && !c.contains(this)) { c.add(this); }%n" + + " }%n" + + " }%n" + + " }%n", + newAssociationLoaded, + targetEntity.getName(), + targetEntity.getName(), + targetElementInverseLoaded, + bidirectionalGetterName + ) + ); + } } // implementation note: association management @OneToMany and @ManyToMay works for add() operations but for remove() a snapshot of the collection is needed so we know what associations to break. // another approach that could force that behavior would be to return Collections.unmodifiableCollection() ... diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/PersistentAttributesHelper.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/PersistentAttributesHelper.java index ab654c551d376cc72cef0299c5d7fb8056ac1a61..e414f29c5acc4a44e6258e02b490752f7eb5b2c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/PersistentAttributesHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/PersistentAttributesHelper.java @@ -208,8 +208,11 @@ public static boolean isPossibleBiDirectionalAssociation(CtField persistentField } public static String getMappedBy(CtField persistentField, CtClass targetEntity, JavassistEnhancementContext context) throws NotFoundException { - final String local = getMappedByFromAnnotation( persistentField ); - return local.isEmpty() ? getMappedByFromTargetEntity( persistentField, targetEntity, context ) : local; + final String mappedBy = getMappedByFromAnnotation( persistentField ); + if ( mappedBy == null || mappedBy.isEmpty() ) { + return null; + } + return mappedBy; } private static String getMappedByFromAnnotation(CtField persistentField) { @@ -230,7 +233,7 @@ private static String getMappedByFromAnnotation(CtField persistentField) { return mtm == null ? "" : mtm.mappedBy(); } - private static String getMappedByFromTargetEntity( + public static String getMappedByFromTargetEntity( CtField persistentField, CtClass targetEntity, JavassistEnhancementContext context) throws NotFoundException { diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/UnloadedCtField.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/UnloadedCtField.java index 3b61833a405111da79a308ef0383746379268fe9..1c04d176de4f350ccb3652f72a16ca3dce1571da 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/UnloadedCtField.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/javassist/UnloadedCtField.java @@ -24,4 +24,9 @@ public UnloadedCtField(CtField ctField) { public boolean hasAnnotation(Class annotationType) { return ctField.hasAnnotation( annotationType ); } + + @Override + public String toString() { + return this.ctField.toString(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/CollectionTracker.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/CollectionTracker.java index bf52c8bf07b5637b16a1347996adba6b54c07f93..879950da3d8f5322dc58360364619618a5091fcf 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/CollectionTracker.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/CollectionTracker.java @@ -7,7 +7,7 @@ package org.hibernate.bytecode.enhance.spi; /** - * Interface to be implemented by collection trackers that hold the expected size od collections, a simplified Map. + * Interface to be implemented by collection trackers that hold the expected size od collections, a simplified {@code Map}. * * @author Luis Barreiro */ diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java index 2f8ddce0b87993b8d20e5aae92e112fc3ee9aa64..4e5da5c87d05c69fddb068a0bf7b113552ad49e9 100755 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/LazyPropertyInitializer.java @@ -7,8 +7,10 @@ package org.hibernate.bytecode.enhance.spi; import java.io.Serializable; +import java.util.Collections; import java.util.Set; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -32,9 +34,18 @@ public Object readResolve() { } }; + /** + * @deprecated Prefer the form of these methods defined on + * {@link BytecodeLazyAttributeInterceptor} instead + */ + @Deprecated interface InterceptorImplementor { - Set getInitializedLazyAttributeNames(); - void attributeInitialized(String name); + default Set getInitializedLazyAttributeNames() { + return Collections.emptySet(); + } + + default void attributeInitialized(String name) { + } } /** diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..a201fdc31682baa8fe38663e58f261d00dad6ea8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractInterceptor.java @@ -0,0 +1,160 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractInterceptor implements SessionAssociableInterceptor { + private final String entityName; + + private transient SharedSessionContractImplementor session; + private boolean allowLoadOutsideTransaction; + private String sessionFactoryUuid; + + @SuppressWarnings("WeakerAccess") + public AbstractInterceptor(String entityName) { + this.entityName = entityName; + } + + public String getEntityName() { + return entityName; + } + + @Override + public SharedSessionContractImplementor getLinkedSession() { + return session; + } + + @Override + public void setSession(SharedSessionContractImplementor session) { + this.session = session; + if ( session != null && !allowLoadOutsideTransaction ) { + this.allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled(); + if ( this.allowLoadOutsideTransaction ) { + this.sessionFactoryUuid = session.getFactory().getUuid(); + } + } + } + + @Override + public void unsetSession() { + this.session = null; + } + + @Override + public boolean allowLoadOutsideTransaction() { + return allowLoadOutsideTransaction; + } + + @Override + public String getSessionFactoryUuid() { + return sessionFactoryUuid; + } + + /** + * Handle the case of reading an attribute. The result is what is returned to the caller + */ + protected abstract Object handleRead(Object target, String attributeName, Object value); + + /** + * Handle the case of writing an attribute. The result is what is set as the entity state + */ + protected abstract Object handleWrite(Object target, String attributeName, Object oldValue, Object newValue); + + @Override + public boolean readBoolean(Object obj, String name, boolean oldValue) { + return (Boolean) handleRead( obj, name, oldValue ); + } + + @Override + public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { + return (Boolean) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public byte readByte(Object obj, String name, byte oldValue) { + return (Byte) handleRead( obj, name, oldValue ); + } + + @Override + public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { + return (Byte) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public char readChar(Object obj, String name, char oldValue) { + return (Character) handleRead( obj, name, oldValue ); + } + + @Override + public char writeChar(Object obj, String name, char oldValue, char newValue) { + return (char) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public short readShort(Object obj, String name, short oldValue) { + return (Short) handleRead( obj, name, oldValue ); + } + + @Override + public short writeShort(Object obj, String name, short oldValue, short newValue) { + return (Short) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public int readInt(Object obj, String name, int oldValue) { + return (Integer) handleRead( obj, name, oldValue ); + } + + @Override + public int writeInt(Object obj, String name, int oldValue, int newValue) { + return (Integer) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public float readFloat(Object obj, String name, float oldValue) { + return (Float) handleRead( obj, name, oldValue ); + } + + @Override + public float writeFloat(Object obj, String name, float oldValue, float newValue) { + return (Float) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public double readDouble(Object obj, String name, double oldValue) { + return (Double) handleRead( obj, name, oldValue ); + } + + @Override + public double writeDouble(Object obj, String name, double oldValue, double newValue) { + return (Double) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public long readLong(Object obj, String name, long oldValue) { + return (Long) handleRead( obj, name, oldValue ); + } + + @Override + public long writeLong(Object obj, String name, long oldValue, long newValue) { + return (Long) handleWrite( obj, name, oldValue, newValue ); + } + + @Override + public Object readObject(Object obj, String name, Object oldValue) { + return handleRead( obj, name, oldValue ); + } + + @Override + public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { + return handleWrite( obj, name, oldValue, newValue ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractLazyLoadInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractLazyLoadInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..3eb53f0261a4d0555ac61c16fde297b0ebd5d64c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/AbstractLazyLoadInterceptor.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractLazyLoadInterceptor extends AbstractInterceptor implements BytecodeLazyAttributeInterceptor { + + @SuppressWarnings("unused") + public AbstractLazyLoadInterceptor(String entityName) { + super( entityName ); + } + + @SuppressWarnings("WeakerAccess") + public AbstractLazyLoadInterceptor(String entityName, SharedSessionContractImplementor session) { + super( entityName ); + setSession( session ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..ae92635ea7360ce8bd440af1dffb6972cc646175 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import java.util.Set; + +import org.hibernate.Incubating; + +/** + * @author Steve Ebersole + */ +@Incubating +public interface BytecodeLazyAttributeInterceptor extends SessionAssociableInterceptor { + /** + * The name of the entity this interceptor is meant to intercept + */ + String getEntityName(); + + /** + * The id of the entity instance this interceptor is associated with + */ + Object getIdentifier(); + + /** + * The names of all lazy attributes which have been initialized + */ + @Override + Set getInitializedLazyAttributeNames(); + + /** + * Callback from the enhanced class that an attribute has been read or written + */ + void attributeInitialized(String name); + + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..697f79a1d46746b935d6f3439e9a12aa1409d2b0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java @@ -0,0 +1,291 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.EntityMode; +import org.hibernate.HibernateException; +import org.hibernate.LockMode; +import org.hibernate.bytecode.BytecodeLogger; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.SelfDirtinessTracker; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.Status; +import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.tuple.entity.EntityTuplizer; +import org.hibernate.type.CompositeType; +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public class EnhancementAsProxyLazinessInterceptor extends AbstractLazyLoadInterceptor { + private final Set identifierAttributeNames; + private final CompositeType nonAggregatedCidMapper; + + private final EntityKey entityKey; + + private final boolean inLineDirtyChecking; + private Set writtenFieldNames; + + private boolean initialized; + + private boolean initializeBeforeWrite; + + public EnhancementAsProxyLazinessInterceptor( + String entityName, + Set identifierAttributeNames, + CompositeType nonAggregatedCidMapper, + EntityKey entityKey, + SharedSessionContractImplementor session) { + super( entityName, session ); + + this.identifierAttributeNames = identifierAttributeNames; + assert identifierAttributeNames != null; + + this.nonAggregatedCidMapper = nonAggregatedCidMapper; + assert nonAggregatedCidMapper != null || identifierAttributeNames.size() == 1; + + this.entityKey = entityKey; + + final EntityPersister entityPersister = session.getFactory().getMetamodel().entityPersister( entityName ); + this.inLineDirtyChecking = entityPersister.getEntityMode() == EntityMode.POJO + && SelfDirtinessTracker.class.isAssignableFrom( entityPersister.getMappedClass() ); + // if self-dirty tracking is enabled but DynamicUpdate is not enabled then we need to initialise the entity + // because the pre-computed update statement contains even not dirty properties and so we need all the values + initializeBeforeWrite = !inLineDirtyChecking || !entityPersister.getEntityMetamodel().isDynamicUpdate(); + } + + public EntityKey getEntityKey() { + return entityKey; + } + + @Override + protected Object handleRead(Object target, String attributeName, Object value) { + // it is illegal for this interceptor to still be attached to the entity after initialization + if ( initialized ) { + throw new IllegalStateException( "EnhancementAsProxyLazinessInterceptor interception on an initialized instance" ); + } + + // the attribute being read is an entity-id attribute + // - we already know the id, return that + if ( identifierAttributeNames.contains( attributeName ) ) { + return extractIdValue( target, attributeName ); + } + + // Use `performWork` to group together multiple Session accesses + return EnhancementHelper.performWork( + this, + (session, isTempSession) -> { + final Object[] writtenValues; + + final EntityPersister entityPersister = session.getFactory() + .getMetamodel() + .entityPersister( getEntityName() ); + final EntityTuplizer entityTuplizer = entityPersister.getEntityTuplizer(); + + if ( writtenFieldNames != null && !writtenFieldNames.isEmpty() ) { + + // enhancement has dirty-tracking available and at least one attribute was explicitly set + + if ( writtenFieldNames.contains( attributeName ) ) { + // the requested attribute was one of the attributes explicitly set, we can just return the explicitly set value + return entityTuplizer.getPropertyValue( target, attributeName ); + } + + // otherwise we want to save all of the explicitly set values in anticipation of + // the force initialization below so that we can "replay" them after the + // initialization + + writtenValues = new Object[writtenFieldNames.size()]; + + int index = 0; + for ( String writtenFieldName : writtenFieldNames ) { + writtenValues[index] = entityTuplizer.getPropertyValue( target, writtenFieldName ); + index++; + } + } + else { + writtenValues = null; + } + + final Object initializedValue = forceInitialize( + target, + attributeName, + session, + isTempSession + ); + + initialized = true; + + if ( writtenValues != null ) { + // here is the replaying of the explicitly set values we prepared above + int index = 0; + for ( String writtenFieldName : writtenFieldNames ) { + entityTuplizer.setPropertyValue( target, writtenFieldName, writtenValues[index++] ); + } + writtenFieldNames.clear(); + } + + return initializedValue; + }, + getEntityName(), + attributeName + ); + } + + private Object extractIdValue(Object target, String attributeName) { + // access to the id or part of it for non-aggregated cid + if ( nonAggregatedCidMapper == null ) { + return getIdentifier(); + } + else { + return nonAggregatedCidMapper.getPropertyValue( + target, + nonAggregatedCidMapper.getPropertyIndex( attributeName ), + getLinkedSession() + ); + } + } + + public Object forceInitialize(Object target, String attributeName) { + BytecodeLogger.LOGGER.tracef( + "EnhancementAsProxyLazinessInterceptor#forceInitialize : %s#%s -> %s )", + entityKey.getEntityName(), + entityKey.getIdentifier(), + attributeName + ); + + return EnhancementHelper.performWork( + this, + (session, isTemporarySession) -> forceInitialize( target, attributeName, session, isTemporarySession ), + getEntityName(), + attributeName + ); + } + + public Object forceInitialize(Object target, String attributeName, SharedSessionContractImplementor session, boolean isTemporarySession) { + BytecodeLogger.LOGGER.tracef( + "EnhancementAsProxyLazinessInterceptor#forceInitialize : %s#%s -> %s )", + entityKey.getEntityName(), + entityKey.getIdentifier(), + attributeName + ); + + final EntityPersister persister = session.getFactory() + .getMetamodel() + .entityPersister( getEntityName() ); + + if ( isTemporarySession ) { + // Add an entry for this entity in the PC of the temp Session + session.getPersistenceContext().addEntity( + target, + Status.READ_ONLY, + // loaded state + ArrayHelper.filledArray( + LazyPropertyInitializer.UNFETCHED_PROPERTY, + Object.class, + persister.getPropertyTypes().length + ), + entityKey, + persister.getVersion( target ), + LockMode.NONE, + // we assume an entry exists in the db + true, + persister, + true + ); + } + + return persister.initializeEnhancedEntityUsedAsProxy( + target, + attributeName, + session + ); + } + + @Override + protected Object handleWrite(Object target, String attributeName, Object oldValue, Object newValue) { + if ( initialized ) { + throw new IllegalStateException( "EnhancementAsProxyLazinessInterceptor interception on an initialized instance" ); + } + + if ( identifierAttributeNames.contains( attributeName ) ) { + // it is illegal for the identifier value to be changed. Normally Hibernate + // validates this during flush. However, here it is dangerous to just allow the + // new value to be set and continue on waiting for the flush for validation + // because this interceptor manages the entity's entry in the PC itself. So + // just do the check here up-front + final boolean changed; + if ( nonAggregatedCidMapper == null ) { + changed = ! entityKey.getPersister().getIdentifierType().isEqual( oldValue, newValue ); + } + else { + final int subAttrIndex = nonAggregatedCidMapper.getPropertyIndex( attributeName ); + final Type subAttrType = nonAggregatedCidMapper.getSubtypes()[subAttrIndex]; + changed = ! subAttrType.isEqual( oldValue, newValue ); + } + + if ( changed ) { + throw new HibernateException( + "identifier of an instance of " + entityKey.getEntityName() + " was altered from " + oldValue + " to " + newValue + ); + } + + // otherwise, setId has been called but passing in the same value - just pass it through + return newValue; + } + + if ( initializeBeforeWrite ) { + // we need to force-initialize the proxy - the fetch group to which the `attributeName` belongs + try { + forceInitialize( target, attributeName ); + } + finally { + initialized = true; + } + + if ( inLineDirtyChecking ) { + ( (SelfDirtinessTracker) target ).$$_hibernate_trackChange( attributeName ); + } + } + else { + // because of the entity being enhanced with `org.hibernate.engine.spi.SelfDirtinessTracker` + // we can skip forcing the initialization. However, in the case of a subsequent read we + // need to know which attributes had been explicitly set so that we can re-play the setters + // after the force-initialization there + if ( writtenFieldNames == null ) { + writtenFieldNames = new HashSet<>(); + } + writtenFieldNames.add( attributeName ); + } + + return newValue; + } + + @Override + public Set getInitializedLazyAttributeNames() { + return Collections.emptySet(); + } + + @Override + public void attributeInitialized(String name) { + if ( initialized ) { + throw new UnsupportedOperationException( "Expected call to EnhancementAsProxyLazinessInterceptor#attributeInitialized" ); + } + } + + @Override + public Object getIdentifier() { + return entityKey.getIdentifier(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/Helper.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java similarity index 48% rename from hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/Helper.java rename to hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java index 9459288a1739cb949c9e6ddc13eaea31e69acfda..3963212211025cdaa27548612d0967977f0fdd61 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/Helper.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java @@ -7,79 +7,108 @@ package org.hibernate.bytecode.enhance.spi.interceptor; import java.util.Locale; +import java.util.function.BiFunction; import org.hibernate.FlushMode; import org.hibernate.LazyInitializationException; +import org.hibernate.bytecode.BytecodeLogger; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.SessionFactoryRegistry; - -import org.jboss.logging.Logger; +import org.hibernate.mapping.OneToOne; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.ToOne; +import org.hibernate.mapping.Value; /** * @author Steve Ebersole */ -public class Helper { - private static final Logger log = Logger.getLogger( Helper.class ); - - interface Consumer { - SharedSessionContractImplementor getLinkedSession(); - boolean allowLoadOutsideTransaction(); - String getSessionFactoryUuid(); - } - - interface LazyInitializationWork { - T doWork(SharedSessionContractImplementor session, boolean isTemporarySession); +public class EnhancementHelper { + /** + * Should the given property be included in the owner's base fetch group? + */ + public static boolean includeInBaseFetchGroup( + Property bootMapping, + boolean isEnhanced, + boolean allowEnhancementAsProxy) { + final Value value = bootMapping.getValue(); + + if ( ! isEnhanced ) { + if ( value instanceof ToOne ) { + if ( ( (ToOne) value ).isUnwrapProxy() ) { + BytecodeLogger.LOGGER.debugf( + "To-one property `%s#%s` was mapped with LAZY + NO_PROXY but the class was not enhanced", + bootMapping.getPersistentClass().getEntityName(), + bootMapping.getName() + ); + } + } + return true; + } - // informational details - String getEntityName(); - String getAttributeName(); - } + if ( value instanceof ToOne ) { + final ToOne toOne = (ToOne) value; + if ( toOne.isLazy() ) { + if ( toOne.isUnwrapProxy() ) { + if ( toOne instanceof OneToOne ) { + return false; + } + // include it in the base fetch group so long as the config allows + // using the FK to create an "enhancement proxy" + return allowEnhancementAsProxy; + } + } - private final Consumer consumer; + return true; + } - public Helper(Consumer consumer) { - this.consumer = consumer; + return ! bootMapping.isLazy(); } - public T performWork(LazyInitializationWork lazyInitializationWork) { - SharedSessionContractImplementor session = consumer.getLinkedSession(); + public static T performWork( + BytecodeLazyAttributeInterceptor interceptor, + BiFunction work, + String entityName, + String attributeName) { + SharedSessionContractImplementor session = interceptor.getLinkedSession(); boolean isTempSession = false; boolean isJta = false; // first figure out which Session to use if ( session == null ) { - if ( consumer.allowLoadOutsideTransaction() ) { - session = openTemporarySessionForLoading( lazyInitializationWork ); + if ( interceptor.allowLoadOutsideTransaction() ) { + session = openTemporarySessionForLoading( interceptor, entityName, attributeName ); isTempSession = true; } else { - throwLazyInitializationException( Cause.NO_SESSION, lazyInitializationWork ); + throwLazyInitializationException( Cause.NO_SESSION, entityName, attributeName ); } } else if ( !session.isOpen() ) { - if ( consumer.allowLoadOutsideTransaction() ) { - session = openTemporarySessionForLoading( lazyInitializationWork ); + if ( interceptor.allowLoadOutsideTransaction() ) { + session = openTemporarySessionForLoading( interceptor, entityName, attributeName ); isTempSession = true; } else { - throwLazyInitializationException( Cause.CLOSED_SESSION, lazyInitializationWork ); + throwLazyInitializationException( Cause.CLOSED_SESSION, entityName, attributeName ); } } else if ( !session.isConnected() ) { - if ( consumer.allowLoadOutsideTransaction() ) { - session = openTemporarySessionForLoading( lazyInitializationWork ); + if ( interceptor.allowLoadOutsideTransaction() ) { + session = openTemporarySessionForLoading( interceptor, entityName, attributeName ); isTempSession = true; } else { - throwLazyInitializationException( Cause.DISCONNECTED_SESSION, lazyInitializationWork ); + throwLazyInitializationException( Cause.DISCONNECTED_SESSION, entityName, attributeName); } } // If we are using a temporary Session, begin a transaction if necessary if ( isTempSession ) { + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork started temporary Session" ); + isJta = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta(); if ( !isJta ) { @@ -88,24 +117,26 @@ else if ( !session.isConnected() ) { // be created even if a current session and transaction are // open (ex: session.clear() was used). We must prevent // multiple transactions. + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork starting transaction on temporary Session" ); session.beginTransaction(); } } try { // do the actual work - return lazyInitializationWork.doWork( session, isTempSession ); + return work.apply( session, isTempSession ); } finally { if ( isTempSession ) { try { // Commit the JDBC transaction is we started one. if ( !isJta ) { + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork committing transaction on temporary Session" ); session.getTransaction().commit(); } } catch (Exception e) { - log.warn( + BytecodeLogger.LOGGER.warn( "Unable to commit JDBC transaction on temporary session used to load lazy " + "collection associated to no session" ); @@ -113,10 +144,11 @@ else if ( !session.isConnected() ) { // Close the just opened temp Session try { + BytecodeLogger.LOGGER.debug( "Enhancement interception Helper#performWork closing temporary Session" ); session.close(); } catch (Exception e) { - log.warn( "Unable to close temporary session used to load lazy collection associated to no session" ); + BytecodeLogger.LOGGER.warn( "Unable to close temporary session used to load lazy collection associated to no session" ); } } } @@ -129,7 +161,7 @@ enum Cause { NO_SF_UUID } - private void throwLazyInitializationException(Cause cause, LazyInitializationWork work) { + private static void throwLazyInitializationException(Cause cause, String entityName, String attributeName) { final String reason; switch ( cause ) { case NO_SESSION: { @@ -156,21 +188,24 @@ private void throwLazyInitializationException(Cause cause, LazyInitializationWor final String message = String.format( Locale.ROOT, "Unable to perform requested lazy initialization [%s.%s] - %s", - work.getEntityName(), - work.getAttributeName(), + entityName, + attributeName, reason ); throw new LazyInitializationException( message ); } - private SharedSessionContractImplementor openTemporarySessionForLoading(LazyInitializationWork lazyInitializationWork) { - if ( consumer.getSessionFactoryUuid() == null ) { - throwLazyInitializationException( Cause.NO_SF_UUID, lazyInitializationWork ); + private static SharedSessionContractImplementor openTemporarySessionForLoading( + BytecodeLazyAttributeInterceptor interceptor, + String entityName, + String attributeName) { + if ( interceptor.getSessionFactoryUuid() == null ) { + throwLazyInitializationException( Cause.NO_SF_UUID, entityName, attributeName ); } final SessionFactoryImplementor sf = (SessionFactoryImplementor) - SessionFactoryRegistry.INSTANCE.getSessionFactory( consumer.getSessionFactoryUuid() ); + SessionFactoryRegistry.INSTANCE.getSessionFactory( interceptor.getSessionFactoryUuid() ); final SharedSessionContractImplementor session = (SharedSessionContractImplementor) sf.openSession(); session.getPersistenceContext().setDefaultReadOnly( true ); session.setHibernateFlushMode( FlushMode.MANUAL ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java index 58672cc9bf35363884fbdfbcfd0f3be2566a8c49..86f9b4b1f2096bf4c024a9ab1c314a39bdebbcec 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java @@ -16,47 +16,39 @@ import org.hibernate.LockMode; import org.hibernate.bytecode.enhance.spi.CollectionTracker; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; -import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.InterceptorImplementor; -import org.hibernate.bytecode.enhance.spi.interceptor.Helper.Consumer; -import org.hibernate.bytecode.enhance.spi.interceptor.Helper.LazyInitializationWork; -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.persister.entity.EntityPersister; -import org.jboss.logging.Logger; - /** * Interceptor that loads attributes lazily * * @author Luis Barreiro * @author Steve Ebersole */ -public class LazyAttributeLoadingInterceptor - implements PersistentAttributeInterceptor, Consumer, InterceptorImplementor { - private static final Logger log = Logger.getLogger( LazyAttributeLoadingInterceptor.class ); - - private final String entityName; +public class LazyAttributeLoadingInterceptor extends AbstractLazyLoadInterceptor { + private final Object identifier; private final Set lazyFields; - private Set initializedLazyFields; - private transient SharedSessionContractImplementor session; - private boolean allowLoadOutsideTransaction; - private String sessionFactoryUuid; - public LazyAttributeLoadingInterceptor( String entityName, + Object identifier, Set lazyFields, SharedSessionContractImplementor session) { - this.entityName = entityName; + super( entityName, session ); + this.identifier = identifier; this.lazyFields = lazyFields; + } - setSession( session ); + @Override + public Object getIdentifier() { + return identifier; } - protected final Object intercept(Object target, String attributeName, Object value) { + @Override + protected Object handleRead(Object target, String attributeName, Object value) { if ( !isAttributeLoaded( attributeName ) ) { Object loadedValue = fetchAttribute( target, attributeName ); attributeInitialized( attributeName ); @@ -65,6 +57,14 @@ protected final Object intercept(Object target, String attributeName, Object val return value; } + @Override + protected Object handleWrite(Object target, String attributeName, Object oldValue, Object newValue) { + if ( !isAttributeLoaded( attributeName ) ) { + attributeInitialized( attributeName ); + } + return newValue; + } + /** * Fetches the lazy attribute. The attribute does not get associated with the entity. (To be used by hibernate methods) */ @@ -73,72 +73,48 @@ public Object fetchAttribute(final Object target, final String attributeName) { } protected Object loadAttribute(final Object target, final String attributeName) { - return new Helper( this ).performWork( - new LazyInitializationWork() { - @Override - public Object doWork(SharedSessionContractImplementor session, boolean isTemporarySession) { - final EntityPersister persister = session.getFactory().getMetamodel().entityPersister( getEntityName() ); - - if ( isTemporarySession ) { - final Serializable id = persister.getIdentifier( target, null ); - - // Add an entry for this entity in the PC of the temp Session - // NOTE : a few arguments that would be nice to pass along here... - // 1) loadedState if we know any - final Object[] loadedState = null; - // 2) does a row exist in the db for this entity? - final boolean existsInDb = true; - session.getPersistenceContext().addEntity( - target, - Status.READ_ONLY, - loadedState, - session.generateEntityKey( id, persister ), - persister.getVersion( target ), - LockMode.NONE, - existsInDb, - persister, - true - ); - } - - final LazyPropertyInitializer initializer = (LazyPropertyInitializer) persister; - final Object loadedValue = initializer.initializeLazyProperty( - attributeName, + return EnhancementHelper.performWork( + this, + (session, isTemporarySession) -> { + final EntityPersister persister = session.getFactory().getMetamodel().entityPersister( getEntityName() ); + + if ( isTemporarySession ) { + final Serializable id = persister.getIdentifier( target, null ); + + // Add an entry for this entity in the PC of the temp Session + // NOTE : a few arguments that would be nice to pass along here... + // 1) loadedState if we know any + final Object[] loadedState = null; + // 2) does a row exist in the db for this entity? + final boolean existsInDb = true; + session.getPersistenceContext().addEntity( target, - session + Status.READ_ONLY, + loadedState, + session.generateEntityKey( id, persister ), + persister.getVersion( target ), + LockMode.NONE, + existsInDb, + persister, + true ); - - takeCollectionSizeSnapshot( target, attributeName, loadedValue ); - return loadedValue; } - @Override - public String getEntityName() { - return entityName; - } - - @Override - public String getAttributeName() { - return attributeName; - } - } + final LazyPropertyInitializer initializer = (LazyPropertyInitializer) persister; + final Object loadedValue = initializer.initializeLazyProperty( + attributeName, + target, + session + ); + + takeCollectionSizeSnapshot( target, attributeName, loadedValue ); + return loadedValue; + }, + getEntityName(), + attributeName ); } - public final void setSession(SharedSessionContractImplementor session) { - this.session = session; - if ( session != null && !allowLoadOutsideTransaction ) { - this.allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled(); - if ( this.allowLoadOutsideTransaction ) { - this.sessionFactoryUuid = session.getFactory().getUuid(); - } - } - } - - public final void unsetSession() { - this.session = null; - } - public boolean isAttributeLoaded(String fieldName) { return !isLazyAttribute( fieldName ) || isInitializedLazyField( fieldName ); } @@ -171,13 +147,11 @@ public boolean hasAnyUninitializedAttributes() { @Override public String toString() { - return "LazyAttributeLoader(entityName=" + entityName + " ,lazyFields=" + lazyFields + ')'; + return "LazyAttributeLoader(entityName=" + getEntityName() + " ,lazyFields=" + lazyFields + ')'; } - // - private void takeCollectionSizeSnapshot(Object target, String fieldName, Object value) { - if ( value != null && value instanceof Collection && target instanceof SelfDirtinessTracker ) { + if ( value instanceof Collection && target instanceof SelfDirtinessTracker ) { CollectionTracker tracker = ( (SelfDirtinessTracker) target ).$$_hibernate_getCollectionTracker(); if ( tracker == null ) { ( (SelfDirtinessTracker) target ).$$_hibernate_clearDirtyAttributes(); @@ -187,152 +161,20 @@ private void takeCollectionSizeSnapshot(Object target, String fieldName, Object } } - @Override - public boolean readBoolean(Object obj, String name, boolean oldValue) { - return (Boolean) intercept( obj, name, oldValue ); - } - - @Override - public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public byte readByte(Object obj, String name, byte oldValue) { - return (Byte) intercept( obj, name, oldValue ); - } - - @Override - public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public char readChar(Object obj, String name, char oldValue) { - return (Character) intercept( obj, name, oldValue ); - } - - @Override - public char writeChar(Object obj, String name, char oldValue, char newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public short readShort(Object obj, String name, short oldValue) { - return (Short) intercept( obj, name, oldValue ); - } - - @Override - public short writeShort(Object obj, String name, short oldValue, short newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public int readInt(Object obj, String name, int oldValue) { - return (Integer) intercept( obj, name, oldValue ); - } - - @Override - public int writeInt(Object obj, String name, int oldValue, int newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public float readFloat(Object obj, String name, float oldValue) { - return (Float) intercept( obj, name, oldValue ); - } - - @Override - public float writeFloat(Object obj, String name, float oldValue, float newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public double readDouble(Object obj, String name, double oldValue) { - return (Double) intercept( obj, name, oldValue ); - } - - @Override - public double writeDouble(Object obj, String name, double oldValue, double newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public long readLong(Object obj, String name, long oldValue) { - return (Long) intercept( obj, name, oldValue ); - } - - @Override - public long writeLong(Object obj, String name, long oldValue, long newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public Object readObject(Object obj, String name, Object oldValue) { - return intercept( obj, name, oldValue ); - } - - @Override - public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { - if ( lazyFields != null && lazyFields.contains( name ) ) { - attributeInitialized( name ); - } - return newValue; - } - - @Override - public SharedSessionContractImplementor getLinkedSession() { - return session; - } - - @Override - public boolean allowLoadOutsideTransaction() { - return allowLoadOutsideTransaction; - } - - @Override - public String getSessionFactoryUuid() { - return sessionFactoryUuid; - } - @Override public void attributeInitialized(String name) { if ( !isLazyAttribute( name ) ) { return; } if ( initializedLazyFields == null ) { - initializedLazyFields = new HashSet(); + initializedLazyFields = new HashSet<>(); } initializedLazyFields.add( name ); } @Override public Set getInitializedLazyAttributeNames() { - return initializedLazyFields == null ? Collections.emptySet() : initializedLazyFields; + return initializedLazyFields == null ? Collections.emptySet() : initializedLazyFields; } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java index 312bea07fdad2fa178265b08c15904c9fde1a153..8192d3d13293accfe148b69440f61f2fef2e610f 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java @@ -29,12 +29,11 @@ public class LazyAttributesMetadata implements Serializable { /** * Build a LazyFetchGroupMetadata based on the attributes defined for the * PersistentClass - * - * @param mappedEntity The entity definition - * - * @return The built LazyFetchGroupMetadata */ - public static LazyAttributesMetadata from(PersistentClass mappedEntity) { + public static LazyAttributesMetadata from( + PersistentClass mappedEntity, + boolean isEnhanced, + boolean allowEnhancementAsProxy) { final Map lazyAttributeDescriptorMap = new LinkedHashMap<>(); final Map> fetchGroupToAttributesMap = new HashMap<>(); @@ -44,7 +43,12 @@ public static LazyAttributesMetadata from(PersistentClass mappedEntity) { while ( itr.hasNext() ) { i++; final Property property = (Property) itr.next(); - if ( property.isLazy() ) { + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + property, + isEnhanced, + allowEnhancementAsProxy + ); + if ( lazy ) { final LazyAttributeDescriptor lazyAttributeDescriptor = LazyAttributeDescriptor.from( property, i, x++ ); lazyAttributeDescriptorMap.put( lazyAttributeDescriptor.getName(), lazyAttributeDescriptor ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/SessionAssociableInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/SessionAssociableInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..7f60ffddbc19ac696075b5d3011e5f18c6f0a1d9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/SessionAssociableInterceptor.java @@ -0,0 +1,25 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.bytecode.enhance.spi.interceptor; + +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; + +/** + * @author Steve Ebersole + */ +public interface SessionAssociableInterceptor extends PersistentAttributeInterceptor { + SharedSessionContractImplementor getLinkedSession(); + + void setSession(SharedSessionContractImplementor session); + + void unsetSession(); + + boolean allowLoadOutsideTransaction(); + + String getSessionFactoryUuid(); +} 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 5c3201f19c6a8600cfdc63ad0bd2364cd4692588..eae143eb673a68be725bc414016a914329f48059 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 @@ -6,6 +6,10 @@ */ package org.hibernate.bytecode.internal.bytebuddy; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.bytecode.spi.BasicProxyFactory; @@ -13,11 +17,9 @@ import org.hibernate.proxy.ProxyConfiguration; import net.bytebuddy.NamingStrategy; +import net.bytebuddy.TypeCache; import net.bytebuddy.description.modifier.Visibility; -import net.bytebuddy.implementation.FieldAccessor; -import net.bytebuddy.implementation.MethodDelegation; -import net.bytebuddy.implementation.bytecode.assign.Assigner; -import net.bytebuddy.matcher.ElementMatchers; +import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; public class BasicProxyFactoryImpl implements BasicProxyFactory { @@ -25,40 +27,55 @@ public class BasicProxyFactoryImpl implements BasicProxyFactory { private static final String PROXY_NAMING_SUFFIX = Environment.useLegacyProxyClassnames() ? "HibernateBasicProxy$" : "HibernateBasicProxy"; private final Class proxyClass; + private final ProxyConfiguration.Interceptor interceptor; - public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyState bytebuddy) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyState byteBuddyState) { if ( superClass == null && ( interfaces == null || interfaces.length < 1 ) ) { throw new AssertionFailure( "attempting to build proxy without any superclass or interfaces" ); } final Class superClassOrMainInterface = superClass != null ? superClass : interfaces[0]; + final TypeCache.SimpleKey cacheKey = getCacheKey( superClass, interfaces ); - this.proxyClass = bytebuddy.getCurrentyByteBuddy() - .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( superClassOrMainInterface.getName() ) ) ) - .subclass( superClass == null ? Object.class : superClass ) - .implement( interfaces == null ? NO_INTERFACES : interfaces ) - .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) - .method( ElementMatchers.isVirtual().and( ElementMatchers.not( ElementMatchers.isFinalizer() ) ) ) - .intercept( MethodDelegation.toField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ) ) - .implement( ProxyConfiguration.class ) - .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) - .make() - .load( superClassOrMainInterface.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( superClassOrMainInterface ) ) - .getLoaded(); + this.proxyClass = byteBuddyState.loadBasicProxy( superClassOrMainInterface, cacheKey, byteBuddy -> byteBuddy + .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( superClassOrMainInterface.getName() ) ) ) + .subclass( superClass == null ? Object.class : superClass, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR ) + .implement( interfaces == null ? NO_INTERFACES : interfaces ) + .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) + .method( byteBuddyState.getProxyDefinitionHelpers().getVirtualNotFinalizerFilter() ) + .intercept( byteBuddyState.getProxyDefinitionHelpers().getDelegateToInterceptorDispatcherMethodDelegation() ) + .implement( ProxyConfiguration.class ) + .intercept( byteBuddyState.getProxyDefinitionHelpers().getInterceptorFieldAccessor() ) + ); + this.interceptor = new PassThroughInterceptor( proxyClass.getName() ); } + @Override public Object getProxy() { try { final ProxyConfiguration proxy = (ProxyConfiguration) proxyClass.newInstance(); - proxy.$$_hibernate_set_interceptor( new PassThroughInterceptor( proxy, proxyClass.getName() ) ); + proxy.$$_hibernate_set_interceptor( this.interceptor ); return proxy; } catch (Throwable t) { - throw new HibernateException( "Unable to instantiate proxy instance" ); + throw new HibernateException( "Unable to instantiate proxy instance", t ); } } public boolean isInstance(Object object) { return proxyClass.isInstance( object ); } + + private TypeCache.SimpleKey getCacheKey(Class superClass, Class[] interfaces) { + Set> key = new HashSet>(); + if ( superClass != null ) { + key.add( superClass ); + } + if ( interfaces != null ) { + key.addAll( Arrays.>asList( interfaces ) ); + } + + return new TypeCache.SimpleKey( key ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java index cc79fb90f24179e71057ade8fc5e497be20fd93b..45b01127c2ceef41fd2d0b49d7482b716ab441b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -6,67 +6,171 @@ */ package org.hibernate.bytecode.internal.bytebuddy; +import static net.bytebuddy.matcher.ElementMatchers.isFinalizer; +import static net.bytebuddy.matcher.ElementMatchers.isSynthetic; +import static net.bytebuddy.matcher.ElementMatchers.isVirtual; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.returns; import static org.hibernate.internal.CoreLogging.messageLogger; +import java.io.File; +import java.io.IOException; import java.lang.invoke.MethodHandles; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.function.Function; import org.hibernate.HibernateException; +import org.hibernate.bytecode.spi.BasicProxyFactory; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyFactory; +import org.hibernate.proxy.ProxyConfiguration; +import org.hibernate.proxy.ProxyFactory; import net.bytebuddy.ByteBuddy; import net.bytebuddy.TypeCache; +import net.bytebuddy.asm.AsmVisitorWrapper.ForDeclaredMethods; +import net.bytebuddy.asm.MemberSubstitution; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.dynamic.DynamicType.Unloaded; import net.bytebuddy.dynamic.loading.ClassInjector; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.dynamic.scaffold.TypeValidation; +import net.bytebuddy.implementation.FieldAccessor; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import net.bytebuddy.pool.TypePool; /** - * An utility to hold all ByteBuddy related state, as in the current version of + * A utility to hold all ByteBuddy related state, as in the current version of * Hibernate the Bytecode Provider state is held in a static field, yet ByteBuddy * is able to benefit from some caching and general state reuse. */ public final class ByteBuddyState { - private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyFactory.class ); + private static final CoreMessageLogger LOG = messageLogger( ByteBuddyState.class ); - /** - * Ideally shouldn't be static but it has to until we can remove the - * deprecated static methods. - */ - private static final ByteBuddy buddy = new ByteBuddy().with( TypeValidation.DISABLED ); + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + private static final boolean DEBUG = false; + + private final ByteBuddy byteBuddy; + + private final ForDeclaredMethods getDeclaredMethodMemberSubstitution; + private final ForDeclaredMethods getMethodMemberSubstitution; + + private final ProxyDefinitionHelpers proxyDefinitionHelpers; /** - * This currently needs to be static: the BytecodeProvider is a static field of Environment and - * is being accessed from static methods. * It will be easier to maintain the cache and its state when it will no longer be static * in Hibernate ORM 6+. * Opted for WEAK keys to avoid leaking the classloader in case the SessionFactory isn't closed. * Avoiding Soft keys as they are prone to cause issues with unstable performance. */ - private static final TypeCache CACHE = new TypeCache.WithInlineExpunction( - TypeCache.Sort.WEAK ); + private final TypeCache proxyCache; + private final TypeCache basicProxyCache; - private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + ByteBuddyState() { + this.byteBuddy = new ByteBuddy().with( TypeValidation.DISABLED ); + + this.proxyCache = new TypeCache.WithInlineExpunction( TypeCache.Sort.WEAK ); + this.basicProxyCache = new TypeCache.WithInlineExpunction( TypeCache.Sort.WEAK ); + + if ( System.getSecurityManager() != null ) { + this.getDeclaredMethodMemberSubstitution = getDeclaredMethodMemberSubstitution(); + this.getMethodMemberSubstitution = getMethodMemberSubstitution(); + } + else { + this.getDeclaredMethodMemberSubstitution = null; + this.getMethodMemberSubstitution = null; + } + + this.proxyDefinitionHelpers = new ProxyDefinitionHelpers(); + } /** - * Access to ByteBuddy. It's almost equivalent to creating a new ByteBuddy instance, - * yet slightly preferrable so to be able to reuse the same instance. - * @return + * Load a proxy as generated by the {@link ProxyFactory}. + * + * @param referenceClass The main class to proxy - might be an interface. + * @param cacheKey The cache key. + * @param makeProxyFunction A function building the proxy. + * @return The loaded proxy class. */ - public ByteBuddy getCurrentyByteBuddy() { - return buddy; + public Class loadProxy(Class referenceClass, TypeCache.SimpleKey cacheKey, + Function> makeProxyFunction) { + return load( referenceClass, proxyCache, cacheKey, makeProxyFunction ); } /** - * @deprecated as we should not need static access to this state. - * This will be removed with no replacement. - * It's actually likely that this whole class becomes unnecessary in the near future. + * Load a proxy as generated by the {@link BasicProxyFactory}. + * + * @param referenceClass The main class to proxy - might be an interface. + * @param cacheKey The cache key. + * @param makeProxyFunction A function building the proxy. + * @return The loaded proxy class. */ - @Deprecated - public static TypeCache getCacheForProxies() { - return CACHE; + Class loadBasicProxy(Class referenceClass, TypeCache.SimpleKey cacheKey, + Function> makeProxyFunction) { + return load( referenceClass, basicProxyCache, cacheKey, makeProxyFunction ); + } + + /** + * Load a class generated by ByteBuddy. + * + * @param referenceClass The main class to proxy - might be an interface. + * @param makeClassFunction A function building the class. + * @return The loaded generated class. + */ + public Class load(Class referenceClass, Function> makeClassFunction) { + Unloaded result = make( makeClassFunction.apply( byteBuddy ) ); + if (DEBUG) { + try { + result.saveIn(new File(System.getProperty("java.io.tmpdir") + "/bytebuddy/")); + } + catch (IOException e) { + LOG.warn("Unable to save generated class %1$s", result.getTypeDescription().getName(), e); + } + } + return result.load( referenceClass.getClassLoader(), resolveClassLoadingStrategy( referenceClass ) ) + .getLoaded(); + } + + /** + * Rewrite a class, used by the enhancer. + *

+ * WARNING: Returns null if rewriteClassFunction returns a null builder. Do not use if you expect the original + * content. + * + * @param typePool the ByteBuddy TypePool + * @param className The original class name. + * @param rewriteClassFunction The function used to rewrite the class. + * @return The rewritten content of the class or null if rewriteClassFunction returns a null builder. + */ + public byte[] rewrite(TypePool typePool, String className, + Function> rewriteClassFunction) { + DynamicType.Builder builder = rewriteClassFunction.apply( byteBuddy ); + if ( builder == null ) { + return null; + } + + return make( typePool, builder ).getBytes(); + } + + /** + * Returns the proxy definition helpers to reuse when defining proxies. + *

+ * These elements are shared as they are immutable. + * + * @return The proxy definition helpers. + */ + public ProxyDefinitionHelpers getProxyDefinitionHelpers() { + return proxyDefinitionHelpers; } /** @@ -78,20 +182,58 @@ public static TypeCache getCacheForProxies() { * of re-creating the small helpers should be negligible. */ void clearState() { - CACHE.clear(); + proxyCache.clear(); + basicProxyCache.clear(); } - /** - * @deprecated as we should not need static access to this state. - * This will be removed with no replacement. - * It's actually likely that this whole class becomes unnecessary in the near future. - */ - @Deprecated - public static ByteBuddy getStaticByteBuddyInstance() { - return buddy; + private Class load(Class referenceClass, TypeCache cache, + TypeCache.SimpleKey cacheKey, Function> makeProxyFunction) { + return cache.findOrInsert( + referenceClass.getClassLoader(), + cacheKey, + () -> make( makeProxyFunction.apply( byteBuddy ) ) + .load( referenceClass.getClassLoader(), resolveClassLoadingStrategy( referenceClass ) ) + .getLoaded(), + cache ); + } + + private Unloaded make(DynamicType.Builder builder) { + return make( null, builder ); + } + + private Unloaded make(TypePool typePool, DynamicType.Builder builder) { + if ( System.getSecurityManager() != null ) { + builder = builder.visit( getDeclaredMethodMemberSubstitution ); + builder = builder.visit( getMethodMemberSubstitution ); + } + + Unloaded unloadedClass; + if ( typePool != null ) { + unloadedClass = builder.make( typePool ); + } + else { + unloadedClass = builder.make(); + } + + if ( DEBUG ) { + try { + unloadedClass.saveIn( new File( System.getProperty( "java.io.tmpdir" ) + "/bytebuddy/" ) ); + } + catch (IOException e) { + LOG.warn( "Unable to save generated class %1$s", unloadedClass.getTypeDescription().getName(), e ); + } + } + + if ( System.getSecurityManager() != null ) { + // we authorize the proxy class to access the method lookup dispatcher + HibernateMethodLookupDispatcher.registerAuthorizedClass( unloadedClass.getTypeDescription().getName() ); + } + + return unloadedClass; } - public static ClassLoadingStrategy resolveClassLoadingStrategy(Class originalClass) { + // This method is kept public static as it is also required by a test. + public static ClassLoadingStrategy resolveClassLoadingStrategy(Class originalClass) { if ( ClassInjector.UsingLookup.isAvailable() ) { // This is only enabled for JDK 9+ @@ -129,4 +271,115 @@ public static ClassLoadingStrategy resolveClassLoadingStrategy(Class origi } } + private static ForDeclaredMethods getDeclaredMethodMemberSubstitution() { + // this should only be called if the security manager is enabled, thus the privileged calls + return MemberSubstitution.relaxed() + .method( ElementMatchers.is( AccessController.doPrivileged( new GetDeclaredMethodAction( Class.class, + "getDeclaredMethod", String.class, Class[].class ) ) ) ) + .replaceWith( + AccessController.doPrivileged( new GetDeclaredMethodAction( HibernateMethodLookupDispatcher.class, + "getDeclaredMethod", Class.class, String.class, Class[].class ) ) ) + .on( ElementMatchers.isTypeInitializer() ); + } + + private static ForDeclaredMethods getMethodMemberSubstitution() { + // this should only be called if the security manager is enabled, thus the privileged calls + return MemberSubstitution.relaxed() + .method( ElementMatchers.is( AccessController.doPrivileged( new GetDeclaredMethodAction( Class.class, + "getMethod", String.class, Class[].class ) ) ) ) + .replaceWith( + AccessController.doPrivileged( new GetDeclaredMethodAction( HibernateMethodLookupDispatcher.class, + "getMethod", Class.class, String.class, Class[].class ) ) ) + .on( ElementMatchers.isTypeInitializer() ); + } + + private static class GetDeclaredMethodAction implements PrivilegedAction { + private final Class clazz; + private final String methodName; + private final Class[] parameterTypes; + + private GetDeclaredMethodAction(Class clazz, String methodName, Class... parameterTypes) { + this.clazz = clazz; + this.methodName = methodName; + this.parameterTypes = parameterTypes; + } + + @Override + public Method run() { + try { + Method method = clazz.getDeclaredMethod( methodName, parameterTypes ); + + return method; + } + catch (NoSuchMethodException e) { + throw new HibernateException( "Unable to prepare getDeclaredMethod()/getMethod() substitution", e ); + } + } + } + + /** + * Shared proxy definition helpers. They are immutable so we can safely share them. + */ + public static class ProxyDefinitionHelpers { + + private final ElementMatcher groovyGetMetaClassFilter; + private final ElementMatcher virtualNotFinalizerFilter; + private final ElementMatcher hibernateGeneratedMethodFilter; + private final MethodDelegation delegateToInterceptorDispatcherMethodDelegation; + private final FieldAccessor.PropertyConfigurable interceptorFieldAccessor; + + private ProxyDefinitionHelpers() { + this.groovyGetMetaClassFilter = isSynthetic().and( named( "getMetaClass" ) + .and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ); + this.virtualNotFinalizerFilter = isVirtual().and( not( isFinalizer() ) ); + this.hibernateGeneratedMethodFilter = nameStartsWith( "$$_hibernate_" ).and( isVirtual() ); + + PrivilegedAction delegateToInterceptorDispatcherMethodDelegationPrivilegedAction = + new PrivilegedAction() { + + @Override + public MethodDelegation run() { + return MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ); + } + }; + + this.delegateToInterceptorDispatcherMethodDelegation = System.getSecurityManager() != null + ? AccessController.doPrivileged( delegateToInterceptorDispatcherMethodDelegationPrivilegedAction ) + : delegateToInterceptorDispatcherMethodDelegationPrivilegedAction.run(); + + PrivilegedAction interceptorFieldAccessorPrivilegedAction = + new PrivilegedAction() { + + @Override + public FieldAccessor.PropertyConfigurable run() { + return FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ) + .withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ); + } + }; + + this.interceptorFieldAccessor = System.getSecurityManager() != null + ? AccessController.doPrivileged( interceptorFieldAccessorPrivilegedAction ) + : interceptorFieldAccessorPrivilegedAction.run(); + } + + public ElementMatcher getGroovyGetMetaClassFilter() { + return groovyGetMetaClassFilter; + } + + public ElementMatcher getVirtualNotFinalizerFilter() { + return virtualNotFinalizerFilter; + } + + public ElementMatcher getHibernateGeneratedMethodFilter() { + return hibernateGeneratedMethodFilter; + } + + public MethodDelegation getDelegateToInterceptorDispatcherMethodDelegation() { + return delegateToInterceptorDispatcherMethodDelegation; + } + + public FieldAccessor.PropertyConfigurable getInterceptorFieldAccessor() { + return interceptorFieldAccessor; + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java index d89a75d73241bbd478fff1fa8693d552415cad82..15f4c1ecc88e4e9fe4c38f33c680804549ab4e06 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java @@ -18,6 +18,7 @@ import org.hibernate.bytecode.spi.BytecodeProvider; import org.hibernate.bytecode.spi.ProxyFactoryFactory; import org.hibernate.bytecode.spi.ReflectionOptimizer; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper; import net.bytebuddy.NamingStrategy; import net.bytebuddy.description.method.MethodDescription; @@ -37,7 +38,6 @@ public class BytecodeProviderImpl implements BytecodeProvider { - private static final ByteBuddyState bytebuddy = new ByteBuddyState(); private static final String INSTANTIATOR_PROXY_NAMING_SUFFIX = "HibernateInstantiator"; private static final String OPTIMIZER_PROXY_NAMING_SUFFIX = "HibernateAccessOptimizer"; private static final ElementMatcher.Junction newInstanceMethodName = ElementMatchers.named( "newInstance" ); @@ -45,9 +45,18 @@ public class BytecodeProviderImpl implements BytecodeProvider { private static final ElementMatcher.Junction setPropertyValuesMethodName = ElementMatchers.named( "setPropertyValues" ); private static final ElementMatcher.Junction getPropertyNamesMethodName = ElementMatchers.named( "getPropertyNames" ); + private final ByteBuddyState byteBuddyState; + + private final ByteBuddyProxyHelper byteBuddyProxyHelper; + + public BytecodeProviderImpl() { + this.byteBuddyState = new ByteBuddyState(); + this.byteBuddyProxyHelper = new ByteBuddyProxyHelper( byteBuddyState ); + } + @Override public ProxyFactoryFactory getProxyFactoryFactory() { - return new ProxyFactoryFactoryImpl( bytebuddy ); + return new ProxyFactoryFactoryImpl( byteBuddyState, byteBuddyProxyHelper ); } @Override @@ -61,15 +70,13 @@ public ReflectionOptimizer getReflectionOptimizer( // we only provide a fast class instantiator if the class can be instantiated final Constructor constructor = findConstructor( clazz ); - fastClass = bytebuddy.getCurrentyByteBuddy() + fastClass = byteBuddyState.load( clazz, byteBuddy -> byteBuddy .with( new NamingStrategy.SuffixingRandom( INSTANTIATOR_PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) ) .subclass( ReflectionOptimizer.InstantiationOptimizer.class ) .method( newInstanceMethodName ) - .intercept( MethodCall.construct( constructor ) ) - .make() - .load( clazz.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( clazz ) ) - .getLoaded(); + .intercept( MethodCall.construct( constructor ) ) + ); } else { fastClass = null; @@ -79,19 +86,17 @@ public ReflectionOptimizer getReflectionOptimizer( final Method[] setters = new Method[setterNames.length]; findAccessors( clazz, getterNames, setterNames, types, getters, setters ); - final Class bulkAccessor = bytebuddy.getCurrentyByteBuddy() + final Class bulkAccessor = byteBuddyState.load( clazz, byteBuddy -> byteBuddy .with( new NamingStrategy.SuffixingRandom( OPTIMIZER_PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) ) .subclass( ReflectionOptimizer.AccessOptimizer.class ) .method( getPropertyValuesMethodName ) - .intercept( new Implementation.Simple( new GetPropertyValues( clazz, getters ) ) ) + .intercept( new Implementation.Simple( new GetPropertyValues( clazz, getters ) ) ) .method( setPropertyValuesMethodName ) - .intercept( new Implementation.Simple( new SetPropertyValues( clazz, setters ) ) ) + .intercept( new Implementation.Simple( new SetPropertyValues( clazz, setters ) ) ) .method( getPropertyNamesMethodName ) - .intercept( MethodCall.call( new CloningPropertyCall( getterNames ) ) ) - .make() - .load( clazz.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( clazz ) ) - .getLoaded(); + .intercept( MethodCall.call( new CloningPropertyCall( getterNames ) ) ) + ); try { return new ReflectionOptimizerImpl( @@ -104,6 +109,10 @@ public ReflectionOptimizer getReflectionOptimizer( } } + public ByteBuddyProxyHelper getByteBuddyProxyHelper() { + return byteBuddyProxyHelper; + } + private static class GetPropertyValues implements ByteCodeAppender { private final Class clazz; @@ -271,11 +280,12 @@ public String[] call() { @Override public Enhancer getEnhancer(EnhancementContext enhancementContext) { - return new EnhancerImpl( enhancementContext, bytebuddy ); + return new EnhancerImpl( enhancementContext, byteBuddyState ); } + @Override public void resetCaches() { - bytebuddy.clearState(); + byteBuddyState.clearState(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java new file mode 100644 index 0000000000000000000000000000000000000000..61cc1c962d2b996882dbf2f037e0ed85c36f6110 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java @@ -0,0 +1,225 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.internal.bytebuddy; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.hibernate.HibernateException; + +/** + * This dispatcher analyzes the stack frames to detect if a particular call should be authorized. + *

+ * Authorized classes are registered when creating the ByteBuddy proxies. + *

+ * It should only be used when the Security Manager is enabled. + */ +public class HibernateMethodLookupDispatcher { + + /** + * The minimum number of stack frames to drop before we can hope to find the caller frame. + */ + private static final int MIN_STACK_FRAMES = 3; + /** + * The maximum number of stack frames to explore to find the caller frame. + *

+ * Beyond that, we give up and throw an exception. + */ + private static final int MAX_STACK_FRAMES = 16; + private static final PrivilegedAction[]> GET_CALLER_STACK_ACTION; + + // Currently, the bytecode provider is created statically and shared between all the session factories. Thus we + // can't clear this set when we close a session factory as we might remove elements coming from another one. + // Considering we can't clear these elements, we use the class names instead of the classes themselves to avoid + // issues. + private static Set authorizedClasses = ConcurrentHashMap.newKeySet(); + + public static Method getDeclaredMethod(Class type, String name, Class[] parameters) { + PrivilegedAction getDeclaredMethodAction = new PrivilegedAction() { + + @Override + public Method run() { + try { + return type.getDeclaredMethod( name, parameters ); + } + catch (NoSuchMethodException | SecurityException e) { + return null; + } + } + }; + + return doPrivilegedAction( getDeclaredMethodAction ); + } + + public static Method getMethod(Class type, String name, Class[] parameters) { + PrivilegedAction getMethodAction = new PrivilegedAction() { + + @Override + public Method run() { + try { + return type.getMethod( name, parameters ); + } + catch (NoSuchMethodException | SecurityException e) { + return null; + } + } + }; + + return doPrivilegedAction( getMethodAction ); + } + + private static Method doPrivilegedAction(PrivilegedAction privilegedAction) { + Class callerClass = getCallerClass(); + + if ( !authorizedClasses.contains( callerClass.getName() ) ) { + throw new SecurityException( "Unauthorized call by class " + callerClass ); + } + + return System.getSecurityManager() != null ? AccessController.doPrivileged( privilegedAction ) : + privilegedAction.run(); + } + + static void registerAuthorizedClass(String className) { + authorizedClasses.add( className ); + } + + static { + // The action below will return the action used at runtime to retrieve the caller stack + PrivilegedAction[]>> initializeGetCallerStackAction = new PrivilegedAction[]>>() { + @Override + public PrivilegedAction[]> run() { + Class stackWalkerClass = null; + try { + // JDK 9 introduced the StackWalker + stackWalkerClass = Class.forName( "java.lang.StackWalker" ); + } + catch (ClassNotFoundException e) { + // ignore, we will deal with that later. + } + + if ( stackWalkerClass != null ) { + // We can use a stack walker + try { + Class optionClass = Class.forName( "java.lang.StackWalker$Option" ); + Object stackWalker = stackWalkerClass.getMethod( "getInstance", optionClass ) + // The first one is RETAIN_CLASS_REFERENCE + .invoke( null, optionClass.getEnumConstants()[0] ); + + Method stackWalkerWalkMethod = stackWalkerClass.getMethod( "walk", Function.class ); + Method stackFrameGetDeclaringClass = Class.forName( "java.lang.StackWalker$StackFrame" ) + .getMethod( "getDeclaringClass" ); + return new StackWalkerGetCallerStackAction( + stackWalker, stackWalkerWalkMethod,stackFrameGetDeclaringClass + ); + } + catch (Throwable e) { + throw new HibernateException( "Unable to initialize the stack walker", e ); + } + } + else { + // We cannot use a stack walker, default to fetching the security manager class context + return new SecurityManagerClassContextGetCallerStackAction(); + } + } + }; + + GET_CALLER_STACK_ACTION = System.getSecurityManager() != null + ? AccessController.doPrivileged( initializeGetCallerStackAction ) + : initializeGetCallerStackAction.run(); + } + + private static Class getCallerClass() { + Class[] stackTrace = System.getSecurityManager() != null + ? AccessController.doPrivileged( GET_CALLER_STACK_ACTION ) + : GET_CALLER_STACK_ACTION.run(); + + // this shouldn't happen but let's be safe + if ( stackTrace.length <= MIN_STACK_FRAMES ) { + throw new SecurityException( "Unable to determine the caller class" ); + } + + boolean hibernateMethodLookupDispatcherDetected = false; + // start at the 4th frame and limit that to the MAX_STACK_FRAMES first frames + int maxFrames = Math.min( MAX_STACK_FRAMES, stackTrace.length ); + for ( int i = MIN_STACK_FRAMES; i < maxFrames; i++ ) { + if ( stackTrace[i].getName().equals( HibernateMethodLookupDispatcher.class.getName() ) ) { + hibernateMethodLookupDispatcherDetected = true; + continue; + } + if ( hibernateMethodLookupDispatcherDetected ) { + return stackTrace[i]; + } + } + + throw new SecurityException( "Unable to determine the caller class" ); + } + + /** + * A privileged action that retrieves the caller stack from the security manager class context. + */ + private static class SecurityManagerClassContextGetCallerStackAction extends SecurityManager + implements PrivilegedAction[]> { + @Override + public Class[] run() { + return getClassContext(); + } + } + + /** + * A privileged action that retrieves the caller stack using a stack walker. + */ + private static class StackWalkerGetCallerStackAction implements PrivilegedAction[]> { + private final Object stackWalker; + private final Method stackWalkerWalkMethod; + private final Method stackFrameGetDeclaringClass; + + StackWalkerGetCallerStackAction(Object stackWalker, Method stackWalkerWalkMethod, + Method stackFrameGetDeclaringClass) { + this.stackWalker = stackWalker; + this.stackWalkerWalkMethod = stackWalkerWalkMethod; + this.stackFrameGetDeclaringClass = stackFrameGetDeclaringClass; + } + + @Override + public Class[] run() { + try { + return (Class[]) stackWalkerWalkMethod.invoke( stackWalker, stackFrameExtractFunction ); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new SecurityException( "Unable to determine the caller class", e ); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private final Function stackFrameExtractFunction = new Function() { + @Override + public Object apply(Stream stream) { + return stream.map( stackFrameGetDeclaringClassFunction ) + .limit( MAX_STACK_FRAMES ) + .toArray( Class[]::new ); + } + }; + + private final Function> stackFrameGetDeclaringClassFunction = new Function>() { + @Override + public Class apply(Object t) { + try { + return (Class) stackFrameGetDeclaringClass.invoke( t ); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new HibernateException( "Unable to get stack frame declaring class", e ); + } + } + }; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/PassThroughInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/PassThroughInterceptor.java index 7d45e2c6cd380938ef5d45f58e1779504cc295ea..09b108a148e92f7382565898979e3980875426c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/PassThroughInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/PassThroughInterceptor.java @@ -11,57 +11,58 @@ import org.hibernate.proxy.ProxyConfiguration; -import net.bytebuddy.implementation.bind.annotation.AllArguments; -import net.bytebuddy.implementation.bind.annotation.Origin; -import net.bytebuddy.implementation.bind.annotation.RuntimeType; -import net.bytebuddy.implementation.bind.annotation.This; - public class PassThroughInterceptor implements ProxyConfiguration.Interceptor { - private HashMap data = new HashMap(); - private final Object proxiedObject; + private HashMap data = new HashMap<>(); private final String proxiedClassName; - public PassThroughInterceptor(Object proxiedObject, String proxiedClassName) { - this.proxiedObject = proxiedObject; + public PassThroughInterceptor(String proxiedClassName) { this.proxiedClassName = proxiedClassName; } - @SuppressWarnings("unchecked") @Override public Object intercept(Object instance, Method method, Object[] arguments) throws Exception { final String name = method.getName(); - if ( "toString".equals( name ) ) { + + if ( "toString".equals( name ) && arguments.length == 0 ) { return proxiedClassName + "@" + System.identityHashCode( instance ); } - else if ( "equals".equals( name ) ) { - return proxiedObject == instance; + + if ( "equals".equals( name ) && arguments.length == 1 ) { + return instance == arguments[0]; } - else if ( "hashCode".equals( name ) ) { + + if ( "hashCode".equals( name ) && arguments.length == 0 ) { return System.identityHashCode( instance ); } - final boolean hasGetterSignature = method.getParameterCount() == 0 - && method.getReturnType() != null; - final boolean hasSetterSignature = method.getParameterCount() == 1 - && ( method.getReturnType() == null || method.getReturnType() == void.class ); - - if ( name.startsWith( "get" ) && hasGetterSignature ) { + if ( name.startsWith( "get" ) && hasGetterSignature( method ) ) { final String propName = name.substring( 3 ); return data.get( propName ); } - else if ( name.startsWith( "is" ) && hasGetterSignature ) { + + if ( name.startsWith( "is" ) && hasGetterSignature( method ) ) { final String propName = name.substring( 2 ); return data.get( propName ); } - else if ( name.startsWith( "set" ) && hasSetterSignature ) { + + if ( name.startsWith( "set" ) && hasSetterSignature( method ) ) { final String propName = name.substring( 3 ); data.put( propName, arguments[0] ); return null; } - else { - // todo : what else to do here? - return null; - } + + // todo : what else to do here? + return null; + } + + private boolean hasGetterSignature(Method method) { + return method.getParameterCount() == 0 + && method.getReturnType() != null; + } + + private boolean hasSetterSignature(Method method) { + return method.getParameterCount() == 1 + && ( method.getReturnType() == null || method.getReturnType() == void.class ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ProxyFactoryFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ProxyFactoryFactoryImpl.java index 9df70b85b1e217a9337f4c12362bcbff361ff5f6..0f68aead89c66fc02a9836c9bb210b219185b494 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ProxyFactoryFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ProxyFactoryFactoryImpl.java @@ -11,22 +11,26 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.proxy.ProxyFactory; import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyFactory; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper; public class ProxyFactoryFactoryImpl implements ProxyFactoryFactory { - private final ByteBuddyState bytebuddy; + private final ByteBuddyState byteBuddyState; - public ProxyFactoryFactoryImpl(ByteBuddyState bytebuddy) { - this.bytebuddy = bytebuddy; + private final ByteBuddyProxyHelper byteBuddyProxyHelper; + + public ProxyFactoryFactoryImpl(ByteBuddyState byteBuddyState, ByteBuddyProxyHelper byteBuddyProxyHelper) { + this.byteBuddyState = byteBuddyState; + this.byteBuddyProxyHelper = byteBuddyProxyHelper; } @Override public ProxyFactory buildProxyFactory(SessionFactoryImplementor sessionFactory) { - return new ByteBuddyProxyFactory(); + return new ByteBuddyProxyFactory( byteBuddyProxyHelper ); } @Override public BasicProxyFactory buildBasicProxyFactory(Class superClass, Class[] interfaces) { - return new BasicProxyFactoryImpl( superClass, interfaces, bytebuddy ); + return new BasicProxyFactoryImpl( superClass, interfaces, byteBuddyState ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/javassist/BulkAccessorFactory.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/javassist/BulkAccessorFactory.java index 80d300c63753812393209abbdf6b6c43f5f2ba45..12d19bcb64f6d2d30067798aa44b60dc0254f52a 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/javassist/BulkAccessorFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/javassist/BulkAccessorFactory.java @@ -71,7 +71,7 @@ BulkAccessor create() { FactoryHelper.writeFile( classfile, writeDirectory ); } - beanClass = FactoryHelper.toClass( classfile, loader, getDomain() ); + beanClass = FactoryHelper.toClass( classfile, null, loader, getDomain() ); return (BulkAccessor) this.newInstance( beanClass ); } catch ( Exception e ) { diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java index b71ba5aad2008cc0ff4e983c62eac621bf83ecb7..deecb83f9ff1924e1e907675c9d1ed9fb15140a8 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeEnhancementMetadata.java @@ -6,8 +6,12 @@ */ package org.hibernate.bytecode.spi; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -33,10 +37,19 @@ public interface BytecodeEnhancementMetadata { LazyAttributesMetadata getLazyAttributesMetadata(); + /** + * Create an "enhancement as proxy" instance for the given entity + * + * @apiNote The `addEmptyEntry` parameter is used to avoid creation of `EntityEntry` instances when we + * do not need them. - mainly from StatelessSession + */ + PersistentAttributeInterceptable createEnhancedProxy(EntityKey keyToLoad, boolean addEmptyEntry, SharedSessionContractImplementor session); + /** * Build and inject an interceptor instance into the enhanced entity. * * @param entity The entity into which built interceptor should be injected + * @param identifier * @param session The session to which the entity instance belongs. * * @return The built and injected interceptor @@ -45,8 +58,19 @@ public interface BytecodeEnhancementMetadata { */ LazyAttributeLoadingInterceptor injectInterceptor( Object entity, + Object identifier, SharedSessionContractImplementor session) throws NotInstrumentedException; + void injectInterceptor( + Object entity, + PersistentAttributeInterceptor interceptor, + SharedSessionContractImplementor session); + + void injectEnhancedEntityAsProxyInterceptor( + Object entity, + EntityKey entityKey, + SharedSessionContractImplementor session); + /** * Extract the field interceptor instance from the enhanced entity. * @@ -58,6 +82,8 @@ LazyAttributeLoadingInterceptor injectInterceptor( */ LazyAttributeLoadingInterceptor extractInterceptor(Object entity) throws NotInstrumentedException; + BytecodeLazyAttributeInterceptor extractLazyInterceptor(Object entity) throws NotInstrumentedException; + boolean hasUnFetchedAttributes(Object entity); boolean isAttributeLoaded(Object entity, String attributeName); diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java index b22ab86d844f78ad6482732ed193ee8bc2208062..6a90c6e5dfa95670b20695c93375d34fcb4b295b 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java @@ -167,13 +167,14 @@ private void evict(Serializable id, CollectionPersister collectionPersister, Eve if ( LOG.isDebugEnabled() ) { LOG.debug( "Evict CollectionRegion " + collectionPersister.getRole() + " for id " + id ); } - AfterTransactionCompletionProcess afterTransactionProcess = new CollectionEvictCacheAction( + CollectionEvictCacheAction evictCacheAction = new CollectionEvictCacheAction( collectionPersister, null, id, session - ).lockCache(); - session.getActionQueue().registerProcess( afterTransactionProcess ); + ); + evictCacheAction.execute(); + session.getActionQueue().registerProcess( evictCacheAction.getAfterTransactionCompletionProcess() ); } //execute the same process as invalidation with collection operations @@ -188,13 +189,9 @@ protected CollectionEvictCacheAction( @Override public void execute() throws HibernateException { - } - - public AfterTransactionCompletionProcess lockCache() { beforeExecutions(); - return getAfterTransactionCompletionProcess(); + evict(); } - } } 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 e7257ed0d0466cb79cf744d5952eeb45df4cb084..9ce34807a33b3e897df1d2574a0005b1fcb89c2d 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 @@ -48,6 +48,7 @@ /** * @author Steve Ebersole * @author Strong Liu + * @author Gail Badner */ public class EnabledCaching implements CacheImplementor, DomainDataRegionBuildingContext { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( EnabledCaching.class ); @@ -57,6 +58,10 @@ public class EnabledCaching implements CacheImplementor, DomainDataRegionBuildin private final Map regionsByName = new ConcurrentHashMap<>(); + // A map by name for QueryResultsRegion instances that have the same name as a Region + // in #regionsByName. + private final Map queryResultsRegionsByDuplicateName = new ConcurrentHashMap<>(); + private final Map entityAccessMap = new ConcurrentHashMap<>(); private final Map naturalIdAccessMap = new ConcurrentHashMap<>(); private final Map collectionAccessMap = new ConcurrentHashMap<>(); @@ -204,6 +209,8 @@ public TimestampsCache getTimestampsCache() { @Override public Region getRegion(String regionName) { + // The Region in regionsByName has precedence over the + // QueryResultsRegion in #queryResultsRegionsByDuplicateName return regionsByName.get( regionName ); } @@ -488,12 +495,23 @@ public QueryResultsCache getQueryResultsCacheStrictly(String regionName) { } protected QueryResultsCache makeQueryResultsRegionAccess(String regionName) { - final QueryResultsRegion region = (QueryResultsRegion) regionsByName.computeIfAbsent( + final Region region = regionsByName.computeIfAbsent( regionName, this::makeQueryResultsRegion ); + final QueryResultsRegion queryResultsRegion; + if ( QueryResultsRegion.class.isInstance( region ) ) { + queryResultsRegion = (QueryResultsRegion) region; + } + else { + // There was already a different type of Region with the same name. + queryResultsRegion = queryResultsRegionsByDuplicateName.computeIfAbsent( + regionName, + this::makeQueryResultsRegion + ); + } final QueryResultsCacheImpl regionAccess = new QueryResultsCacheImpl( - region, + queryResultsRegion, timestampsCache ); namedQueryResultsCacheMap.put( regionName, regionAccess ); @@ -502,20 +520,9 @@ protected QueryResultsCache makeQueryResultsRegionAccess(String regionName) { } protected QueryResultsRegion makeQueryResultsRegion(String regionName) { - // make sure there is not an existing domain-data region with that name.. - final Region existing = regionsByName.get( regionName ); - if ( existing != null ) { - if ( !QueryResultsRegion.class.isInstance( existing ) ) { - throw new IllegalStateException( "Cannot store both domain-data and query-result-data in the same region [" + regionName ); - } - - throw new IllegalStateException( "Illegal call to create QueryResultsRegion - one already existed" ); - } - return regionFactory.buildQueryResultsRegion( regionName, getSessionFactory() ); } - @Override public Set getCacheRegionNames() { return regionsByName.keySet(); @@ -524,6 +531,10 @@ public Set getCacheRegionNames() { @Override public void evictRegion(String regionName) { getRegion( regionName ).clear(); + final QueryResultsRegion queryResultsRegionWithDuplicateName = queryResultsRegionsByDuplicateName.get( regionName ); + if ( queryResultsRegionWithDuplicateName != null ) { + queryResultsRegionWithDuplicateName.clear(); + } } @Override @@ -545,6 +556,9 @@ public void close() { for ( Region region : regionsByName.values() ) { region.destroy(); } + for ( Region region : queryResultsRegionsByDuplicateName.values() ) { + region.destroy(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheImplementor.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheImplementor.java index 00a28c7b99d0d0ade44e1ab0717ef40fc61927c3..c77b13db7b591988c29ff495cca270f325c9c162 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheImplementor.java @@ -52,7 +52,9 @@ public interface CacheImplementor extends Service, Cache, org.hibernate.engine.s void prime(Set cacheRegionConfigs); /** - * Get a cache Region by name + * Get a cache Region by name. If there is both a {@link DomainDataRegion} + * and a {@link QueryResultsRegion} with the specified name, then the + * {@link DomainDataRegion} will be returned. * * @apiNote It is only valid to call this method after {@link #prime} has * been performed diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/SecondLevelCacheLogger.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/SecondLevelCacheLogger.java index 1f7fce912bbdb436430438b4212b48aea411081b..413e741664a0c9d8078ee17c7231efc6ffc586b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/SecondLevelCacheLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/SecondLevelCacheLogger.java @@ -6,6 +6,7 @@ */ package org.hibernate.cache.spi; +import org.hibernate.cache.spi.access.AccessType; import org.hibernate.metamodel.model.domain.NavigableRole; import org.jboss.logging.BasicLogger; @@ -90,4 +91,12 @@ public interface SecondLevelCacheLogger extends BasicLogger { ) void usingLegacyCacheName(String currentName, String legacyName); + @LogMessage(level = WARN) + @Message( + value = "Cache [%1$s] uses the [%2$s] access type, but [%3$s] does not support it natively." + + " Make sure your cache implementation supports JTA transactions.", + id = NAMESPACE + 8 + ) + void nonStandardSupportForAccessType(String key, String accessType, String regionName); + } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/NaturalIdDataAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/NaturalIdDataAccess.java index bec175ad1c8c2913e0141a94139fada882bc7be9..5c6b7fd18d08c3651d5b0c53a1dbb8b885859a3d 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/access/NaturalIdDataAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/access/NaturalIdDataAccess.java @@ -16,10 +16,10 @@ *

  • INSERTS : {@link #insert} -> {@link #afterInsert}
  • *
  • UPDATES : {@link #lockItem} -> {@link #remove} -> {@link #update} -> {@link #afterUpdate}
  • *
  • DELETES : {@link #lockItem} -> {@link #remove} -> {@link #unlockItem}
  • - *
  • LOADS : {@link @putFromLoad}
  • + *
  • LOADS : {@link #putFromLoad}
  • * * Note the special case of UPDATES above. Because the cache key itself has changed here we need to remove the - * old entry as well as + * old entry as well *

    * There is another usage pattern that is used to invalidate entries * afterQuery performing "bulk" HQL/SQL operations: @@ -44,7 +44,7 @@ public interface NaturalIdDataAccess extends CachedDomainDataAccess { * @param naturalIdValues the sequence of values which unequivocally identifies a cached element on this region * @param rootEntityDescriptor the persister of the element being cached * - * @return a key which can be used to identify this an element unequivocally on this same region + * @return a key which can be used to identify an element unequivocally on this same region */ Object generateCacheKey( Object[] naturalIdValues, @@ -69,7 +69,7 @@ Object generateCacheKey( * @param key The item key * @param value The item * - * @return Were the contents of the cache actual changed by this operation? + * @return Were the contents of the cache actually changed by this operation? * * @throws CacheException Propagated from underlying cache provider */ @@ -84,7 +84,7 @@ Object generateCacheKey( * @param key The item key * @param value The item * - * @return Were the contents of the cache actual changed by this operation? + * @return Were the contents of the cache actually changed by this operation? * * @throws CacheException Propagated from underlying cache provider */ @@ -99,7 +99,7 @@ Object generateCacheKey( * @param key The item key * @param value The item * - * @return Were the contents of the cache actual changed by this operation? + * @return Were the contents of the cache actually changed by this operation? * * @throws CacheException Propagated from underlying cache provider */ @@ -115,7 +115,7 @@ Object generateCacheKey( * @param value The item * @param lock The lock previously obtained from {@link #lockItem} * - * @return Were the contents of the cache actual changed by this operation? + * @return Were the contents of the cache actually changed by this operation? * * @throws CacheException Propagated from underlying cache provider */ diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractCachedDomainDataAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractCachedDomainDataAccess.java index 7bcd27b280616ebde0355a93b0d73c63bb518336..e4475a3a5a8385d0551ddb35e39f4752ba743f38 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractCachedDomainDataAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractCachedDomainDataAccess.java @@ -38,9 +38,8 @@ protected DomainDataStorageAccess getStorageAccess() { return storageAccess; } - @SuppressWarnings({"unchecked", "WeakerAccess"}) protected void clearCache() { - log.debugf( "Clearing cache data map [region=`%s`]" ); + log.debugf( "Clearing cache data map [region=`%s`]", region.getName() ); getStorageAccess().evictData(); } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/cache/CollectionTransactionAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionTransactionAccess.java similarity index 96% rename from hibernate-testing/src/main/java/org/hibernate/testing/cache/CollectionTransactionAccess.java rename to hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionTransactionAccess.java index 456354e4b7e3f9217e25fe80e928d58cdc8b730c..10fd566be7b46a7ddcd9bafb8252675b33a5feeb 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/cache/CollectionTransactionAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/CollectionTransactionAccess.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.testing.cache; +package org.hibernate.cache.spi.support; import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; import org.hibernate.cache.spi.CacheKeysFactory; diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/cache/DomainDataRegionImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DomainDataRegionImpl.java similarity index 91% rename from hibernate-testing/src/main/java/org/hibernate/testing/cache/DomainDataRegionImpl.java rename to hibernate-core/src/main/java/org/hibernate/cache/spi/support/DomainDataRegionImpl.java index 3fc79fce61c03146fcaa1ca9216c3f23b9b7fb40..a33a6e82cc16d09602a8dbb090af82acfea0a5d4 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/cache/DomainDataRegionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/DomainDataRegionImpl.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.testing.cache; +package org.hibernate.cache.spi.support; import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext; @@ -15,7 +15,6 @@ import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.NaturalIdDataAccess; -import org.hibernate.cache.spi.support.DomainDataRegionTemplate; /** * @author Steve Ebersole @@ -24,13 +23,14 @@ public class DomainDataRegionImpl extends DomainDataRegionTemplate { @SuppressWarnings("WeakerAccess") public DomainDataRegionImpl( DomainDataRegionConfig regionConfig, - CachingRegionFactory regionFactory, + RegionFactoryTemplate regionFactory, + DomainDataStorageAccess domainDataStorageAccess, CacheKeysFactory defaultKeysFactory, DomainDataRegionBuildingContext buildingContext) { super( regionConfig, regionFactory, - new MapStorageAccessImpl(), + domainDataStorageAccess, defaultKeysFactory, buildingContext ); diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/cache/EntityTransactionalAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityTransactionalAccess.java similarity index 97% rename from hibernate-testing/src/main/java/org/hibernate/testing/cache/EntityTransactionalAccess.java rename to hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityTransactionalAccess.java index 3182952744400a8f26114496701ee7a6d019f83e..33cd0d85836010730e50e94522e9d79b84257a0c 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/cache/EntityTransactionalAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/EntityTransactionalAccess.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.testing.cache; +package org.hibernate.cache.spi.support; import org.hibernate.cache.cfg.spi.EntityDataCachingConfig; import org.hibernate.cache.spi.CacheKeysFactory; diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/cache/NaturalIdTransactionalAccess.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdTransactionalAccess.java similarity index 96% rename from hibernate-testing/src/main/java/org/hibernate/testing/cache/NaturalIdTransactionalAccess.java rename to hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdTransactionalAccess.java index ed3086256d303c51dce752baeff497454b2d6843..66cf1a9faebb3e6002d1bfea8ffe916a66e8966b 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/cache/NaturalIdTransactionalAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/NaturalIdTransactionalAccess.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.testing.cache; +package org.hibernate.cache.spi.support; import org.hibernate.cache.cfg.spi.NaturalIdDataCachingConfig; import org.hibernate.cache.spi.CacheKeysFactory; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index 5f421537178e1d33407250dde96bf57cd76c1b1d..822c830cf1a4e2eee793f65d3d969375e4fed386 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -370,59 +370,67 @@ private static void bindGenericGenerator(GenericGenerator def, MetadataBuildingC context.getMetadataCollector().addIdentifierGenerator( buildIdGenerator( def, context ) ); } - private static void bindQueries(XAnnotatedElement annotatedElement, MetadataBuildingContext context) { - { - SqlResultSetMapping ann = annotatedElement.getAnnotation( SqlResultSetMapping.class ); - QueryBinder.bindSqlResultSetMapping( ann, context, false ); - } - { - SqlResultSetMappings ann = annotatedElement.getAnnotation( SqlResultSetMappings.class ); - if ( ann != null ) { - for ( SqlResultSetMapping current : ann.value() ) { - QueryBinder.bindSqlResultSetMapping( current, context, false ); - } + private static void bindNamedJpaQueries(XAnnotatedElement annotatedElement, MetadataBuildingContext context) { + QueryBinder.bindSqlResultSetMapping( + annotatedElement.getAnnotation( SqlResultSetMapping.class ), + context, + false + ); + + final SqlResultSetMappings ann = annotatedElement.getAnnotation( SqlResultSetMappings.class ); + if ( ann != null ) { + for ( SqlResultSetMapping current : ann.value() ) { + QueryBinder.bindSqlResultSetMapping( current, context, false ); } } - { - NamedQuery ann = annotatedElement.getAnnotation( NamedQuery.class ); - QueryBinder.bindQuery( ann, context, false ); - } - { - org.hibernate.annotations.NamedQuery ann = annotatedElement.getAnnotation( - org.hibernate.annotations.NamedQuery.class - ); - QueryBinder.bindQuery( ann, context ); - } - { - NamedQueries ann = annotatedElement.getAnnotation( NamedQueries.class ); - QueryBinder.bindQueries( ann, context, false ); - } - { - org.hibernate.annotations.NamedQueries ann = annotatedElement.getAnnotation( - org.hibernate.annotations.NamedQueries.class - ); - QueryBinder.bindQueries( ann, context ); - } - { - NamedNativeQuery ann = annotatedElement.getAnnotation( NamedNativeQuery.class ); - QueryBinder.bindNativeQuery( ann, context, false ); - } - { - org.hibernate.annotations.NamedNativeQuery ann = annotatedElement.getAnnotation( - org.hibernate.annotations.NamedNativeQuery.class - ); - QueryBinder.bindNativeQuery( ann, context ); - } - { - NamedNativeQueries ann = annotatedElement.getAnnotation( NamedNativeQueries.class ); - QueryBinder.bindNativeQueries( ann, context, false ); - } - { - org.hibernate.annotations.NamedNativeQueries ann = annotatedElement.getAnnotation( - org.hibernate.annotations.NamedNativeQueries.class - ); - QueryBinder.bindNativeQueries( ann, context ); - } + + QueryBinder.bindQuery( + annotatedElement.getAnnotation( NamedQuery.class ), + context, + false + ); + + QueryBinder.bindQueries( + annotatedElement.getAnnotation( NamedQueries.class ), + context, + false + ); + + QueryBinder.bindNativeQuery( + annotatedElement.getAnnotation( NamedNativeQuery.class ), + context, + false + ); + + QueryBinder.bindNativeQueries( + annotatedElement.getAnnotation( NamedNativeQueries.class ), + context, + false + ); + } + + private static void bindQueries(XAnnotatedElement annotatedElement, MetadataBuildingContext context) { + bindNamedJpaQueries( annotatedElement, context ); + + QueryBinder.bindQuery( + annotatedElement.getAnnotation( org.hibernate.annotations.NamedQuery.class ), + context + ); + + QueryBinder.bindQueries( + annotatedElement.getAnnotation( org.hibernate.annotations.NamedQueries.class ), + context + ); + + QueryBinder.bindNativeQuery( + annotatedElement.getAnnotation( org.hibernate.annotations.NamedNativeQuery.class ), + context + ); + + QueryBinder.bindNativeQueries( + annotatedElement.getAnnotation( org.hibernate.annotations.NamedNativeQueries.class ), + context + ); // NamedStoredProcedureQuery handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bindNamedStoredProcedureQuery( @@ -561,10 +569,7 @@ public static void bindClass( if(superEntity != null && ( clazzToProcess.getAnnotation( AttributeOverride.class ) != null || clazzToProcess.getAnnotation( AttributeOverrides.class ) != null ) ) { - throw new AnnotationException( - "An entity annotated with @Inheritance cannot use @AttributeOverride or @AttributeOverrides: " + - clazzToProcess.getName() - ); + LOG.unsupportedAttributeOverrideWithEntityInheritance( clazzToProcess.getName() ); } PersistentClass persistentClass = makePersistentClass( inheritanceState, superEntity, context ); @@ -1759,7 +1764,16 @@ private static void processElementAnnotations( joinColumn.setExplicitTableName( join.getTable().getName() ); } } - final boolean mandatory = !ann.optional() || forcePersist; + // MapsId means the columns belong to the pk; + // A @MapsId association (obviously) must be non-null when the entity is first persisted. + // If a @MapsId association is not mapped with @NotFound(IGNORE), then the association + // is mandatory (even if the association has optional=true). + // If a @MapsId association has optional=true and is mapped with @NotFound(IGNORE) then + // the association is optional. + final boolean mandatory = + !ann.optional() || + property.isAnnotationPresent( Id.class ) || + ( property.isAnnotationPresent( MapsId.class ) && !ignoreNotFound ); bindManyToOne( getCascadeStrategy( ann.cascade(), hibernateCascade, false, forcePersist ), joinColumns, @@ -1789,11 +1803,23 @@ else if ( property.isAnnotationPresent( OneToOne.class ) ) { } //FIXME support a proper PKJCs - boolean trueOneToOne = property.isAnnotationPresent( PrimaryKeyJoinColumn.class ) + final boolean hasPkjc = property.isAnnotationPresent( PrimaryKeyJoinColumn.class ) || property.isAnnotationPresent( PrimaryKeyJoinColumns.class ); + boolean trueOneToOne = hasPkjc; Cascade hibernateCascade = property.getAnnotation( Cascade.class ); NotFound notFound = property.getAnnotation( NotFound.class ); boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE ); + // MapsId means the columns belong to the pk; + // A @MapsId association (obviously) must be non-null when the entity is first persisted. + // If a @MapsId association is not mapped with @NotFound(IGNORE), then the association + // is mandatory (even if the association has optional=true). + // If a @MapsId association has optional=true and is mapped with @NotFound(IGNORE) then + // the association is optional. + // @OneToOne(optional = true) with @PKJC makes the association optional. + final boolean mandatory = + !ann.optional() || + property.isAnnotationPresent( Id.class ) || + ( property.isAnnotationPresent( MapsId.class ) && !ignoreNotFound ); OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ); JoinTable assocTable = propertyHolder.getJoinTable( property ); @@ -1803,9 +1829,6 @@ else if ( property.isAnnotationPresent( OneToOne.class ) ) { joinColumn.setExplicitTableName( join.getTable().getName() ); } } - //MapsId means the columns belong to the pk => not null - //@OneToOne with @PKJC can still be optional - final boolean mandatory = !ann.optional() || forcePersist; bindOneToOne( getCascadeStrategy( ann.cascade(), hibernateCascade, ann.orphanRemoval(), forcePersist ), joinColumns, @@ -3004,7 +3027,7 @@ private static void bindManyToOne( column.setUpdatable( false ); } } - + final JoinColumn joinColumn = property.getAnnotation( JoinColumn.class ); final JoinColumns joinColumns = property.getAnnotation( JoinColumns.class ); @@ -3035,37 +3058,13 @@ private static void bindManyToOne( final String propertyName = inferredData.getPropertyName(); value.setTypeUsingReflection( propertyHolder.getClassName(), propertyName ); - if ( ( joinColumn != null && joinColumn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) - || ( joinColumns != null && joinColumns.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) ) { - // not ideal... - value.setForeignKeyName( "none" ); - } - else { - final ForeignKey fk = property.getAnnotation( ForeignKey.class ); - if ( fk != null && StringHelper.isNotEmpty( fk.name() ) ) { - value.setForeignKeyName( fk.name() ); - } - else { - final javax.persistence.ForeignKey fkOverride = propertyHolder.getOverriddenForeignKey( - StringHelper.qualify( propertyHolder.getPath(), propertyName ) - ); - if ( fkOverride != null && fkOverride.value() == ConstraintMode.NO_CONSTRAINT ) { - value.setForeignKeyName( "none" ); - } - else if ( fkOverride != null ) { - value.setForeignKeyName( StringHelper.nullIfEmpty( fkOverride.name() ) ); - value.setForeignKeyDefinition( StringHelper.nullIfEmpty( fkOverride.foreignKeyDefinition() ) ); - } - else if ( joinColumns != null ) { - value.setForeignKeyName( StringHelper.nullIfEmpty( joinColumns.foreignKey().name() ) ); - value.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumns.foreignKey().foreignKeyDefinition() ) ); - } - else if ( joinColumn != null ) { - value.setForeignKeyName( StringHelper.nullIfEmpty( joinColumn.foreignKey().name() ) ); - value.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumn.foreignKey().foreignKeyDefinition() ) ); - } - } - } + bindForeignKeyNameAndDefinition( + value, + property, + propertyHolder.getOverriddenForeignKey( StringHelper.qualify( propertyHolder.getPath(), propertyName ) ), + joinColumn, + joinColumns + ); String path = propertyHolder.getPath() + "." + propertyName; FkSecondPass secondPass = new ToOneFkSecondPass( @@ -3396,6 +3395,41 @@ public static FetchMode getFetchMode(FetchType fetch) { } } + public static void bindForeignKeyNameAndDefinition( + SimpleValue value, + XProperty property, + javax.persistence.ForeignKey fkOverride, + JoinColumn joinColumn, + JoinColumns joinColumns) { + if ( ( joinColumn != null && joinColumn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) + || ( joinColumns != null && joinColumns.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) ) { + value.setForeignKeyName( "none" ); + } + else { + final ForeignKey fk = property.getAnnotation( ForeignKey.class ); + if ( fk != null && StringHelper.isNotEmpty( fk.name() ) ) { + value.setForeignKeyName( fk.name() ); + } + else { + if ( fkOverride != null && fkOverride.value() == ConstraintMode.NO_CONSTRAINT ) { + value.setForeignKeyName( "none" ); + } + else if ( fkOverride != null ) { + value.setForeignKeyName( StringHelper.nullIfEmpty( fkOverride.name() ) ); + value.setForeignKeyDefinition( StringHelper.nullIfEmpty( fkOverride.foreignKeyDefinition() ) ); + } + else if ( joinColumns != null ) { + value.setForeignKeyName( StringHelper.nullIfEmpty( joinColumns.foreignKey().name() ) ); + value.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumns.foreignKey().foreignKeyDefinition() ) ); + } + else if ( joinColumn != null ) { + value.setForeignKeyName( StringHelper.nullIfEmpty( joinColumn.foreignKey().name() ) ); + value.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumn.foreignKey().foreignKeyDefinition() ) ); + } + } + } + } + private static HashMap buildGenerators(XAnnotatedElement annElt, MetadataBuildingContext context) { InFlightMetadataCollector metadataCollector = context.getMetadataCollector(); HashMap generators = new HashMap<>(); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index febc0c3b12fa247449fe77caba6d5068801f9bde..e7e75b217ba9725cc1f4d3e987eeacd91652ad51 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -859,6 +859,27 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { */ String ENFORCE_LEGACY_PROXY_CLASSNAMES = "hibernate.bytecode.enforce_legacy_proxy_classnames"; + /** + * Should Hibernate use enhanced entities "as a proxy"? + * + * E.g., when an application uses {@link org.hibernate.Session#load} against an enhanced + * class, enabling this will allow Hibernate to create an "empty" instance of the enhanced + * class to act as the proxy - it contains just the identifier which is later used to + * trigger the base initialization but no other data is loaded + * + * Not enabling this (the legacy default behavior) would cause the "base" attributes to + * be loaded. Any lazy-group attributes would not be initialized. + * + * Applications using bytecode enhancement and switching to allowing this should be careful + * in use of the various {@link org.hibernate.Hibernate} methods such as + * {@link org.hibernate.Hibernate#isInitialized}, + * {@link org.hibernate.Hibernate#isPropertyInitialized}, etc - enabling this setting changes + * the results of those methods + * + * @implSpec See {@link org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor} + */ + String ALLOW_ENHANCEMENT_AS_PROXY = "hibernate.bytecode.allow_enhancement_as_proxy"; + /** * The classname of the HQL query parser factory */ @@ -901,6 +922,18 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { */ String WRAP_RESULT_SETS = "hibernate.jdbc.wrap_result_sets"; + /** + * Indicates if exception handling for a SessionFactory built via Hibernate's native bootstrapping + * should behave the same as native exception handling in Hibernate ORM 5.1, When set to {@code true}, + * {@link HibernateException} will not be wrapped or converted according to the JPA specification. + *

    + * This setting will be ignored for a SessionFactory built via JPA bootstrapping. + *

    + * Values are {@code true} or {@code false}. + * Default value is {@code false} + */ + String NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE = "hibernate.native_exception_handling_51_compliance"; + /** * Enable ordering of update statements by primary key value */ @@ -1455,19 +1488,28 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { */ String HBM2DDL_HALT_ON_ERROR = "hibernate.hbm2ddl.halt_on_error"; - String JMX_ENABLED = "hibernate.jmx.enabled"; - String JMX_PLATFORM_SERVER = "hibernate.jmx.usePlatformServer"; - String JMX_AGENT_ID = "hibernate.jmx.agentId"; - String JMX_DOMAIN_NAME = "hibernate.jmx.defaultDomain"; - String JMX_SF_NAME = "hibernate.jmx.sessionFactoryName"; - String JMX_DEFAULT_OBJ_NAME_DOMAIN = "org.hibernate.core"; - /** * Setting to identify a {@link org.hibernate.CustomEntityDirtinessStrategy} to use. May point to * either a class name or instance. */ String CUSTOM_ENTITY_DIRTINESS_STRATEGY = "hibernate.entity_dirtiness_strategy"; + /** + * Controls whether an entity's "where" clause, mapped using @Where(clause="....") + * or <entity ... where="...">, is taken into account when loading one-to-many + * or many-to-many collections of that type of entity. + *

    + * This setting has no affect on collections of embeddable values containing an association to + * that type of entity. + *

    + * When `true` (the default), the entity's "where" clause will be taken into account when loading + * one-to-many or many-to-many collections of that type of entity. + *

    + * `false` indicates that the entity's "where" clause will be ignored when loading one-to-many or + * many-to-many collections of that type of entity. + */ + String USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS = "hibernate.use_entity_where_clause_for_collections"; + /** * Strategy for multi-tenancy. @@ -1584,10 +1626,6 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { */ String JTA_TRACK_BY_THREAD = "hibernate.jta.track_by_thread"; - String JACC_CONTEXT_ID = "hibernate.jacc_context_id"; - String JACC_PREFIX = "hibernate.jacc"; - String JACC_ENABLED = "hibernate.jacc.enabled"; - /** * If enabled, allows schema update and validation to support synonyms. Due * to the possibility that this would return duplicate tables (especially in @@ -1939,4 +1977,57 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { */ String IN_CLAUSE_PARAMETER_PADDING = "hibernate.query.in_clause_parameter_padding"; + /** + * @deprecated Support for JACC will be removed in 6.0 + */ + @Deprecated + String JACC_CONTEXT_ID = "hibernate.jacc_context_id"; + /** + * @deprecated Support for JACC will be removed in 6.0 + */ + @Deprecated + String JACC_PREFIX = "hibernate.jacc"; + /** + * @deprecated Support for JACC will be removed in 6.0 + */ + @Deprecated + String JACC_ENABLED = "hibernate.jacc.enabled"; + + /** + * @deprecated Scheduled for removal in 6.0; see https://hibernate.atlassian.net/browse/HHH-14847 + * and https://hibernate.atlassian.net/browse/HHH-14846 + */ + @Deprecated + String JMX_ENABLED = "hibernate.jmx.enabled"; + /** + * @deprecated Scheduled for removal in 6.0; see https://hibernate.atlassian.net/browse/HHH-14847 + * and https://hibernate.atlassian.net/browse/HHH-14846 + */ + @Deprecated + String JMX_PLATFORM_SERVER = "hibernate.jmx.usePlatformServer"; + /** + * @deprecated Scheduled for removal in 6.0; see https://hibernate.atlassian.net/browse/HHH-14847 + * and https://hibernate.atlassian.net/browse/HHH-14846 + */ + @Deprecated + String JMX_AGENT_ID = "hibernate.jmx.agentId"; + /** + * @deprecated Scheduled for removal in 6.0; see https://hibernate.atlassian.net/browse/HHH-14847 + * and https://hibernate.atlassian.net/browse/HHH-14846 + */ + @Deprecated + String JMX_DOMAIN_NAME = "hibernate.jmx.defaultDomain"; + /** + * @deprecated Scheduled for removal in 6.0; see https://hibernate.atlassian.net/browse/HHH-14847 + * and https://hibernate.atlassian.net/browse/HHH-14846 + */ + @Deprecated + String JMX_SF_NAME = "hibernate.jmx.sessionFactoryName"; + /** + * @deprecated Scheduled for removal in 6.0; see https://hibernate.atlassian.net/browse/HHH-14847 + * and https://hibernate.atlassian.net/browse/HHH-14846 + */ + @Deprecated + String JMX_DEFAULT_OBJ_NAME_DOMAIN = "org.hibernate.core"; + } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/EJB3DTDEntityResolver.java b/hibernate-core/src/main/java/org/hibernate/cfg/EJB3DTDEntityResolver.java index 8fa3956e220c9972f6aac20fa02e950ee4b3f25f..4adec44fdffa89ee4bfcb5fa76ab7af51256cbcf 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/EJB3DTDEntityResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/EJB3DTDEntityResolver.java @@ -41,13 +41,27 @@ public boolean isResolved() { public InputSource resolveEntity(String publicId, String systemId) { LOG.tracev( "Resolving XML entity {0} : {1}", publicId, systemId ); if ( systemId != null ) { - if ( systemId.endsWith( "orm_2_1.xsd" ) ) { + if ( systemId.endsWith( "orm_3_0.xsd" ) ) { + InputStream dtdStream = getStreamFromClasspath( "orm_3_0.xsd" ); + final InputSource source = buildInputSource( publicId, systemId, dtdStream, false ); + if ( source != null ) { + return source; + } + } + else if ( systemId.endsWith( "orm_2_1.xsd" ) ) { InputStream dtdStream = getStreamFromClasspath( "orm_2_1.xsd" ); final InputSource source = buildInputSource( publicId, systemId, dtdStream, false ); if ( source != null ) { return source; } } + else if ( systemId.endsWith( "orm_2_2.xsd" ) ) { + InputStream dtdStream = getStreamFromClasspath( "orm_2_2.xsd" ); + final InputSource source = buildInputSource( publicId, systemId, dtdStream, false ); + if ( source != null ) { + return source; + } + } else if ( systemId.endsWith( "orm_2_0.xsd" ) ) { InputStream dtdStream = getStreamFromClasspath( "orm_2_0.xsd" ); final InputSource source = buildInputSource( publicId, systemId, dtdStream, false ); @@ -62,6 +76,20 @@ else if ( systemId.endsWith( "orm_1_0.xsd" ) ) { return source; } } + else if ( systemId.endsWith( "persistence_3_0.xsd" ) ) { + InputStream dtdStream = getStreamFromClasspath( "persistence_3_0.xsd" ); + final InputSource source = buildInputSource( publicId, systemId, dtdStream, true ); + if ( source != null ) { + return source; + } + } + else if ( systemId.endsWith( "persistence_2_2.xsd" ) ) { + InputStream dtdStream = getStreamFromClasspath( "persistence_2_2.xsd" ); + final InputSource source = buildInputSource( publicId, systemId, dtdStream, true ); + if ( source != null ) { + return source; + } + } else if ( systemId.endsWith( "persistence_2_1.xsd" ) ) { InputStream dtdStream = getStreamFromClasspath( "persistence_2_1.xsd" ); final InputSource source = buildInputSource( publicId, systemId, dtdStream, true ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java index 7a1f3baa7b95bfdbc43efd1adaef9c69d7d1112d..c0e8eddc4a7e6e0f8c2b67299989b2cea4b249b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java @@ -8,11 +8,12 @@ import java.util.Iterator; import java.util.Map; -import javax.persistence.ConstraintMode; + +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; import org.hibernate.AnnotationException; import org.hibernate.MappingException; -import org.hibernate.annotations.ForeignKey; import org.hibernate.annotations.LazyGroup; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.boot.spi.MetadataBuildingContext; @@ -91,19 +92,19 @@ public void doSecondPass(Map persistentClasses) throws MappingException { value.setCascadeDeleteEnabled( cascadeOnDelete ); //value.setLazy( fetchMode != FetchMode.JOIN ); - if ( !optional ) { - value.setConstrained( true ); - } - if ( value.isReferenceToPrimaryKey() ) { - value.setForeignKeyType( ForeignKeyDirection.TO_PARENT ); - } - else { - value.setForeignKeyType( - value.isConstrained() - ? ForeignKeyDirection.FROM_PARENT - : ForeignKeyDirection.TO_PARENT - ); - } + value.setConstrained( !optional ); + final ForeignKeyDirection foreignKeyDirection = !BinderHelper.isEmptyAnnotationValue( mappedBy ) + ? ForeignKeyDirection.TO_PARENT + : ForeignKeyDirection.FROM_PARENT; + value.setForeignKeyType(foreignKeyDirection); + AnnotationBinder.bindForeignKeyNameAndDefinition( + value, + inferredData.getProperty(), + inferredData.getProperty().getAnnotation( javax.persistence.ForeignKey.class ), + inferredData.getProperty().getAnnotation( JoinColumn.class ), + inferredData.getProperty().getAnnotation( JoinColumns.class ) + ); + PropertyBinder binder = new PropertyBinder(); binder.setName( propertyName ); binder.setValue( value ); @@ -259,23 +260,6 @@ else if ( otherSideProperty.getValue() instanceof ManyToOne ) { ); } } - - final ForeignKey fk = inferredData.getProperty().getAnnotation( ForeignKey.class ); - if ( fk != null && !BinderHelper.isEmptyAnnotationValue( fk.name() ) ) { - value.setForeignKeyName( fk.name() ); - } - else { - final javax.persistence.ForeignKey jpaFk = inferredData.getProperty().getAnnotation( javax.persistence.ForeignKey.class ); - if ( jpaFk != null ) { - if ( jpaFk.value() == ConstraintMode.NO_CONSTRAINT ) { - value.setForeignKeyName( "none" ); - } - else { - value.setForeignKeyName( StringHelper.nullIfEmpty( jpaFk.name() ) ); - value.setForeignKeyDefinition( StringHelper.nullIfEmpty( jpaFk.foreignKeyDefinition() ) ); - } - } - } } /** diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java index 8c566518e923aaca8e5575f8ec6a7fd14e85ea15..75e3d85a01aebc6a067be5653cb6c19a4753e662 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java @@ -47,6 +47,7 @@ import org.hibernate.annotations.Loader; import org.hibernate.annotations.ManyToAny; import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; import org.hibernate.annotations.OptimisticLock; import org.hibernate.annotations.OrderBy; import org.hibernate.annotations.Parameter; @@ -70,6 +71,7 @@ import org.hibernate.cfg.AccessType; import org.hibernate.cfg.AnnotatedClassType; import org.hibernate.cfg.AnnotationBinder; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.BinderHelper; import org.hibernate.cfg.CollectionPropertyHolder; import org.hibernate.cfg.CollectionSecondPass; @@ -84,9 +86,11 @@ import org.hibernate.cfg.PropertyPreloadedData; import org.hibernate.cfg.SecondPass; import org.hibernate.criterion.Junction; +import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.mapping.Any; import org.hibernate.mapping.Backref; import org.hibernate.mapping.Collection; @@ -98,6 +102,7 @@ import org.hibernate.mapping.ManyToOne; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; +import org.hibernate.mapping.Selectable; import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.Table; @@ -953,42 +958,58 @@ private void bindFilters(boolean hasAssociationTable) { } } - StringBuilder whereBuffer = new StringBuilder(); - if ( property.getElementClass() != null ) { + final boolean useEntityWhereClauseForCollections = ConfigurationHelper.getBoolean( + AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, + buildingContext + .getBuildingOptions() + .getServiceRegistry() + .getService( ConfigurationService.class ) + .getSettings(), + true + ); + + // There are 2 possible sources of "where" clauses that apply to the associated entity table: + // 1) from the associated entity mapping; i.e., @Entity @Where(clause="...") + // (ignored if useEntityWhereClauseForCollections == false) + // 2) from the collection mapping; + // for one-to-many, e.g., @OneToMany @JoinColumn @Where(clause="...") public Set getRatings(); + // for many-to-many e.g., @ManyToMany @Where(clause="...") public Set getRatings(); + String whereOnClassClause = null; + if ( useEntityWhereClauseForCollections && property.getElementClass() != null ) { Where whereOnClass = property.getElementClass().getAnnotation( Where.class ); if ( whereOnClass != null ) { - String clause = whereOnClass.clause(); - if ( StringHelper.isNotEmpty( clause ) ) { - whereBuffer.append( clause ); - } + whereOnClassClause = whereOnClass.clause(); } } Where whereOnCollection = property.getAnnotation( Where.class ); + String whereOnCollectionClause = null; if ( whereOnCollection != null ) { - String clause = whereOnCollection.clause(); - if ( StringHelper.isNotEmpty( clause ) ) { - if ( whereBuffer.length() > 0 ) { - whereBuffer.append( ' ' ); - whereBuffer.append( Junction.Nature.AND.getOperator() ); - whereBuffer.append( ' ' ); - } - whereBuffer.append( clause ); - } + whereOnCollectionClause = whereOnCollection.clause(); } - if ( whereBuffer.length() > 0 ) { - String whereClause = whereBuffer.toString(); - if ( hasAssociationTable ) { - collection.setManyToManyWhere( whereClause ); - } - else { - collection.setWhere( whereClause ); - } + final String whereClause = StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty( + whereOnClassClause, + whereOnCollectionClause + ); + if ( hasAssociationTable ) { + // A many-to-many association has an association (join) table + // Collection#setManytoManyWhere is used to set the "where" clause that applies to + // to the many-to-many associated entity table (not the join table). + collection.setManyToManyWhere( whereClause ); + } + else { + // A one-to-many association does not have an association (join) table. + // Collection#setWhere is used to set the "where" clause that applies to the collection table + // (which is the associated entity table for a one-to-many association). + collection.setWhere( whereClause ); } WhereJoinTable whereJoinTable = property.getAnnotation( WhereJoinTable.class ); String whereJoinTableClause = whereJoinTable == null ? null : whereJoinTable.clause(); if ( StringHelper.isNotEmpty( whereJoinTableClause ) ) { if ( hasAssociationTable ) { + // This is a many-to-many association. + // 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). collection.setWhere( whereJoinTableClause ); } else { @@ -1191,14 +1212,24 @@ else if ( fkOverride != null ) { key.setForeignKeyDefinition( StringHelper.nullIfEmpty( fkOverride.foreignKeyDefinition() ) ); } else { - final JoinColumn joinColumnAnn = property.getAnnotation( JoinColumn.class ); - if ( joinColumnAnn != null ) { - if ( joinColumnAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) { - key.setForeignKeyName( "none" ); - } - else { - key.setForeignKeyName( StringHelper.nullIfEmpty( joinColumnAnn.foreignKey().name() ) ); - key.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumnAnn.foreignKey().foreignKeyDefinition() ) ); + final OneToMany oneToManyAnn = property.getAnnotation( OneToMany.class ); + final OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); + if ( oneToManyAnn != null && !oneToManyAnn.mappedBy().isEmpty() + && ( onDeleteAnn == null || onDeleteAnn.action() != OnDeleteAction.CASCADE ) ) { + // foreign key should be up to @ManyToOne side + // @OnDelete generate "on delete cascade" foreign key + key.setForeignKeyName( "none" ); + } + else { + final JoinColumn joinColumnAnn = property.getAnnotation( JoinColumn.class ); + if ( joinColumnAnn != null ) { + if ( joinColumnAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) { + key.setForeignKeyName( "none" ); + } + else { + key.setForeignKeyName( StringHelper.nullIfEmpty( joinColumnAnn.foreignKey().name() ) ); + key.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumnAnn.foreignKey().foreignKeyDefinition() ) ); + } } } } @@ -1660,13 +1691,13 @@ public static void bindManytoManyInverseFk( final String mappedBy = columns[0].getMappedBy(); if ( StringHelper.isNotEmpty( mappedBy ) ) { final Property property = referencedEntity.getRecursiveProperty( mappedBy ); - Iterator mappedByColumns; + Iterator mappedByColumns; if ( property.getValue() instanceof Collection ) { mappedByColumns = ( (Collection) property.getValue() ).getKey().getColumnIterator(); } else { //find the appropriate reference key, can be in a join - Iterator joinsIt = referencedEntity.getJoinIterator(); + Iterator joinsIt = referencedEntity.getJoinIterator(); KeyValue key = null; while ( joinsIt.hasNext() ) { Join join = (Join) joinsIt.next(); @@ -1675,7 +1706,9 @@ public static void bindManytoManyInverseFk( break; } } - if ( key == null ) key = property.getPersistentClass().getIdentifier(); + if ( key == null ) { + key = property.getPersistentClass().getIdentifier(); + } mappedByColumns = key.getColumnIterator(); } while ( mappedByColumns.hasNext() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java index 19ede93efeb312c58909b895b9425b802a1c8b43..ad96518092ced09fc238bdffc2a998e3b75a8afa 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java @@ -1006,12 +1006,11 @@ private SecondaryTable findMatchingSecondaryTable(Join join) { SecondaryTables secondaryTables = annotatedClass.getAnnotation( SecondaryTables.class ); if ( secondaryTables != null ) { - for ( SecondaryTable secondaryTable2 : secondaryTables.value() ) { - if ( secondaryTable != null && nameToMatch.equals( secondaryTable.name() ) ) { - return secondaryTable; + for ( SecondaryTable secondaryTablesEntry : secondaryTables.value() ) { + if ( secondaryTablesEntry != null && nameToMatch.equals( secondaryTablesEntry.name() ) ) { + return secondaryTablesEntry; } } - } return null; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java index 11e918bdac33529299d0084475243581de1f28b9..6478a90cb12cf1470104ccbc21595de3ec0ac172 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java @@ -48,11 +48,15 @@ public static void bindQuery( NamedQuery queryAnn, MetadataBuildingContext context, boolean isDefault) { - if ( queryAnn == null ) return; + if ( queryAnn == null ) { + return; + } + if ( BinderHelper.isEmptyAnnotationValue( queryAnn.name() ) ) { throw new AnnotationException( "A named query must have a name when used in class or package level" ); } - //EJBQL Query + + // JPA-QL Query QueryHintDefinition hints = new QueryHintDefinition( queryAnn.hints() ); String queryName = queryAnn.query(); NamedQueryDefinition queryDefinition = new NamedQueryDefinitionBuilder( queryAnn.name() ) @@ -112,14 +116,17 @@ public static void bindNativeQuery( if ( !BinderHelper.isEmptyAnnotationValue( resultSetMapping ) ) { //sql result set usage - builder.setResultSetRef( resultSetMapping ) - .createNamedQueryDefinition(); + builder.setResultSetRef( resultSetMapping ).createNamedQueryDefinition(); } else if ( !void.class.equals( queryAnn.resultClass() ) ) { //class mapping usage //FIXME should be done in a second pass due to entity name? - final NativeSQLQueryRootReturn entityQueryReturn = - new NativeSQLQueryRootReturn( "alias1", queryAnn.resultClass().getName(), new HashMap(), LockMode.READ ); + final NativeSQLQueryRootReturn entityQueryReturn = new NativeSQLQueryRootReturn( + "alias1", + queryAnn.resultClass().getName(), + new HashMap(), + LockMode.READ + ); builder.setQueryReturns( new NativeSQLQueryReturn[] {entityQueryReturn} ); } else { @@ -151,59 +158,50 @@ public static void bindNativeQuery( throw new AnnotationException( "A named query must have a name when used in class or package level" ); } - NamedSQLQueryDefinition query; - String resultSetMapping = queryAnn.resultSetMapping(); + final String resultSetMapping = queryAnn.resultSetMapping(); + + final NamedSQLQueryDefinitionBuilder builder = new NamedSQLQueryDefinitionBuilder() + .setName( queryAnn.name() ) + .setQuery( queryAnn.query() ) + .setCacheable( queryAnn.cacheable() ) + .setCacheRegion( + BinderHelper.isEmptyAnnotationValue( queryAnn.cacheRegion() ) + ? null + : queryAnn.cacheRegion() + ) + .setTimeout( queryAnn.timeout() < 0 ? null : queryAnn.timeout() ) + .setFetchSize( queryAnn.fetchSize() < 0 ? null : queryAnn.fetchSize() ) + .setFlushMode( getFlushMode( queryAnn.flushMode() ) ) + .setCacheMode( getCacheMode( queryAnn.cacheMode() ) ) + .setReadOnly( queryAnn.readOnly() ) + .setComment( BinderHelper.isEmptyAnnotationValue( queryAnn.comment() ) ? null : queryAnn.comment() ) + .setParameterTypes( null ) + .setCallable( queryAnn.callable() ); + + if ( !BinderHelper.isEmptyAnnotationValue( resultSetMapping ) ) { //sql result set usage - query = new NamedSQLQueryDefinitionBuilder().setName( queryAnn.name() ) - .setQuery( queryAnn.query() ) - .setResultSetRef( resultSetMapping ) - .setQuerySpaces( null ) - .setCacheable( queryAnn.cacheable() ) - .setCacheRegion( - BinderHelper.isEmptyAnnotationValue( queryAnn.cacheRegion() ) ? - null : - queryAnn.cacheRegion() - ) - .setTimeout( queryAnn.timeout() < 0 ? null : queryAnn.timeout() ) - .setFetchSize( queryAnn.fetchSize() < 0 ? null : queryAnn.fetchSize() ) - .setFlushMode( getFlushMode( queryAnn.flushMode() ) ) - .setCacheMode( getCacheMode( queryAnn.cacheMode() ) ) - .setReadOnly( queryAnn.readOnly() ) - .setComment( BinderHelper.isEmptyAnnotationValue( queryAnn.comment() ) ? null : queryAnn.comment() ) - .setParameterTypes( null ) - .setCallable( queryAnn.callable() ) - .createNamedQueryDefinition(); + builder.setResultSetRef( resultSetMapping ); } - else if ( !void.class.equals( queryAnn.resultClass() ) ) { + else if ( ! void.class.equals( queryAnn.resultClass() ) ) { //class mapping usage //FIXME should be done in a second pass due to entity name? - final NativeSQLQueryRootReturn entityQueryReturn = - new NativeSQLQueryRootReturn( "alias1", queryAnn.resultClass().getName(), new HashMap(), LockMode.READ ); - query = new NamedSQLQueryDefinitionBuilder().setName( queryAnn.name() ) - .setQuery( queryAnn.query() ) - .setQueryReturns( new NativeSQLQueryReturn[] {entityQueryReturn} ) - .setQuerySpaces( null ) - .setCacheable( queryAnn.cacheable() ) - .setCacheRegion( - BinderHelper.isEmptyAnnotationValue( queryAnn.cacheRegion() ) ? - null : - queryAnn.cacheRegion() - ) - .setTimeout( queryAnn.timeout() < 0 ? null : queryAnn.timeout() ) - .setFetchSize( queryAnn.fetchSize() < 0 ? null : queryAnn.fetchSize() ) - .setFlushMode( getFlushMode( queryAnn.flushMode() ) ) - .setCacheMode( getCacheMode( queryAnn.cacheMode() ) ) - .setReadOnly( queryAnn.readOnly() ) - .setComment( BinderHelper.isEmptyAnnotationValue( queryAnn.comment() ) ? null : queryAnn.comment() ) - .setParameterTypes( null ) - .setCallable( queryAnn.callable() ) - .createNamedQueryDefinition(); + final NativeSQLQueryRootReturn entityQueryReturn = new NativeSQLQueryRootReturn( + "alias1", + queryAnn.resultClass().getName(), + new HashMap(), + LockMode.READ + ); + builder.setQueryReturns( new NativeSQLQueryReturn[] {entityQueryReturn} ); } else { - throw new NotYetImplementedException( "Pure native scalar queries are not yet supported" ); + LOG.debugf( "Raw scalar native-query (no explicit result mappings) found : %s", queryAnn.name() ); } + + final NamedSQLQueryDefinition query = builder.createNamedQueryDefinition(); + context.getMetadataCollector().addNamedNativeQuery( query ); + if ( LOG.isDebugEnabled() ) { LOG.debugf( "Binding named native query: %s => %s", query.getName(), queryAnn.query() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java index fc8389a8243de81bfb35f62035b1f45e17a59a50..938b9ae30a66b377a454f1f621eee23633c24429 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java @@ -26,6 +26,7 @@ import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.engine.spi.TypedValue; @@ -37,6 +38,7 @@ import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; +import org.hibernate.resource.transaction.spi.TransactionStatus; import org.hibernate.type.CompositeType; import org.hibernate.type.IntegerType; import org.hibernate.type.LongType; @@ -85,6 +87,14 @@ protected AbstractPersistentCollection(SharedSessionContractImplementor session) this.session = session; } + /** + * @deprecated {@link #AbstractPersistentCollection(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + protected AbstractPersistentCollection(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + @Override public final String getRole() { return role; @@ -503,6 +513,7 @@ protected final void performQueuedOperations() { for ( DelayedOperation operation : operationQueue ) { operation.operate(); } + clearOperationQueue(); } @Override @@ -514,11 +525,15 @@ public void setSnapshot(Serializable key, String role, Serializable snapshot) { @Override public void postAction() { - operationQueue = null; + clearOperationQueue(); cachedSize = -1; clearDirty(); } + public final void clearOperationQueue() { + operationQueue = null; + } + @Override public Object getValue() { return this; @@ -540,9 +555,8 @@ public boolean endRead() { public boolean afterInitialize() { setInitialized(); //do this bit after setting initialized to true or it will recurse - if ( operationQueue != null ) { + if ( hasQueuedOperations() ) { performQueuedOperations(); - operationQueue = null; cachedSize = -1; return false; } @@ -611,6 +625,33 @@ public final boolean unsetSession(SharedSessionContractImplementor currentSessio prepareForPossibleLoadingOutsideTransaction(); if ( currentSession == this.session ) { if ( !isTempSession ) { + if ( hasQueuedOperations() ) { + final String collectionInfoString = MessageHelper.collectionInfoString( getRole(), getKey() ); + try { + final TransactionStatus transactionStatus = + session.getTransactionCoordinator().getTransactionDriverControl().getStatus(); + if ( transactionStatus.isOneOf( + TransactionStatus.ROLLED_BACK, + TransactionStatus.MARKED_ROLLBACK, + TransactionStatus.FAILED_COMMIT, + TransactionStatus.FAILED_ROLLBACK, + TransactionStatus.ROLLING_BACK + ) ) { + // It was due to a rollback. + LOG.queuedOperationWhenDetachFromSessionOnRollback( collectionInfoString ); + } + else { + // We don't know why the collection is being detached. + // Just log the info. + LOG.queuedOperationWhenDetachFromSession( collectionInfoString ); + } + } + catch (Exception e) { + // We don't know why the collection is being detached. + // Just log the info. + LOG.queuedOperationWhenDetachFromSession( collectionInfoString ); + } + } this.session = null; } return true; @@ -638,25 +679,22 @@ public final boolean setCurrentSession(SharedSessionContractImplementor session) if ( session == this.session ) { return false; } - else { - if ( this.session != null ) { - final String msg = generateUnexpectedSessionStateMessage( session ); - if ( isConnectedToSession() ) { - throw new HibernateException( - "Illegal attempt to associate a collection with two open sessions. " + msg - ); - } - else { - LOG.logUnexpectedSessionInCollectionNotConnected( msg ); - this.session = session; - return true; - } + else if ( this.session != null ) { + final String msg = generateUnexpectedSessionStateMessage( session ); + if ( isConnectedToSession() ) { + throw new HibernateException( + "Illegal attempt to associate a collection with two open sessions. " + msg + ); } else { - this.session = session; - return true; + LOG.logUnexpectedSessionInCollectionNotConnected( msg ); } } + if ( hasQueuedOperations() ) { + LOG.queuedOperationWhenAttachToSession( MessageHelper.collectionInfoString( getRole(), getKey() ) ); + } + this.session = session; + return true; } private String generateUnexpectedSessionStateMessage(SharedSessionContractImplementor session) { @@ -1281,6 +1319,26 @@ public static void identityRemove( } } + /** + * Removes entity entries that have an equal identifier with the incoming entity instance + * + * @param list The list containing the entity instances + * @param entityInstance The entity instance to match elements. + * @param entityName The entity name + * @param session The session + * + * @deprecated {@link #identityRemove(Collection, Object, String, SharedSessionContractImplementor)} + * should be used instead. + */ + @Deprecated + public static void identityRemove( + Collection list, + Object entityInstance, + String entityName, + SessionImplementor session) { + identityRemove( list, entityInstance, entityName, (SharedSessionContractImplementor) session ); + } + @Override public Object getIdentifier(Object entry, int i) { throw new UnsupportedOperationException(); diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java index bb36565ab7db709f181088f8d319c206d8877fb5..866766afef5f1592b5394224d5a9256d785d7743 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java @@ -16,6 +16,7 @@ import java.util.Iterator; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.loader.CollectionAliases; @@ -55,6 +56,20 @@ public PersistentArrayHolder(SharedSessionContractImplementor session, Object ar setInitialized(); } + /** + * Constructs a PersistentCollection instance for holding an array. + * + * @param session The session + * @param array The array (the persistent "collection"). + * + * @deprecated {@link #PersistentArrayHolder(SharedSessionContractImplementor, Object)} + * should be used instead. + */ + @Deprecated + public PersistentArrayHolder(SessionImplementor session, Object array) { + this( (SharedSessionContractImplementor) session, array ); + } + /** * Constructs a PersistentCollection instance for holding an array. * @@ -66,7 +81,19 @@ public PersistentArrayHolder(SharedSessionContractImplementor session, Collectio elementClass = persister.getElementClass(); } - + /** + * Constructs a PersistentCollection instance for holding an array. + * + * @param session The session + * @param persister The persister for the array + * + * @deprecated {@link #PersistentArrayHolder(SharedSessionContractImplementor, CollectionPersister)} + * should be used instead. + */ + @Deprecated + public PersistentArrayHolder(SessionImplementor session, CollectionPersister persister) { + this( (SharedSessionContractImplementor) session, persister ); + } @Override public Serializable getSnapshot(CollectionPersister persister) throws HibernateException { diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java index 3762d456e2b0448643b2e7fb6df132b694c466ac..20cc23f4404ea036caae81dd78d17cc425ecfb72 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java @@ -11,11 +11,15 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ListIterator; +import java.util.Map; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -33,6 +37,9 @@ public class PersistentBag extends AbstractPersistentCollection implements List protected List bag; + // The Collection provided to a PersistentBag constructor, + private Collection providedCollection; + /** * Constructs a PersistentBag. Needed for SOAP libraries, etc */ @@ -49,6 +56,18 @@ public PersistentBag(SharedSessionContractImplementor session) { super( session ); } + /** + * Constructs a PersistentBag + * + * @param session The session + * + * @deprecated {@link #PersistentBag(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentBag(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Constructs a PersistentBag * @@ -58,6 +77,7 @@ public PersistentBag(SharedSessionContractImplementor session) { @SuppressWarnings("unchecked") public PersistentBag(SharedSessionContractImplementor session, Collection coll) { super( session ); + providedCollection = coll; if ( coll instanceof List ) { bag = (List) coll; } @@ -71,9 +91,28 @@ public PersistentBag(SharedSessionContractImplementor session, Collection coll) setDirectlyAccessible( true ); } + /** + * Constructs a PersistentBag + * + * @param session The session + * @param coll The base elements. + * + * @deprecated {@link #PersistentBag(SharedSessionContractImplementor, Collection)} + * should be used instead. + */ + @Deprecated + public PersistentBag(SessionImplementor session, Collection coll) { + this( (SharedSessionContractImplementor) session, coll ); + } + @Override public boolean isWrapper(Object collection) { - return bag==collection; + return bag == collection; + } + + @Override + public boolean isDirectlyProvidedCollection(Object collection) { + return isDirectlyAccessible() && providedCollection == collection; } @Override @@ -92,7 +131,7 @@ public Object readFrom(ResultSet rs, CollectionPersister persister, CollectionAl throws HibernateException, SQLException { // note that if we load this collection from a cartesian product // the multiplicity would be broken ... so use an idbag instead - final Object element = persister.readElement( rs, owner, descriptor.getSuffixedElementAliases(), getSession() ) ; + final Object element = persister.readElement( rs, owner, descriptor.getSuffixedElementAliases(), getSession() ); if ( element != null ) { bag.add( element ); } @@ -105,38 +144,112 @@ public void beforeInitialize(CollectionPersister persister, int anticipatedSize) } @Override + @SuppressWarnings("unchecked") public boolean equalsSnapshot(CollectionPersister persister) throws HibernateException { final Type elementType = persister.getElementType(); - final List sn = (List) getSnapshot(); + final List sn = (List) getSnapshot(); if ( sn.size() != bag.size() ) { return false; } - for ( Object elt : bag ) { - final boolean unequal = countOccurrences( elt, bag, elementType ) != countOccurrences( elt, sn, elementType ); - if ( unequal ) { + + // HHH-11032 - Group objects by Type.getHashCode() to reduce the complexity of the search + final Map> hashToInstancesBag = groupByEqualityHash( bag, elementType ); + final Map> hashToInstancesSn = groupByEqualityHash( sn, elementType ); + if ( hashToInstancesBag.size() != hashToInstancesSn.size() ) { + return false; + } + + // First iterate over the hashToInstancesBag entries to see if the number + // of List values is different for any hash value. + for ( Map.Entry> hashToInstancesBagEntry : hashToInstancesBag.entrySet() ) { + final Integer hash = hashToInstancesBagEntry.getKey(); + final List instancesBag = hashToInstancesBagEntry.getValue(); + final List instancesSn = hashToInstancesSn.get( hash ); + if ( instancesSn == null || ( instancesBag.size() != instancesSn.size() ) ) { return false; } } + + // We already know that both hashToInstancesBag and hashToInstancesSn have: + // 1) the same hash values; + // 2) the same number of values with the same hash value. + + // Now check if the number of occurrences of each element is the same. + for ( Map.Entry> hashToInstancesBagEntry : hashToInstancesBag.entrySet() ) { + final Integer hash = hashToInstancesBagEntry.getKey(); + final List instancesBag = hashToInstancesBagEntry.getValue(); + final List instancesSn = hashToInstancesSn.get( hash ); + for ( Object instance : instancesBag ) { + if ( !expectOccurrences( + instance, + instancesBag, + elementType, + countOccurrences( instance, instancesSn, elementType ) + ) ) { + return false; + } + } + } return true; } + /** + * Groups items in searchedBag according to persistence "equality" as defined in Type.isSame and Type.getHashCode + * + * @return Map of "equality" hashCode to List of objects + */ + private Map> groupByEqualityHash(List searchedBag, Type elementType) { + if ( searchedBag.isEmpty() ) { + return Collections.emptyMap(); + } + Map> map = new HashMap<>(); + for ( Object o : searchedBag ) { + map.computeIfAbsent( nullableHashCode( o, elementType ), k -> new ArrayList<>() ).add( o ); + } + return map; + } + + /** + * @param o + * @param elementType + * @return the default elementType hashcode of the object o, or null if the object is null + */ + private Integer nullableHashCode(Object o, Type elementType) { + if ( o == null ) { + return null; + } + else { + return elementType.getHashCode( o ); + } + } + @Override public boolean isSnapshotEmpty(Serializable snapshot) { return ( (Collection) snapshot ).isEmpty(); } - private int countOccurrences(Object element, List list, Type elementType) - throws HibernateException { - final Iterator iter = list.iterator(); + private int countOccurrences(Object element, List list, Type elementType) { int result = 0; - while ( iter.hasNext() ) { - if ( elementType.isSame( element, iter.next() ) ) { + for ( Object listElement : list ) { + if ( elementType.isSame( element, listElement ) ) { result++; } } return result; } + private boolean expectOccurrences(Object element, List list, Type elementType, int expected) { + int result = 0; + for ( Object listElement : list ) { + if ( elementType.isSame( element, listElement ) ) { + if ( result++ > expected ) { + return false; + } + } + } + return result == expected; + } + @Override @SuppressWarnings("unchecked") public Serializable getSnapshot(CollectionPersister persister) @@ -159,7 +272,7 @@ public Serializable disassemble(CollectionPersister persister) throws HibernateException { final int length = bag.size(); final Serializable[] result = new Serializable[length]; - for ( int i=0; ii && elementType.isSame( old, bag.get( i++ ) ) ) { - //a shortcut if its location didn't change! + if ( bag.size() > i && elementType.isSame( old, bag.get( i++ ) ) ) { + //a shortcut if its location didn't change! found = true; } else { @@ -263,7 +376,7 @@ public int size() { @Override public boolean isEmpty() { - return readSize() ? getCachedSize()==0 : bag.isEmpty(); + return readSize() ? getCachedSize() == 0 : bag.isEmpty(); } @Override @@ -326,7 +439,7 @@ public boolean containsAll(Collection c) { @Override @SuppressWarnings("unchecked") public boolean addAll(Collection values) { - if ( values.size()==0 ) { + if ( values.size() == 0 ) { return false; } if ( !isOperationQueueEnabled() ) { @@ -337,14 +450,14 @@ public boolean addAll(Collection values) { for ( Object value : values ) { queueOperation( new SimpleAdd( value ) ); } - return values.size()>0; + return values.size() > 0; } } @Override @SuppressWarnings("unchecked") public boolean removeAll(Collection c) { - if ( c.size()>0 ) { + if ( c.size() > 0 ) { initialize( true ); if ( bag.removeAll( c ) ) { elementRemoved = true; @@ -381,7 +494,7 @@ public void clear() { } else { initialize( true ); - if ( ! bag.isEmpty() ) { + if ( !bag.isEmpty() ) { bag.clear(); dirty(); } @@ -390,7 +503,7 @@ public void clear() { @Override public Object getIndex(Object entry, int i, CollectionPersister persister) { - throw new UnsupportedOperationException("Bags don't have indexes"); + throw new UnsupportedOperationException( "Bags don't have indexes" ); } @Override @@ -503,7 +616,7 @@ public List subList(int start, int end) { @Override public boolean entryExists(Object entry, int i) { - return entry!=null; + return entry != null; } @Override @@ -517,8 +630,9 @@ public String toString() { * JVM instance comparison to do the equals. * The semantic is broken not to have to initialize a * collection for a simple equals() operation. - * @see java.lang.Object#equals(java.lang.Object) * + * @see java.lang.Object#equals(java.lang.Object) + *

    * {@inheritDoc} */ @Override @@ -544,7 +658,7 @@ public Object getAddedInstance() { @Override public Object getOrphan() { - throw new UnsupportedOperationException("queued clear cannot be used with orphan delete"); + throw new UnsupportedOperationException( "queued clear cannot be used with orphan delete" ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java index f115e0b4eddfb95b8a2bbc0bd21416dc91b74449..217c330cf38d733447c5a50026dd243015451ce1 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java @@ -18,6 +18,7 @@ import java.util.Map; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -39,6 +40,9 @@ public class PersistentIdentifierBag extends AbstractPersistentCollection implem protected List values; protected Map identifiers; + // The Collection provided to a PersistentIdentifierBag constructor, + private Collection providedValues; + /** * Constructs a PersistentIdentifierBag. This form needed for SOAP libraries, etc */ @@ -55,6 +59,17 @@ public PersistentIdentifierBag(SharedSessionContractImplementor session) { super( session ); } + /** + * Constructs a PersistentIdentifierBag. + * + * @param session The session + * @deprecated {@link #PersistentIdentifierBag(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentIdentifierBag(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Constructs a PersistentIdentifierBag. * @@ -64,6 +79,7 @@ public PersistentIdentifierBag(SharedSessionContractImplementor session) { @SuppressWarnings("unchecked") public PersistentIdentifierBag(SharedSessionContractImplementor session, Collection coll) { super( session ); + providedValues = coll; if (coll instanceof List) { values = (List) coll; } @@ -78,6 +94,18 @@ public PersistentIdentifierBag(SharedSessionContractImplementor session, Collect identifiers = new HashMap<>(); } + /** + * Constructs a PersistentIdentifierBag. + * + * @param session The session + * @param coll The base elements + * @deprecated {@link #PersistentIdentifierBag(SharedSessionContractImplementor, Collection)} should be used instead. + */ + @Deprecated + public PersistentIdentifierBag(SessionImplementor session, Collection coll) { + this( (SharedSessionContractImplementor) session, coll ); + } + @Override public void initializeFromCache(CollectionPersister persister, Serializable disassembled, Object owner) throws HibernateException { @@ -100,7 +128,12 @@ public Object getIdentifier(Object entry, int i) { @Override public boolean isWrapper(Object collection) { - return values==collection; + return values == collection; + } + + @Override + public boolean isDirectlyProvidedCollection(Object collection) { + return isDirectlyAccessible() && providedValues == collection; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java index 9e74d430d915fb07e5c0e39ff361d6c36eaf703c..8a0015665224f14685af08622df886370e713d17 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java @@ -16,6 +16,7 @@ import java.util.ListIterator; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -46,6 +47,17 @@ public PersistentList(SharedSessionContractImplementor session) { super( session ); } + /** + * Constructs a PersistentList. + * + * @param session The session + * @deprecated {@link #PersistentList(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentList(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Constructs a PersistentList. * @@ -59,6 +71,18 @@ public PersistentList(SharedSessionContractImplementor session, List list) { setDirectlyAccessible( true ); } + /** + * Constructs a PersistentList. + * + * @param session The session + * @param list The raw list + * @deprecated {@link #PersistentList(SharedSessionContractImplementor, List)} should be used instead. + */ + @Deprecated + public PersistentList(SessionImplementor session, List list) { + this( (SharedSessionContractImplementor) session, list ); + } + @Override @SuppressWarnings( {"unchecked"}) public Serializable getSnapshot(CollectionPersister persister) throws HibernateException { diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java index 0e8e3aa5593ca5956ba2f2d79ffd51b6bc984f95..0976a3cf7e1d3a914fca0a12ea608987310c2b0c 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java @@ -18,6 +18,7 @@ import java.util.Set; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -54,6 +55,17 @@ public PersistentMap(SharedSessionContractImplementor session) { super( session ); } + /** + * Instantiates a lazy map (the underlying map is un-initialized). + * + * @param session The session to which this map will belong. + * @deprecated {@link #PersistentMap(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentMap(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Instantiates a non-lazy map (the underlying map is constructed * from the incoming map reference). @@ -68,6 +80,19 @@ public PersistentMap(SharedSessionContractImplementor session, Map map) { setDirectlyAccessible( true ); } + /** + * Instantiates a non-lazy map (the underlying map is constructed + * from the incoming map reference). + * + * @param session The session to which this map will belong. + * @param map The underlying map data. + * @deprecated {@link #PersistentMap(SharedSessionContractImplementor, Map)} should be used instead. + */ + @Deprecated + public PersistentMap(SessionImplementor session, Map map) { + this( (SharedSessionContractImplementor) session, map ); + } + @Override @SuppressWarnings( {"unchecked"}) public Serializable getSnapshot(CollectionPersister persister) throws HibernateException { diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java index 317c6f0dacc407912b4d7c60eae01bb716386787..0f9a77f72f52b5efb3d609346909a0345cb57ee5 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java @@ -17,6 +17,7 @@ import java.util.Set; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.CollectionAliases; import org.hibernate.persister.collection.CollectionPersister; @@ -54,6 +55,17 @@ public PersistentSet(SharedSessionContractImplementor session) { super( session ); } + /** + * Instantiates a lazy set (the underlying set is un-initialized). + * + * @param session The session to which this set will belong. + * @deprecated {@link #PersistentSet(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentSet(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Instantiates a non-lazy set (the underlying set is constructed * from the incoming set reference). @@ -72,6 +84,19 @@ public PersistentSet(SharedSessionContractImplementor session, java.util.Set set setDirectlyAccessible( true ); } + /** + * Instantiates a non-lazy set (the underlying set is constructed + * from the incoming set reference). + * + * @param session The session to which this set will belong. + * @param set The underlying set data. + * @deprecated {@link #PersistentSet(SharedSessionContractImplementor, java.util.Set)} should be used instead. + */ + @Deprecated + public PersistentSet(SessionImplementor session, java.util.Set set) { + this( (SharedSessionContractImplementor) session, set ); + } + @Override @SuppressWarnings( {"unchecked"}) public Serializable getSnapshot(CollectionPersister persister) throws HibernateException { diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedMap.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedMap.java index b3e67b28da41203795c8203a1da8d0c1076cf8e7..f47825d0963f7fa293208aaa39ba135566e4cded 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedMap.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedMap.java @@ -46,6 +46,17 @@ public PersistentSortedMap(SharedSessionContractImplementor session) { super( session ); } + /** + * Constructs a PersistentSortedMap. + * + * @param session The session + * @deprecated {@link #PersistentSortedMap(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentSortedMap(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Constructs a PersistentSortedMap. * @@ -57,6 +68,18 @@ public PersistentSortedMap(SharedSessionContractImplementor session, SortedMap m comparator = map.comparator(); } + /** + * Constructs a PersistentSortedMap. + * + * @param session The session + * @param map The underlying map data + * @deprecated {@link #PersistentSortedMap(SharedSessionContractImplementor, SortedMap)} should be used instead. + */ + @Deprecated + public PersistentSortedMap(SessionImplementor session, SortedMap map) { + this( (SharedSessionContractImplementor) session, map ); + } + @SuppressWarnings({"unchecked", "UnusedParameters"}) protected Serializable snapshot(BasicCollectionPersister persister, EntityMode entityMode) throws HibernateException { final TreeMap clonedMap = new TreeMap( comparator ); diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedSet.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedSet.java index 75d3606bbdebb1c1cef94c79807794a03235c8ca..b52e6bff31f3dc71ff4c9242abfb61f1783762d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedSet.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSortedSet.java @@ -43,6 +43,17 @@ public PersistentSortedSet(SharedSessionContractImplementor session) { super( session ); } + /** + * Constructs a PersistentSortedSet + * + * @param session The session + * @deprecated {@link #PersistentSortedSet(SharedSessionContractImplementor)} should be used instead. + */ + @Deprecated + public PersistentSortedSet(SessionImplementor session) { + this( (SharedSessionContractImplementor) session ); + } + /** * Constructs a PersistentSortedSet * @@ -54,6 +65,18 @@ public PersistentSortedSet(SharedSessionContractImplementor session, SortedSet s comparator = set.comparator(); } + /** + * Constructs a PersistentSortedSet + * + * @param session The session + * @param set The underlying set data + * @deprecated {@link #PersistentSortedSet(SharedSessionContractImplementor, SortedSet)} should be used instead. + */ + @Deprecated + public PersistentSortedSet(SessionImplementor session, SortedSet set) { + this( (SharedSessionContractImplementor) session, set ); + } + @SuppressWarnings({"unchecked", "UnusedParameters"}) protected Serializable snapshot(BasicCollectionPersister persister, EntityMode entityMode) throws HibernateException { diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java index a087beab6f85f472d3f31742015fc79c2b363cc8..24032cee229b11e56df5fb274187af1f41819aab 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java @@ -83,7 +83,7 @@ public interface PersistentCollection { * database state is now synchronized with the memory state. */ void postAction(); - + /** * Return the user-visible collection (or array) instance * @@ -102,7 +102,7 @@ public interface PersistentCollection { * @return Whether to end the read. */ boolean endRead(); - + /** * Called after initializing from cache * @@ -184,7 +184,7 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return The identifier value */ Object getIdentifier(Object entry, int i); - + /** * Get the index of the given collection entry * @@ -195,7 +195,7 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return The index value */ Object getIndex(Object entry, int i, CollectionPersister persister); - + /** * Get the value of the given collection entry. Generally the given entry parameter value will just be returned. * Might get a different value for a duplicate entries in a Set. @@ -205,7 +205,7 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return The corresponding object that is part of the collection elements. */ Object getElement(Object entry); - + /** * Get the snapshot value of the given collection entry * @@ -243,7 +243,7 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return {@code true} if the given snapshot is empty */ boolean isSnapshotEmpty(Serializable snapshot); - + /** * Disassemble the collection to get it ready for the cache * @@ -355,7 +355,7 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return The iterator */ Iterator queuedAdditionIterator(); - + /** * Get the "queued" orphans * @@ -364,28 +364,28 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri * @return The orphaned elements */ Collection getQueuedOrphans(String entityName); - + /** * Get the current collection key value * * @return the current collection key value */ Serializable getKey(); - + /** * Get the current role name * * @return the collection role name */ String getRole(); - + /** * Is the collection unreferenced? * * @return {@code true} if the collection is no longer referenced by an owner */ boolean isUnreferenced(); - + /** * Is the collection dirty? Note that this is only * reliable during the flush cycle, after the @@ -400,24 +400,39 @@ default boolean isElementRemoved(){ return false; } + /** + * Was {@code collection} provided directly to this PersistentCollection + * (i.e., provided as an argument to a constructor)? + *

    + * Implementors that can copy elements out of a directly provided + * collection into the wrapped collection should override this method. + *

    + * @param collection The collection + * @return true, if {@code collection} was provided directly to this + * PersistentCollection; false, otherwise. + */ + default boolean isDirectlyProvidedCollection(Object collection) { + return isDirectlyAccessible() && isWrapper( collection ); + } + /** * Clear the dirty flag, after flushing changes * to the database. */ void clearDirty(); - + /** * Get the snapshot cached by the collection instance * * @return The internally stored snapshot state */ Serializable getStoredSnapshot(); - + /** * Mark the collection as dirty */ void dirty(); - + /** * Called before inserting rows, to ensure that any surrogate keys * are fully generated @@ -444,5 +459,5 @@ default boolean isElementRemoved(){ * @return The orphans */ Collection getOrphans(Serializable snapshot, String entityName); - + } diff --git a/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java b/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java index 85cc4334dce49ca9ded2aec02d63c6310a374c63..b87cab0e14a3f4f372c883fbbf570f37d7f3267d 100644 --- a/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/context/internal/ThreadLocalSessionContext.java @@ -342,7 +342,8 @@ else if ( "reconnect".equals( methodName ) || "disconnect".equals( methodName ) LOG.tracef( "Allowing invocation [%s] to proceed to real (non-transacted) session - deprecated methods", methodName ); } else { - throw new HibernateException( methodName + " is not valid without active transaction" ); + throw new HibernateException( "Calling method '" + methodName + "' is not valid without an active transaction (Current status: " + + realSession.getTransaction().getStatus() + ")" ); } } LOG.tracef( "Allowing proxy invocation [%s] to proceed to real session", methodName ); diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/CriteriaQuery.java b/hibernate-core/src/main/java/org/hibernate/criterion/CriteriaQuery.java index 3dbebe2e9db48a5e1efcf5aaad9628b24e78f7aa..d5b672687419ea817e2b0ac5ee6f2939a8a18861 100755 --- a/hibernate-core/src/main/java/org/hibernate/criterion/CriteriaQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/CriteriaQuery.java @@ -8,6 +8,7 @@ import org.hibernate.Criteria; import org.hibernate.HibernateException; +import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.TypedValue; import org.hibernate.type.Type; @@ -200,4 +201,16 @@ public interface CriteriaQuery { * @return The generated alias */ public String generateSQLAlias(); + + default Type getForeignKeyType(Criteria criteria, String associationPropertyName){ + throw new NotYetImplementedException("CriteriaQuery#getForeignKeyType() has not been yet implemented!"); + } + + default String[] getForeignKeyColumns(Criteria criteria, String associationPropertyName){ + throw new NotYetImplementedException("CriteriaQuery#getForeignKeyColumns() has not been yet implemented!"); + } + + default TypedValue getForeignKeyTypeValue(Criteria criteria, String associationPropertyName, Object value){ + throw new NotYetImplementedException("CriteriaQuery#getForeignKeyTypeValue() has not been yet implemented!"); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/ForeignKeyExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/ForeignKeyExpression.java new file mode 100644 index 0000000000000000000000000000000000000000..8fc343e69d661a248b268b447263b2ba9fc63328 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/criterion/ForeignKeyExpression.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.criterion; + +import org.hibernate.Criteria; +import org.hibernate.engine.spi.TypedValue; +import org.hibernate.internal.util.StringHelper; + +public class ForeignKeyExpression implements Criterion { + private final String associationPropertyName; + private final Object value; + private final String operator; + + public ForeignKeyExpression(String associationPropertyName, Object value, String operator) { + this.associationPropertyName = associationPropertyName; + this.value = value; + this.operator = operator; + } + + @Override + public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) { + final String[] columns = criteriaQuery.getForeignKeyColumns( criteria, associationPropertyName ); + + String result = String.join( " and ", StringHelper.suffix( columns, operator + " ?" ) ); + if ( columns.length > 1 ) { + result = '(' + result + ')'; + } + return result; + } + + @Override + public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) { + return new TypedValue[] { criteriaQuery.getForeignKeyTypeValue( criteria, associationPropertyName, value ) }; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/ForeignKeyNullExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/ForeignKeyNullExpression.java new file mode 100644 index 0000000000000000000000000000000000000000..fa81e944f369cb445273e8a2e90083c338f6d7d5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/criterion/ForeignKeyNullExpression.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.criterion; + +import org.hibernate.Criteria; +import org.hibernate.engine.spi.TypedValue; +import org.hibernate.internal.util.StringHelper; + +public class ForeignKeyNullExpression implements Criterion { + private static final TypedValue[] NO_VALUES = new TypedValue[0]; + + private final String associationPropertyName; + private final boolean negated; + + public ForeignKeyNullExpression(String associationPropertyName) { + this.associationPropertyName = associationPropertyName; + this.negated = false; + } + + public ForeignKeyNullExpression(String associationPropertyName, boolean negated) { + this.associationPropertyName = associationPropertyName; + this.negated = negated; + } + + @Override + public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) { + final String[] columns = criteriaQuery.getForeignKeyColumns( criteria, associationPropertyName ); + + String result = String.join( " and ", StringHelper.suffix( columns, getSuffix() ) ); + if ( columns.length > 1 ) { + result = '(' + result + ')'; + } + return result; + } + + private String getSuffix() { + if ( negated ) { + return " is not null"; + } + return " is null"; + } + + @Override + public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) { + return NO_VALUES; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/ForeingKeyProjection.java b/hibernate-core/src/main/java/org/hibernate/criterion/ForeingKeyProjection.java new file mode 100644 index 0000000000000000000000000000000000000000..6df5fb1cc7496909a12338d89f7164eac1d601a4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/criterion/ForeingKeyProjection.java @@ -0,0 +1,55 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.criterion; + +import org.hibernate.Criteria; +import org.hibernate.type.Type; + +public class ForeingKeyProjection extends SimpleProjection { + private String associationPropertyName; + + protected ForeingKeyProjection(String associationPropertyName) { + this.associationPropertyName = associationPropertyName; + } + + @Override + public Type[] getTypes(Criteria criteria, CriteriaQuery criteriaQuery) { + return new Type[] { criteriaQuery.getForeignKeyType( criteria, associationPropertyName ) }; + } + + @Override + public String toSqlString(Criteria criteria, int position, CriteriaQuery criteriaQuery) { + final StringBuilder buf = new StringBuilder(); + final String[] cols = criteriaQuery.getForeignKeyColumns( criteria, associationPropertyName ); + for ( int i = 0; i < cols.length; i++ ) { + buf.append( cols[i] ) + .append( " as y" ) + .append( position + i ) + .append( '_' ); + if ( i < cols.length - 1 ) { + buf.append( ", " ); + } + } + return buf.toString(); + } + + @Override + public boolean isGrouped() { + return false; + } + + @Override + public String toGroupSqlString(Criteria criteria, CriteriaQuery criteriaQuery) { + return super.toGroupSqlString( criteria, criteriaQuery ); + } + + @Override + public String toString() { + return "fk"; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/Projections.java b/hibernate-core/src/main/java/org/hibernate/criterion/Projections.java index e2826eb7930d0263d7d268ce30cca6a8b67fad1c..dbb310622d75fd3e8e0b7cc340df9c02fd392387 100755 --- a/hibernate-core/src/main/java/org/hibernate/criterion/Projections.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/Projections.java @@ -61,6 +61,16 @@ public static IdentifierProjection id() { return new IdentifierProjection(); } + /* + * An foreign key value projection. + * + * @return The foreign key projection + * + */ + public static ForeingKeyProjection fk(String associationPropertyName) { + return new ForeingKeyProjection(associationPropertyName); + } + /** * Create a distinct projection from a projection. * diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/Restrictions.java b/hibernate-core/src/main/java/org/hibernate/criterion/Restrictions.java index 264f7f8a9d3afd73a19a79d0c64721c7745b3189..891cb1a16729c7d092e26a1218cdb3de92270318 100755 --- a/hibernate-core/src/main/java/org/hibernate/criterion/Restrictions.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/Restrictions.java @@ -36,6 +36,22 @@ public class Restrictions { public static Criterion idEq(Object value) { return new IdentifierEqExpression( value ); } + + public static Criterion fkEq(String associationPropertyName, Object value) { + return new ForeignKeyExpression( associationPropertyName, value, "=" ); + } + + public static Criterion fkNe(String associationPropertyName, Object value) { + return new ForeignKeyExpression( associationPropertyName, value, "<>" ); + } + + public static Criterion fkIsNotNull(String associationPropertyName) { + return new ForeignKeyNullExpression( associationPropertyName, true); + } + + public static Criterion fkIsNull(String associationPropertyName) { + return new ForeignKeyNullExpression( associationPropertyName ); + } /** * Apply an "equal" constraint to the named property * diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java index 4b2876ec80309c757032fc079166d9afab438982..26733b6b68581797442926bed01cd2aa5c7483ea 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java @@ -102,11 +102,18 @@ import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor; /** - * An abstract base class for HANA dialects.
    - * SAP HANA Reference
    - * NOTE: This dialect is currently configured to create foreign keys with on update cascade. + * An abstract base class for SAP HANA dialects. + *

    + * For more information on interacting with the SAP HANA database, refer to the + * SAP HANA SQL and System Views Reference + * and the SAP + * HANA Client Interface Programming Reference. + *

    + * Note: This dialect is configured to create foreign keys with {@code on update cascade}. * - * @author Andrew Clemons + * @author Andrew Clemons + * @author Jonathan Bregler */ public abstract class AbstractHANADialect extends Dialect { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Cache71Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Cache71Dialect.java index 652b7bb6ca641ed64de8ef65ee75cc71777e7779..8f1dcc4c7887d9eb8bf608b71645cb70957eaef9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Cache71Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Cache71Dialect.java @@ -49,7 +49,7 @@ /** * Caché 2007.1 dialect. * - * This class is required in order to use Hibernate with Intersystems Caché SQL. Compatible with + * This class is required in order to use Hibernate with InterSystems Caché SQL. Compatible with * Caché 2007.1. * *

    PREREQUISITES

    @@ -102,7 +102,7 @@ * For example, in Hibernate 3.2, typical entries in hibernate.properties would have the following * "name=value" pairs: *

    - * + *
    * * * diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java index 314f1eba2d54f264904e7daf822fa483738376de..e7841d24177633324f8309e26f582e67409b8daf 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java @@ -6,14 +6,17 @@ */ package org.hibernate.dialect; +import java.sql.Types; + import org.hibernate.dialect.function.DB2SubstringFunction; -import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernate.hql.spi.id.IdTableSupportStandardImpl; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; import org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy; import org.hibernate.hql.spi.id.local.AfterUseAction; -import org.hibernate.hql.spi.id.local.LocalTemporaryTableBulkIdStrategy; -import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.sql.CharTypeDescriptor; +import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor; /** * An SQL dialect for DB2 9.7. @@ -57,4 +60,30 @@ public String getCreateIdTableStatementOptions() { AfterUseAction.CLEAN ); } + + @Override + protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { + // See HHH-12753 + // It seems that DB2's JDBC 4.0 support as of 9.5 does not support the N-variant methods like + // NClob or NString. Therefore here we overwrite the sql type descriptors to use the non-N variants + // which are supported. + switch ( sqlCode ) { + case Types.NCHAR: + return CharTypeDescriptor.INSTANCE; + + case Types.NCLOB: + if ( useInputStreamToInsertBlob() ) { + return ClobTypeDescriptor.STREAM_BINDING; + } + else { + return ClobTypeDescriptor.CLOB_BINDING; + } + + case Types.NVARCHAR: + return VarcharTypeDescriptor.INSTANCE; + + default: + return super.getSqlTypeDescriptorOverride( sqlCode ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java index 7e3ea78b576d4929afe7aa1b69bc2252e25b1bda..12b95f4e4d646e5ab44fbf36651340f3c3032af7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -36,10 +36,9 @@ import org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy; import org.hibernate.hql.spi.id.local.AfterUseAction; import org.hibernate.hql.spi.id.local.LocalTemporaryTableBulkIdStrategy; -import org.hibernate.internal.CoreLogging; -import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.sql.DecimalTypeDescriptor; import org.hibernate.type.descriptor.sql.SmallIntTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; @@ -49,7 +48,6 @@ * @author Gavin King */ public class DB2Dialect extends Dialect { - private static final CoreMessageLogger log = CoreLogging.messageLogger( DB2Dialect.class ); private static final AbstractLimitHandler LIMIT_HANDLER = new AbstractLimitHandler() { @Override @@ -100,7 +98,12 @@ public DB2Dialect() { registerColumnType( Types.TIME, "time" ); registerColumnType( Types.TIMESTAMP, "timestamp" ); registerColumnType( Types.VARBINARY, "varchar($l) for bit data" ); - registerColumnType( Types.NUMERIC, "numeric($p,$s)" ); + // DB2 converts numeric to decimal under the hood + // Note that the type returned by DB2 for a numeric column will be Types.DECIMAL. Thus, we have an issue when + // comparing the types during the schema validation, defining the type to decimal here as the type names will + // also be compared and there will be a match. See HHH-12827 for the details. + registerColumnType( Types.NUMERIC, "decimal($p,$s)" ); + registerColumnType( Types.DECIMAL, "decimal($p,$s)" ); registerColumnType( Types.BLOB, "blob($l)" ); registerColumnType( Types.CLOB, "clob($l)" ); registerColumnType( Types.LONGVARCHAR, "long varchar" ); @@ -210,7 +213,7 @@ public DB2Dialect() { registerKeyword( "only" ); getDefaultProperties().setProperty( Environment.STATEMENT_BATCH_SIZE, NO_BATCH ); - + uniqueDelegate = new DB2UniqueDelegate( this ); } @@ -365,7 +368,7 @@ public int registerResultSetOutParameter(CallableStatement statement, int col) t @Override public ResultSet getResultSet(CallableStatement ps) throws SQLException { boolean isResultSet = ps.execute(); - // This assumes you will want to ignore any update counts + // This assumes you will want to ignore any update counts while ( !isResultSet && ps.getUpdateCount() != -1 ) { isResultSet = ps.getMoreResults(); } @@ -478,7 +481,14 @@ public boolean supportsTupleDistinctCounts() { @Override protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { - return sqlCode == Types.BOOLEAN ? SmallIntTypeDescriptor.INSTANCE : super.getSqlTypeDescriptorOverride( sqlCode ); + if ( sqlCode == Types.BOOLEAN ) { + return SmallIntTypeDescriptor.INSTANCE; + } + else if ( sqlCode == Types.NUMERIC ) { + return DecimalTypeDescriptor.INSTANCE; + } + + return super.getSqlTypeDescriptorOverride( sqlCode ); } @Override @@ -496,12 +506,12 @@ public JDBCException convert(SQLException sqlException, String message, String s } }; } - + @Override public UniqueDelegate getUniqueDelegate() { return uniqueDelegate; } - + @Override public String getNotExpression( String expression ) { return "not (" + expression + ")"; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Database.java b/hibernate-core/src/main/java/org/hibernate/dialect/Database.java index f9db3a2d903e92f56a17c5655eeb3fe8b8644ad5..ace32856e7366bfccb51a1366546baae09614f59 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Database.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Database.java @@ -324,7 +324,7 @@ public Dialect resolveDialect(DialectResolutionInfo info) { MYSQL { @Override public Class latestDialect() { - return MySQL57Dialect.class; + return MySQL8Dialect.class; } @Override @@ -349,6 +349,14 @@ else if ( minorVersion < 7 ) { return new MySQL57Dialect(); } } + else if ( majorVersion < 8) { + // There is no MySQL 6 or 7. + // Adding this just in case. + return new MySQL57Dialect(); + } + else if ( majorVersion == 8 ) { + return new MySQL8Dialect(); + } return latestDialectInstance( this ); } @@ -381,7 +389,7 @@ public Dialect resolveDialect(DialectResolutionInfo info) { case 8: return new Oracle8iDialect(); default: - latestDialectInstance( this ); + return latestDialectInstance( this ); } } 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 66c1a9c3ade25bfbda2d4b9c227cf189cc0e09fa..49dbb18543d188f2c93e6378ad4beef13244fd04 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.regex.Pattern; import org.hibernate.HibernateException; import org.hibernate.LockMode; @@ -140,6 +141,9 @@ public abstract class Dialect implements ConversionContext { */ public static final String CLOSED_QUOTE = "`\"]"; + private static final Pattern ESCAPE_CLOSING_COMMENT_PATTERN = Pattern.compile( "\\*/" ); + private static final Pattern ESCAPE_OPENING_COMMENT_PATTERN = Pattern.compile( "/\\*" ); + private final TypeNames typeNames = new TypeNames(); private final TypeNames hibernateTypeNames = new TypeNames(); @@ -332,7 +336,7 @@ public String getTypeName(int code) throws HibernateException { /** * Get the name of the database type associated with the given - * {@link java.sql.Types} typecode with the given storage specification + * {@link Types} typecode with the given storage specification * parameters. * * @param code The {@link java.sql.Types} typecode @@ -1651,7 +1655,7 @@ public String getCurrentTimestampSQLFunctionName() { * @return The Dialect's preferred SQLExceptionConverter, or null to * indicate that the default {@link SQLExceptionConverter} should be used. * - * @see {@link #buildSQLExceptionConversionDelegate()} + * @see #buildSQLExceptionConversionDelegate() * @deprecated {@link #buildSQLExceptionConversionDelegate()} should be * overridden instead. */ @@ -2506,7 +2510,7 @@ public boolean supportsCircularCascadeDeleteConstraints() { * Are subselects supported as the left-hand-side (LHS) of * IN-predicates. *

    - * In other words, is syntax like "... IN (1, 2, 3) ..." supported? + * In other words, is syntax like {@code ... IN (1, 2, 3) ...} supported? * * @return True if subselects can appear as the LHS of an in-predicate; * false otherwise. @@ -3002,6 +3006,14 @@ public String addSqlHintOrComment( } protected String prependComment(String sql, String comment) { - return "/* " + comment + " */ " + sql; + return "/* " + escapeComment( comment ) + " */ " + sql; + } + + public static String escapeComment(String comment) { + if ( StringHelper.isNotEmpty( comment ) ) { + final String escaped = ESCAPE_CLOSING_COMMENT_PATTERN.matcher( comment ).replaceAll( "*\\\\/" ); + return ESCAPE_OPENING_COMMENT_PATTERN.matcher( escaped ).replaceAll( "/\\\\*" ); + } + return comment; } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java index c916289b4daa833452fa0d989fabbf65c5b712c3..f9e73a582db85e892310b623e68c6859f2d775a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java @@ -12,11 +12,18 @@ import org.hibernate.hql.spi.id.local.AfterUseAction; /** - * An SQL dialect for HANA.
    - * SAP HANA Reference
    + * An SQL dialect for the SAP HANA column store. + *

    + * For more information on interacting with the SAP HANA database, refer to the + * SAP HANA SQL and System Views Reference + * and the SAP + * HANA Client Interface Programming Reference. + *

    * Column tables are created by this dialect when using the auto-ddl feature. * - * @author Andrew Clemons + * @author Andrew Clemons + * @author Jonathan Bregler */ public class HANAColumnStoreDialect extends AbstractHANADialect { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java index 009c19401d2fc255956b67d8e1485ce03896e2cb..79d4e3f3e79cdc531a9612654b33f4ef74c49b3f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java @@ -12,11 +12,18 @@ import org.hibernate.hql.spi.id.local.AfterUseAction; /** - * An SQL dialect for HANA.
    - * SAP HANA Reference
    + * An SQL dialect for the SAP HANA row store. + *

    + * For more information on interacting with the SAP HANA database, refer to the + * SAP HANA SQL and System Views Reference + * and the SAP + * HANA Client Interface Programming Reference. + *

    * Row tables are created by this dialect when using the auto-ddl feature. * - * @author Andrew Clemons + * @author Andrew Clemons + * @author Jonathan Bregler */ public class HANARowStoreDialect extends AbstractHANADialect { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLDialect.java index 706cc778c73b90820258e873f2cc6ab89f5f443d..ccbed0996d69873d04b5a4c53fbd38cc6707dec7 100755 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLDialect.java @@ -18,7 +18,7 @@ * An Hibernate 3 SQL dialect for Mimer SQL. This dialect requires Mimer SQL 9.2.1 or later * because of the mappings to NCLOB, BINARY, and BINARY VARYING. * - * @author Fredrik lund + * @author Fredrik lund */ @SuppressWarnings("deprecation") public class MimerSQLDialect extends Dialect { @@ -27,8 +27,8 @@ public class MimerSQLDialect extends Dialect { private static final int BINARY_MAX_LENGTH = 2000; /** - * Even thoug Mimer SQL supports character and binary columns up to 15 000 in lenght, - * this is also the maximum width of the table (exluding LOBs). To avoid breaking the limit all the + * Even though Mimer SQL supports character and binary columns up to 15 000 in length, + * this is also the maximum width of the table (excluding LOBs). To avoid breaking the limit all the * time we limit the length of the character columns to CHAR_MAX_LENTH, NATIONAL_CHAR_LENGTH for national * characters, and BINARY_MAX_LENGTH for binary types. */ diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java index b7ee0fdf56e26f0452d23cfb2ca7b585079143b0..0af7f0fe38bc84091ea662e3d05261ab65b93447 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -527,7 +527,8 @@ public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() { @Override public JDBCException convert(SQLException sqlException, String message, String sql) { switch ( sqlException.getErrorCode() ) { - case 1205: { + case 1205: + case 3572: { return new PessimisticLockException( message, sqlException, sql ); } case 1207: diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle12cDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle12cDialect.java index c031ae765e3180940fa373f913e14be8a4bd4de1..e184c65e0c90f20a80317b5a67d85bbb629c5fbe 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle12cDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle12cDialect.java @@ -10,8 +10,9 @@ import org.hibernate.cfg.Environment; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.Oracle12cIdentityColumnSupport; +import org.hibernate.dialect.pagination.AbstractLimitHandler; import org.hibernate.dialect.pagination.LimitHandler; -import org.hibernate.dialect.pagination.SQL2008StandardLimitHandler; +import org.hibernate.dialect.pagination.Oracle12LimitHandler; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.service.ServiceRegistry; @@ -26,6 +27,8 @@ public class Oracle12cDialect extends Oracle10gDialect { public static final String PREFER_LONG_RAW = "hibernate.dialect.oracle.prefer_long_raw"; + private static final AbstractLimitHandler LIMIT_HANDLER = Oracle12LimitHandler.INSTANCE; + public Oracle12cDialect() { super(); getDefaultProperties().setProperty( Environment.BATCH_VERSIONED_DATA, "true" ); @@ -55,11 +58,6 @@ protected void registerDefaultProperties() { getDefaultProperties().setProperty( Environment.USE_GET_GENERATED_KEYS, "true" ); } - @Override - public LimitHandler getLimitHandler() { - return SQL2008StandardLimitHandler.INSTANCE; - } - @Override public String getNativeIdentifierGeneratorStrategy() { return "sequence"; @@ -69,4 +67,9 @@ public String getNativeIdentifierGeneratorStrategy() { public IdentityColumnSupport getIdentityColumnSupport() { return new Oracle12cIdentityColumnSupport(); } + + @Override + public LimitHandler getLimitHandler() { + return LIMIT_HANDLER; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE157Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE157Dialect.java index 5b8d17073ec939364b403d9199296f7c7100dc31..01155cd994045a5bb494d4299dc9d965f6279791 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE157Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE157Dialect.java @@ -12,6 +12,8 @@ import org.hibernate.JDBCException; import org.hibernate.LockOptions; import org.hibernate.dialect.function.SQLFunctionTemplate; +import org.hibernate.dialect.pagination.LimitHandler; +import org.hibernate.dialect.pagination.SybaseASE157LimitHandler; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.LockTimeoutException; import org.hibernate.exception.spi.SQLExceptionConversionDelegate; @@ -27,6 +29,8 @@ */ public class SybaseASE157Dialect extends SybaseASE15Dialect { + private static final SybaseASE157LimitHandler LIMIT_HANDLER = new SybaseASE157LimitHandler(); + /** * Constructs a SybaseASE157Dialect */ @@ -102,4 +106,19 @@ public JDBCException convert(SQLException sqlException, String message, String s } }; } + + @Override + public boolean supportsLimit() { + return true; + } + + @Override + public boolean supportsLimitOffset() { + return false; + } + + @Override + public LimitHandler getLimitHandler() { + return LIMIT_HANDLER; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE15Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE15Dialect.java index 4d75e1bdc3af738e3bc87ca5a80f5a86f06fbc98..0027263e19e3388f81d89cd46a4c11e77e101c22 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE15Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE15Dialect.java @@ -431,4 +431,9 @@ protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { public boolean supportsLockTimeouts() { return false; } + + @Override + public boolean supportsPartitionBy() { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java index 19020e932900b5c8e9c53e246e938bc3038e38e3..b20284657c69a082b2d9473048b5f71397bfcca1 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java @@ -43,4 +43,9 @@ protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { public String getNullColumnString() { return " null"; } + + @Override + public String getCurrentSchemaCommand() { + return "select db_name()"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/TeradataDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/TeradataDialect.java index e27281c2176cfac3bcfc70f798f1ddcbbd127e00..afd4c7bf5775ad7f7da62c4a9639cb31cc15e3ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/TeradataDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/TeradataDialect.java @@ -65,7 +65,7 @@ public TeradataDialect() { // bit_length feels a bit broken to me. We have to cast to char in order to // pass when a numeric value is supplied. But of course the answers given will - // be wildly different for these two datatypes. 1234.5678 will be 9 bytes as + // be wildly different for these two data types. 1234.5678 will be 9 bytes as // a char string but will be 8 or 16 bytes as a true numeric. // Jay Nance 2006-09-22 registerFunction( @@ -103,7 +103,7 @@ public TeradataDialect() { /** * Does this dialect support the FOR UPDATE syntax? * - * @return empty string ... Teradata does not support FOR UPDATE syntax + * @return empty string ... Teradata does not support FOR UPDATE syntax */ @Override public String getForUpdateString() { 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 61e4c4f3ee1b3f9a0ee6373c57eec0b1627c517c..d957b4444659881a086ebdbbdd2303c032e87ecf 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 @@ -50,7 +50,7 @@ public void lock(Serializable id, Object version, Object object, int timeout, Sh } final EntityEntry entry = session.getPersistenceContext().getEntry( object ); // Register the EntityIncrementVersionProcess action to run just prior to transaction commit. - ( (EventSource) session ).getActionQueue().registerProcess( new EntityIncrementVersionProcess( object, entry ) ); + ( (EventSource) session ).getActionQueue().registerProcess( new EntityIncrementVersionProcess( object ) ); } protected LockMode getLockMode() { 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 262149c2aa3a5ba3abc8d464e8f509b2e2300fbc..dceea879260225b4afd3b65f398bfbe3b0660b69 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 @@ -12,7 +12,6 @@ import org.hibernate.LockMode; import org.hibernate.OptimisticLockException; import org.hibernate.action.internal.EntityVerifyVersionProcess; -import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; import org.hibernate.persister.entity.Lockable; @@ -49,9 +48,8 @@ public void lock(Serializable id, Object version, Object object, int timeout, Sh if ( !lockable.isVersioned() ) { throw new OptimisticLockException( object, "[" + lockMode + "] not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); } - final EntityEntry entry = session.getPersistenceContext().getEntry( object ); // Register the EntityVerifyVersionProcess action to run just prior to transaction commit. - ( (EventSource) session ).getActionQueue().registerProcess( new EntityVerifyVersionProcess( object, entry ) ); + ( (EventSource) session ).getActionQueue().registerProcess( new EntityVerifyVersionProcess( object ) ); } protected LockMode getLockMode() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java index 3013254bee5e0038b85a5c595962ef6b49a7b370..d0642b2277d1388ec524a7574f1712866614ade9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java @@ -9,6 +9,7 @@ import java.sql.PreparedStatement; import java.sql.SQLException; +import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.RowSelection; /** @@ -37,13 +38,25 @@ public interface LimitHandler { /** * Return processed SQL query. * - * @param sql the SQL query to process. + * @param sql the SQL query to process. * @param selection the selection criteria for rows. * * @return Query statement with LIMIT clause applied. */ String processSql(String sql, RowSelection selection); + /** + * Return processed SQL query. + * + * @param sql the SQL query to process. + * @param queryParameters the queryParameters. + * + * @return Query statement with LIMIT clause applied. + */ + default String processSql(String sql, QueryParameters queryParameters ){ + return processSql( sql, queryParameters.getRowSelection() ); + } + /** * Bind parameter values needed by the LIMIT clause before original SELECT statement. * diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/Oracle12LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/Oracle12LimitHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..c4d7f23f15db31a6354e9fbf508560898908dbb8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/Oracle12LimitHandler.java @@ -0,0 +1,176 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.dialect.pagination; + +import java.util.Locale; + +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.RowSelection; + +/** + * A {@link LimitHandler} for databases which support the + * ANSI SQL standard syntax {@code FETCH FIRST m ROWS ONLY} + * and {@code OFFSET n ROWS FETCH NEXT m ROWS ONLY}. + * + * @author Gavin King + */ +public class Oracle12LimitHandler extends AbstractLimitHandler { + + public boolean bindLimitParametersInReverseOrder; + public boolean useMaxForLimit; + + public static final Oracle12LimitHandler INSTANCE = new Oracle12LimitHandler(); + + Oracle12LimitHandler() { + } + + @Override + public String processSql(String sql, RowSelection selection) { + final boolean hasFirstRow = LimitHelper.hasFirstRow( selection ); + final boolean hasMaxRows = LimitHelper.hasMaxRows( selection ); + + if ( !hasMaxRows ) { + return sql; + } + + return processSql( sql, getForUpdateIndex( sql ), hasFirstRow ); + } + + @Override + public String processSql(String sql, QueryParameters queryParameters) { + final RowSelection selection = queryParameters.getRowSelection(); + + final boolean hasFirstRow = LimitHelper.hasFirstRow( selection ); + final boolean hasMaxRows = LimitHelper.hasMaxRows( selection ); + + if ( !hasMaxRows ) { + return sql; + } + sql = sql.trim(); + + final LockOptions lockOptions = queryParameters.getLockOptions(); + if ( lockOptions != null ) { + final LockMode lockMode = lockOptions.getLockMode(); + switch ( lockMode ) { + case UPGRADE: + case PESSIMISTIC_READ: + case PESSIMISTIC_WRITE: + case UPGRADE_NOWAIT: + case FORCE: + case PESSIMISTIC_FORCE_INCREMENT: + case UPGRADE_SKIPLOCKED: + return processSql( sql, selection ); + default: + return processSqlOffsetFetch( sql, hasFirstRow ); + } + } + return processSqlOffsetFetch( sql, hasFirstRow ); + } + + private String processSqlOffsetFetch(String sql, boolean hasFirstRow) { + + final int forUpdateLastIndex = getForUpdateIndex( sql ); + + if ( forUpdateLastIndex > -1 ) { + return processSql( sql, forUpdateLastIndex, hasFirstRow ); + } + + bindLimitParametersInReverseOrder = false; + useMaxForLimit = false; + + final int offsetFetchLength; + final String offsetFetchString; + if ( hasFirstRow ) { + offsetFetchString = " offset ? rows fetch next ? rows only"; + } + else { + offsetFetchString = " fetch first ? rows only"; + } + offsetFetchLength = sql.length() + offsetFetchString.length(); + + return new StringBuilder( offsetFetchLength ).append( sql ).append( offsetFetchString ).toString(); + } + + private String processSql(String sql, int forUpdateIndex, boolean hasFirstRow) { + bindLimitParametersInReverseOrder = true; + useMaxForLimit = true; + + String forUpdateClause = null; + boolean isForUpdate = false; + if ( forUpdateIndex > -1 ) { + // save 'for update ...' and then remove it + forUpdateClause = sql.substring( forUpdateIndex ); + sql = sql.substring( 0, forUpdateIndex - 1 ); + isForUpdate = true; + } + + final StringBuilder pagingSelect; + + final int forUpdateClauseLength; + if ( forUpdateClause == null ) { + forUpdateClauseLength = 0; + } + else { + forUpdateClauseLength = forUpdateClause.length() + 1; + } + + if ( hasFirstRow ) { + pagingSelect = new StringBuilder( sql.length() + forUpdateClauseLength + 98 ); + pagingSelect.append( "select * from ( select row_.*, rownum rownum_ from ( " ); + pagingSelect.append( sql ); + pagingSelect.append( " ) row_ where rownum <= ?) where rownum_ > ?" ); + } + else { + pagingSelect = new StringBuilder( sql.length() + forUpdateClauseLength + 37 ); + pagingSelect.append( "select * from ( " ); + pagingSelect.append( sql ); + pagingSelect.append( " ) where rownum <= ?" ); + } + + if ( isForUpdate ) { + pagingSelect.append( " " ); + pagingSelect.append( forUpdateClause ); + } + + return pagingSelect.toString(); + } + + private int getForUpdateIndex(String sql) { + final int forUpdateLastIndex = sql.toLowerCase( Locale.ROOT ).lastIndexOf( "for update" ); + // We need to recognize cases like : select a from t where b = 'for update'; + final int lastIndexOfQuote = sql.lastIndexOf( "'" ); + if ( forUpdateLastIndex > -1 ) { + if ( lastIndexOfQuote == -1 ) { + return forUpdateLastIndex; + } + if ( lastIndexOfQuote > forUpdateLastIndex ) { + return -1; + } + return forUpdateLastIndex; + } + return forUpdateLastIndex; + } + + @Override + public final boolean supportsLimit() { + return true; + } + + @Override + public boolean bindLimitParametersInReverseOrder() { + return bindLimitParametersInReverseOrder; + } + + @Override + public boolean useMaxForLimit() { + return useMaxForLimit; + } + + +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SybaseASE157LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SybaseASE157LimitHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..5137385a050511c126e3c6141204b9fb4af5b49e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SybaseASE157LimitHandler.java @@ -0,0 +1,83 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.dialect.pagination; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.hibernate.engine.spi.RowSelection; + +/** + * This limit handler is very conservative and is only triggered in simple cases involving a select or select distinct. + *

    + * Note that if the query already contains "top" just after the select or select distinct, we don't add anything to the + * query. It might just be a column name but, in any case, we just don't add the top clause and default to the previous + * behavior so it's not an issue. + */ +public class SybaseASE157LimitHandler extends AbstractLimitHandler { + + private static final Pattern SELECT_DISTINCT_PATTERN = Pattern.compile( "^(\\s*select\\s+distinct\\s+).*", + Pattern.CASE_INSENSITIVE ); + private static final Pattern SELECT_PATTERN = Pattern.compile( "^(\\s*select\\s+).*", Pattern.CASE_INSENSITIVE ); + private static final Pattern TOP_PATTERN = Pattern.compile( "^\\s*top\\s+.*", Pattern.CASE_INSENSITIVE ); + + @Override + public String processSql(String sql, RowSelection selection) { + if ( selection.getMaxRows() == null ) { + return sql; + } + + int top = getMaxOrLimit( selection ); + if ( top == Integer.MAX_VALUE ) { + return sql; + } + + Matcher selectDistinctMatcher = SELECT_DISTINCT_PATTERN.matcher( sql ); + if ( selectDistinctMatcher.matches() ) { + return insertTop( selectDistinctMatcher, sql, top ); + } + + Matcher selectMatcher = SELECT_PATTERN.matcher( sql ); + if ( selectMatcher.matches() ) { + return insertTop( selectMatcher, sql, top ); + } + + return sql; + } + + @Override + public boolean supportsLimit() { + return true; + } + + @Override + public boolean supportsLimitOffset() { + return false; + } + + @Override + public boolean useMaxForLimit() { + return true; + } + + @Override + public boolean supportsVariableLimit() { + return false; + } + + private static String insertTop(Matcher matcher, String sql, int top) { + int end = matcher.end( 1 ); + + if ( TOP_PATTERN.matcher( sql.substring( end ) ).matches() ) { + return sql; + } + + StringBuilder sb = new StringBuilder( sql ); + sb.insert( end, "top " + top + " " ); + return sb.toString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java index bb87bced99ba87fe1c45f1f0765e30c24896f46e..6fa6ccc632914b2e01faa5906983cdd7c2140ec1 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java @@ -16,12 +16,15 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.Session; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CachedNaturalIdValueSource; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntryExtraState; import org.hibernate.engine.spi.EntityKey; 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.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -34,9 +37,9 @@ * A base implementation of EntityEntry * * @author Gavin King - * @author Emmanuel Bernard + * @author Emmanuel Bernard * @author Gunnar Morling - * @author Sanne Grinovero + * @author Sanne Grinovero */ public abstract class AbstractEntityEntry implements Serializable, EntityEntry { protected final Serializable id; @@ -345,6 +348,15 @@ private boolean isUnequivocallyNonDirty(Object entity) { return ! persister.hasCollections() && ! ( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes(); } + if ( entity instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) entity; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + // we never have to check an uninitialized proxy + return true; + } + } + final CustomEntityDirtinessStrategy customEntityDirtinessStrategy = getPersistenceContext().getSession().getFactory().getCustomEntityDirtinessStrategy(); if ( customEntityDirtinessStrategy.canDirtyCheck( entity, getPersister(), (Session) getPersistenceContext().getSession() ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java index 5d04bbaaa909b3d2f6e11d91f8304ac69c4821a9..225bef5735f4df0fc76b830620021e6938085108 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java @@ -58,7 +58,6 @@ public static void removeNotFoundBatchLoadableEntityKeys( // All results should be in the PersistenceContext idSet.remove( session.getContextEntityIdentifier( result ) ); } - assert idSet.size() == ids.length - results.size(); if ( LOG.isDebugEnabled() ) { LOG.debug( "Entities of type [" + persister.getEntityName() + "] not found; IDs: " + idSet ); } 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 08a42c420d3f82a2476b37549c5ae79372916acc..7d9d97cecaa49d37cb21a16076dfe3b592fafa52 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 @@ -12,12 +12,10 @@ import java.util.Iterator; import org.hibernate.HibernateException; -import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadingAction; -import org.hibernate.engine.spi.CascadingActions; import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.Status; @@ -96,31 +94,52 @@ public static void cascade( final String propertyName = propertyNames[ i ]; final boolean isUninitializedProperty = hasUninitializedLazyProperties && - !persister.getInstrumentationMetadata().isAttributeLoaded( parent, propertyName ); + !persister.getBytecodeEnhancementMetadata().isAttributeLoaded( parent, propertyName ); if ( style.doCascade( action ) ) { final Object child; if ( isUninitializedProperty ) { // parent is a bytecode enhanced entity. - // cascading to an uninitialized, lazy value. + // Cascade to an uninitialized, lazy value only if + // parent is managed in the PersistenceContext. + // If parent is a detached entity being merged, + // then parent will not be in the PersistencContext + // (so lazy attributes must not be initialized). + if ( eventSource.getPersistenceContext().getEntry( parent ) == null ) { + // parent was not in the PersistenceContext + continue; + } if ( types[ i ].isCollectionType() ) { - // The collection does not need to be loaded from the DB. - // CollectionType#resolve will return an uninitialized PersistentCollection. - // The action will initialize the collection later, if necessary. - child = types[ i ].resolve( LazyPropertyInitializer.UNFETCHED_PROPERTY, eventSource, parent ); - // TODO: it would be nice to be able to set the attribute in parent using - // persister.setPropertyValue( parent, i, child ). - // Unfortunately, that would cause the uninitialized collection to be - // loaded from the DB. + // CollectionType#getCollection gets the PersistentCollection + // that corresponds to the uninitialized collection from the + // PersistenceContext. If not present, an uninitialized + // PersistentCollection will be added to the PersistenceContext. + // The action may initialize it later, if necessary. + // This needs to be done even when action.performOnLazyProperty() returns false. + final CollectionType collectionType = (CollectionType) types[i]; + child = collectionType.getCollection( + collectionType.getKeyOfOwner( parent, eventSource ), + eventSource, + parent, + null + ); } - else if ( action.performOnLazyProperty() ) { - // The (non-collection) attribute needs to be initialized so that - // the action can be performed on the initialized attribute. - LazyAttributeLoadingInterceptor interceptor = persister.getInstrumentationMetadata().extractInterceptor( parent ); + else if ( types[ i ].isComponentType() ) { + // Hibernate does not support lazy embeddables, so this shouldn't happen. + throw new UnsupportedOperationException( + "Lazy components are not supported." + ); + } + else if ( action.performOnLazyProperty() && types[ i ].isEntityType() ) { + // Only need to initialize a lazy entity attribute when action.performOnLazyProperty() + // returns true. + LazyAttributeLoadingInterceptor interceptor = persister.getBytecodeEnhancementMetadata() + .extractInterceptor( parent ); child = interceptor.fetchAttribute( parent, propertyName ); + } else { - // Nothing to do, so just skip cascading to this lazy (non-collection) attribute. + // Nothing to do, so just skip cascading to this lazy attribute. continue; } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java index 6fad916ebc1c20081d6fabe1d06371537233b6aa..f4b4a61a961f01b276c7da78960ff7926372664b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java @@ -10,6 +10,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; +import org.hibernate.action.internal.DelayedPostInsertIdentifier; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EntityEntry; @@ -163,66 +164,70 @@ public static void processReachableCollection( //TODO: better to pass the id in as an argument? ce.setCurrentKey( type.getKeyOfOwner( entity, session ) ); - final boolean isBytecodeEnhanced = persister.getOwnerEntityPersister().getInstrumentationMetadata().isEnhancedForLazyLoading(); + final boolean isBytecodeEnhanced = persister.getOwnerEntityPersister().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); if ( isBytecodeEnhanced && !collection.wasInitialized() ) { - // skip it - LOG.debugf( - "Skipping uninitialized bytecode-lazy collection: %s", - MessageHelper.collectionInfoString( persister, collection, ce.getCurrentKey(), session ) - ); + // the class of the collection owner is enhanced for lazy loading and we found an un-initialized PersistentCollection + // - skip it + if ( LOG.isDebugEnabled() ) { + LOG.debugf( + "Skipping uninitialized bytecode-lazy collection: %s", + MessageHelper.collectionInfoString(persister, collection, ce.getCurrentKey(), session) + ); + } ce.setReached( true ); ce.setProcessed( true ); + return; } - else { - // The CollectionEntry.isReached() stuff is just to detect any silly users - // who set up circular or shared references between/to collections. - if ( ce.isReached() ) { - // We've been here before - throw new HibernateException( - "Found shared references to a collection: " + type.getRole() + + // The CollectionEntry.isReached() stuff is just to detect any silly users + // who set up circular or shared references between/to collections. + if ( ce.isReached() ) { + // We've been here before + throw new HibernateException( + "Found shared references to a collection: " + type.getRole() + ); + } + + ce.setReached( true ); + + if ( LOG.isDebugEnabled() ) { + if ( collection.wasInitialized() ) { + LOG.debugf( + "Collection found: %s, was: %s (initialized)", + MessageHelper.collectionInfoString( + persister, + collection, + ce.getCurrentKey(), + session + ), + MessageHelper.collectionInfoString( + ce.getLoadedPersister(), + collection, + ce.getLoadedKey(), + session + ) ); } - ce.setReached( true ); - - if ( LOG.isDebugEnabled() ) { - if ( collection.wasInitialized() ) { - LOG.debugf( - "Collection found: %s, was: %s (initialized)", - MessageHelper.collectionInfoString( - persister, - collection, - ce.getCurrentKey(), - session - ), - MessageHelper.collectionInfoString( - ce.getLoadedPersister(), - collection, - ce.getLoadedKey(), - session - ) - ); - } - else { - LOG.debugf( - "Collection found: %s, was: %s (uninitialized)", - MessageHelper.collectionInfoString( - persister, - collection, - ce.getCurrentKey(), - session - ), - MessageHelper.collectionInfoString( - ce.getLoadedPersister(), - collection, - ce.getLoadedKey(), - session - ) - ); - } + else { + LOG.debugf( + "Collection found: %s, was: %s (uninitialized)", + MessageHelper.collectionInfoString( + persister, + collection, + ce.getCurrentKey(), + session + ), + MessageHelper.collectionInfoString( + ce.getLoadedPersister(), + collection, + ce.getLoadedKey(), + session + ) + ); } - - prepareCollectionForUpdate( collection, ce, factory ); } + + prepareCollectionForUpdate( collection, ce, factory ); } /** @@ -245,9 +250,15 @@ private static void prepareCollectionForUpdate( if ( loadedPersister != null || currentPersister != null ) { // it is or was referenced _somewhere_ + // check if the key changed + // excludes marking key changed when the loaded key is a DelayedPostInsertIdentifier. + final boolean keyChanged = currentPersister != null + && entry != null + && !currentPersister.getKeyType().isEqual( entry.getLoadedKey(), entry.getCurrentKey(), factory ) + && !( entry.getLoadedKey() instanceof DelayedPostInsertIdentifier ); + // if either its role changed, or its key changed - final boolean ownerChanged = loadedPersister != currentPersister - || !currentPersister.getKeyType().isEqual( entry.getLoadedKey(), entry.getCurrentKey(), factory ); + final boolean ownerChanged = loadedPersister != currentPersister || keyChanged; if ( ownerChanged ) { // do a check 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 86ec60d5b1f92b375f6e3d23593c2bf1b78c87c1..c2273ae57138376490644e63afb3857b408d6e01 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 @@ -11,7 +11,7 @@ /** * Contains optional state from {@link org.hibernate.engine.spi.EntityEntry}. * - * @author Emmanuel Bernard + * @author Emmanuel Bernard */ public class EntityEntryExtraStateHolder implements EntityEntryExtraState { private EntityEntryExtraState next; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java index e98f9e7c7f0c3925094c2cc58a9ddf16b5b071ab..879533baffb4bc0af7674728af41e606011721bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java @@ -13,7 +13,9 @@ import org.hibernate.TransientObjectException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.StringHelper; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; @@ -36,6 +38,7 @@ public static class Nullifier { private final boolean isEarlyInsert; private final SharedSessionContractImplementor session; private final Object self; + private final EntityPersister persister; /** * Constructs a Nullifier @@ -44,11 +47,18 @@ public static class Nullifier { * @param isDelete Are we in the middle of a delete action? * @param isEarlyInsert Is this an early insert (INSERT generated id strategy)? * @param session The session + * @param persister The EntityPersister for {@code self} */ - public Nullifier(Object self, boolean isDelete, boolean isEarlyInsert, SharedSessionContractImplementor session) { + public Nullifier( + final Object self, + final boolean isDelete, + final boolean isEarlyInsert, + final SharedSessionContractImplementor session, + final EntityPersister persister) { this.isDelete = isDelete; this.isEarlyInsert = isEarlyInsert; this.session = session; + this.persister = persister; this.self = self; } @@ -57,11 +67,12 @@ public Nullifier(Object self, boolean isDelete, boolean isEarlyInsert, SharedSes * points toward that entity. * * @param values The entity attribute values - * @param types The entity attribute types */ - public void nullifyTransientReferences(final Object[] values, final Type[] types) { + public void nullifyTransientReferences(final Object[] values) { + final String[] propertyNames = persister.getPropertyNames(); + final Type[] types = persister.getPropertyTypes(); for ( int i = 0; i < types.length; i++ ) { - values[i] = nullifyTransientReferences( values[i], types[i] ); + values[i] = nullifyTransientReferences( values[i], propertyNames[i], types[i] ); } } @@ -70,34 +81,53 @@ public void nullifyTransientReferences(final Object[] values, final Type[] types * input argument otherwise. This is how Hibernate avoids foreign key constraint violations. * * @param value An entity attribute value + * @param propertyName An entity attribute name * @param type An entity attribute type * * @return {@code null} if the argument is an unsaved entity; otherwise return the argument. */ - private Object nullifyTransientReferences(final Object value, final Type type) { + private Object nullifyTransientReferences(final Object value, final String propertyName, final Type type) { + final Object returnedValue; if ( value == null ) { - return null; + returnedValue = null; } else if ( type.isEntityType() ) { final EntityType entityType = (EntityType) type; if ( entityType.isOneToOne() ) { - return value; + returnedValue = value; } else { - final String entityName = entityType.getAssociatedEntityName(); - return isNullifiable( entityName, value ) ? null : value; + // If value is lazy, it may need to be initialized to + // determine if the value is nullifiable. + final Object possiblyInitializedValue = initializeIfNecessary( value, propertyName, entityType ); + if ( possiblyInitializedValue == null ) { + // The uninitialized value was initialized to null + returnedValue = null; + } + else { + // If the value is not nullifiable, make sure that the + // possibly initialized value is returned. + returnedValue = isNullifiable( entityType.getAssociatedEntityName(), possiblyInitializedValue ) + ? null + : possiblyInitializedValue; + } } } else if ( type.isAnyType() ) { - return isNullifiable( null, value ) ? null : value; + returnedValue = isNullifiable( null, value ) ? null : value; } else if ( type.isComponentType() ) { final CompositeType actype = (CompositeType) type; final Object[] subvalues = actype.getPropertyValues( value, session ); final Type[] subtypes = actype.getSubtypes(); + final String[] subPropertyNames = actype.getPropertyNames(); boolean substitute = false; for ( int i = 0; i < subvalues.length; i++ ) { - final Object replacement = nullifyTransientReferences( subvalues[i], subtypes[i] ); + final Object replacement = nullifyTransientReferences( + subvalues[i], + StringHelper.qualify( propertyName, subPropertyNames[i] ), + subtypes[i] + ); if ( replacement != subvalues[i] ) { substitute = true; subvalues[i] = replacement; @@ -107,7 +137,47 @@ else if ( type.isComponentType() ) { // todo : need to account for entity mode on the CompositeType interface :( actype.setPropertyValues( value, subvalues, EntityMode.POJO ); } - return value; + returnedValue = value; + } + else { + returnedValue = value; + } + // value != returnedValue if either: + // 1) returnedValue was nullified (set to null); + // or 2) returnedValue was initialized, but not nullified. + // When bytecode-enhancement is used for dirty-checking, the change should + // only be tracked when returnedValue was nullified (1)). + if ( value != returnedValue && returnedValue == null && SelfDirtinessTracker.class.isInstance( self ) ) { + ( (SelfDirtinessTracker) self ).$$_hibernate_trackChange( propertyName ); + } + return returnedValue; + } + + private Object initializeIfNecessary( + final Object value, + final String propertyName, + final Type type) { + if ( isDelete && + value == LazyPropertyInitializer.UNFETCHED_PROPERTY && + type.isEntityType() && + !session.getPersistenceContext().getNullifiableEntityKeys().isEmpty() ) { + // IMPLEMENTATION NOTE: If cascade-remove was mapped for the attribute, + // then value should have been initialized previously, when the remove operation was + // cascaded to the property (because CascadingAction.DELETE.performOnLazyProperty() + // returns true). This particular situation can only arise when cascade-remove is not + // mapped for the association. + + // There is at least one nullifiable entity. We don't know if the lazy + // associated entity is one of the nullifiable entities. If it is, and + // the property is not nullified, then a constraint violation will result. + // The only way to find out if the associated entity is nullifiable is + // to initialize it. + // TODO: there may be ways to fine-tune when initialization is necessary + // (e.g., only initialize when the associated entity type is a + // superclass or the same as the entity type of a nullifiable entity). + // It is unclear if a more complicated check would impact performance + // more than just initializing the associated entity. + return ( (LazyPropertyInitializer) persister ).initializeLazyProperty( propertyName, self, session ); } else { return value; @@ -162,9 +232,7 @@ private boolean isNullifiable(final String entityName, Object object) else { return entityEntry.isNullifiable( isEarlyInsert, session ); } - } - } /** @@ -307,8 +375,8 @@ public static NonNullableTransientDependencies findNonNullableTransientEntities( Object[] values, boolean isEarlyInsert, SharedSessionContractImplementor session) { - final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session ); final EntityPersister persister = session.getEntityPersister( entityName, entity ); + final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session, persister ); final String[] propertyNames = persister.getPropertyNames(); final Type[] types = persister.getPropertyTypes(); final boolean[] nullability = persister.getPropertyNullability(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ImmutableEntityEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ImmutableEntityEntry.java index d32c1138246605c2ed3f6e7d46206a81b86656d8..7a93747a20bd8ede26dc96b1752b3462966b1c69 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/ImmutableEntityEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ImmutableEntityEntry.java @@ -22,12 +22,12 @@ /** * An EntityEntry implementation for immutable entities. Note that this implementation is not completely - * immutable in terms of its internal state; the term immutable here refers to the entity is describes. + * immutable in terms of its internal state; the term immutable here refers to the entity it describes. * * @author Gavin King - * @author Emmanuel Bernard + * @author Emmanuel Bernard * @author Gunnar Morling - * @author Sanne Grinovero + * @author Sanne Grinovero * * @see org.hibernate.annotations.Immutable */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/MutableEntityEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/MutableEntityEntry.java index d68d7e29bd95528bdb3180c41fcad6cfecbaf43a..59f9fc2d4f5aebccfe2d00d6f80a9f62861ec6ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/MutableEntityEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/MutableEntityEntry.java @@ -22,9 +22,9 @@ * An EntityEntry implementation for mutable entities. * * @author Gavin King - * @author Emmanuel Bernard + * @author Emmanuel Bernard * @author Gunnar Morling - * @author Sanne Grinovero + * @author Sanne Grinovero */ public final class MutableEntityEntry extends AbstractEntityEntry { /** 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 28bf7af324db800f56960e2dcc93067f9ad7cd9d..d3a5be13c4ba60d947588df9a20f8bb2662d2a5a 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 @@ -31,6 +31,8 @@ import org.hibernate.PersistentObjectException; import org.hibernate.TransientObjectException; import org.hibernate.action.spi.AfterTransactionCompletionProcess; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.cache.spi.access.SoftLock; @@ -173,10 +175,6 @@ public StatefulPersistenceContext(SharedSessionContractImplementor session) { nullifiableEntityKeys = new HashSet<>(); - initTransientState(); - } - - private void initTransientState() { nullAssociations = new HashSet<>( INIT_COLL_SIZE ); nonlazyCollections = new ArrayList<>( INIT_COLL_SIZE ); } @@ -231,7 +229,6 @@ public void clear() { } for ( Entry objectEntityEntryEntry : entityEntryContext.reentrantSafeEntityEntries() ) { - // todo : I dont think this need be reentrant safe if ( objectEntityEntryEntry.getKey() instanceof PersistentAttributeInterceptable ) { final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) objectEntityEntryEntry.getKey() ).$$_hibernate_getInterceptor(); if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { @@ -240,9 +237,8 @@ public void clear() { } } - for ( Map.Entry aCollectionEntryArray : IdentityMap.concurrentEntries( collectionEntries ) ) { - aCollectionEntryArray.getKey().unsetSession( getSession() ); - } + final SharedSessionContractImplementor session = getSession(); + IdentityMap.onEachKey( collectionEntries, k -> k.unsetSession( session ) ); arrayHolders.clear(); entitiesByKey.clear(); @@ -495,6 +491,8 @@ public EntityEntry addEntry( final boolean existsInDatabase, final EntityPersister persister, final boolean disableVersionIncrement) { + assert lockMode != null; + final EntityEntry e; /* @@ -571,15 +569,29 @@ public boolean containsProxy(Object entity) { @Override public boolean reassociateIfUninitializedProxy(Object value) throws MappingException { - if ( !Hibernate.isInitialized( value ) ) { - final HibernateProxy proxy = (HibernateProxy) value; - final LazyInitializer li = proxy.getHibernateLazyInitializer(); - reassociateProxy( li, proxy ); - return true; - } - else { - return false; + if ( ! Hibernate.isInitialized( value ) ) { + + // could be a proxy.... + if ( value instanceof HibernateProxy ) { + final HibernateProxy proxy = (HibernateProxy) value; + final LazyInitializer li = proxy.getHibernateLazyInitializer(); + reassociateProxy( li, proxy ); + return true; + } + + // or an uninitialized enhanced entity ("bytecode proxy")... + if ( value instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable bytecodeProxy = (PersistentAttributeInterceptable) value; + final BytecodeLazyAttributeInterceptor interceptor = (BytecodeLazyAttributeInterceptor) bytecodeProxy.$$_hibernate_getInterceptor(); + if ( interceptor != null ) { + interceptor.setSession( getSession() ); + } + return true; + } + } + + return false; } @Override @@ -602,7 +614,7 @@ public void reassociateProxy(Object value, Serializable id) throws MappingExcept private void reassociateProxy(LazyInitializer li, HibernateProxy proxy) { if ( li.getSession() != this.getSession() ) { final EntityPersister persister = session.getFactory().getMetamodel().entityPersister( li.getEntityName() ); - final EntityKey key = session.generateEntityKey( li.getIdentifier(), persister ); + final EntityKey key = session.generateEntityKey( li.getInternalIdentifier(), persister ); // any earlier proxy takes precedence proxiesByKey.putIfAbsent( key, proxy ); proxy.getHibernateLazyInitializer().setSession( session ); @@ -636,6 +648,14 @@ public Object unproxyAndReassociate(Object maybeProxy) throws HibernateException //initialize + unwrap the object and return it return li.getImplementation(); } + else if ( maybeProxy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) maybeProxy; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + ( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( maybeProxy, null ); + } + return maybeProxy; + } else { return maybeProxy; } @@ -720,6 +740,11 @@ public Object proxyFor(Object impl) throws HibernateException { return proxyFor( e.getPersister(), e.getEntityKey(), impl ); } + @Override + public void addEnhancedProxy(EntityKey key, PersistentAttributeInterceptable entity) { + entitiesByKey.put( key, entity ); + } + @Override public Object getCollectionOwner(Serializable key, CollectionPersister collectionPersister) throws MappingException { // todo : we really just need to add a split in the notions of: @@ -1229,7 +1254,7 @@ && isFoundInParent( propertyName, childEntity, persister, collectionPersister, p ); } if ( found ) { - return proxy.getHibernateLazyInitializer().getIdentifier(); + return proxy.getHibernateLazyInitializer().getInternalIdentifier(); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java index 3b426effe5ef72dd9804b5f46f7f431d17a226a3..ef4472d93ea36cba10f1d482c0a9775eb693ab13 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java @@ -38,6 +38,7 @@ import org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl; import org.hibernate.proxy.HibernateProxy; import org.hibernate.stat.internal.StatsHelper; +import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; @@ -103,6 +104,34 @@ public static void postHydrate( } } + /** + * @deprecated This method will be removed. Use {@link #initializeEntity(Object, boolean, SharedSessionContractImplementor, PreLoadEvent, Iterable)} instead. + * + * @param entity The entity being loaded + * @param readOnly Is the entity being loaded as read-only + * @param session The Session + * @param preLoadEvent The (re-used) pre-load event + */ + @Deprecated + public static void initializeEntity( + final Object entity, + final boolean readOnly, + final SharedSessionContractImplementor session, + final PreLoadEvent preLoadEvent) { + final PersistenceContext persistenceContext = session.getPersistenceContext(); + final EntityEntry entityEntry = persistenceContext.getEntry( entity ); + if ( entityEntry == null ) { + throw new AssertionFailure( "possible non-threadsafe access to the session" ); + } + final EventListenerGroup listenerGroup = session + .getFactory() + .getServiceRegistry() + .getService( EventListenerRegistry.class ) + .getEventListenerGroup( EventType.PRE_LOAD ); + final Iterable listeners = listenerGroup.listeners(); + doInitializeEntity( entity, entityEntry, readOnly, session, preLoadEvent, listeners ); + } + /** * Perform the second step of 2-phase load. Fully initialize the entity * instance. @@ -115,18 +144,20 @@ public static void postHydrate( * @param readOnly Is the entity being loaded as read-only * @param session The Session * @param preLoadEvent The (re-used) pre-load event + * @param preLoadEventListeners the pre-load event listeners */ public static void initializeEntity( final Object entity, final boolean readOnly, final SharedSessionContractImplementor session, - final PreLoadEvent preLoadEvent) { + final PreLoadEvent preLoadEvent, + final Iterable preLoadEventListeners) { final PersistenceContext persistenceContext = session.getPersistenceContext(); final EntityEntry entityEntry = persistenceContext.getEntry( entity ); if ( entityEntry == null ) { throw new AssertionFailure( "possible non-threadsafe access to the session" ); } - doInitializeEntity( entity, entityEntry, readOnly, session, preLoadEvent ); + doInitializeEntity( entity, entityEntry, readOnly, session, preLoadEvent, preLoadEventListeners ); } private static void doInitializeEntity( @@ -134,7 +165,8 @@ private static void doInitializeEntity( final EntityEntry entityEntry, final boolean readOnly, final SharedSessionContractImplementor session, - final PreLoadEvent preLoadEvent) throws HibernateException { + final PreLoadEvent preLoadEvent, + final Iterable preLoadEventListeners) throws HibernateException { final PersistenceContext persistenceContext = session.getPersistenceContext(); final EntityPersister persister = entityEntry.getPersister(); final Serializable id = entityEntry.getId(); @@ -153,7 +185,6 @@ private static void doInitializeEntity( final Type[] types = persister.getPropertyTypes(); for ( int i = 0; i < hydratedState.length; i++ ) { final Object value = hydratedState[i]; - Boolean overridingEager = getOverridingEager( session, entityName, propertyNames[i], types[i] ); if ( value == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { // IMPLEMENTATION NOTE: This is a lazy property on a bytecode-enhanced entity. // hydratedState[i] needs to remain LazyPropertyInitializer.UNFETCHED_PROPERTY so that @@ -164,11 +195,13 @@ private static void doInitializeEntity( // HHH-10989: We need to resolve the collection so that a CollectionReference is added to StatefulPersistentContext. // As mentioned above, hydratedState[i] needs to remain LazyPropertyInitializer.UNFETCHED_PROPERTY // so do not assign the resolved, unitialized PersistentCollection back to hydratedState[i]. + Boolean overridingEager = getOverridingEager( session, entityName, propertyNames[i], types[i], debugEnabled ); types[i].resolve( value, session, entity, overridingEager ); } } else if ( value != PropertyAccessStrategyBackRefImpl.UNKNOWN ) { // we know value != LazyPropertyInitializer.UNFETCHED_PROPERTY + Boolean overridingEager = getOverridingEager( session, entityName, propertyNames[i], types[i], debugEnabled ); hydratedState[i] = types[i].resolve( value, session, entity, overridingEager ); } } @@ -176,13 +209,7 @@ else if ( value != PropertyAccessStrategyBackRefImpl.UNKNOWN ) { //Must occur after resolving identifiers! if ( session.isEventSource() ) { preLoadEvent.setEntity( entity ).setState( hydratedState ).setId( id ).setPersister( persister ); - - final EventListenerGroup listenerGroup = session - .getFactory() - .getServiceRegistry() - .getService( EventListenerRegistry.class ) - .getEventListenerGroup( EventType.PRE_LOAD ); - for ( PreLoadEventListener listener : listenerGroup.listeners() ) { + for ( PreLoadEventListener listener : preLoadEventListeners ) { listener.onPreLoad( preLoadEvent ); } } @@ -190,6 +217,7 @@ else if ( value != PropertyAccessStrategyBackRefImpl.UNKNOWN ) { persister.setPropertyValues( entity, hydratedState ); final SessionFactoryImplementor factory = session.getFactory(); + final StatisticsImplementor statistics = factory.getStatistics(); if ( persister.canWriteToCache() && session.getCacheMode().isPutEnabled() ) { if ( debugEnabled ) { @@ -231,8 +259,8 @@ else if ( value != PropertyAccessStrategyBackRefImpl.UNKNOWN ) { useMinimalPuts( session, entityEntry ) ); - if ( put && factory.getStatistics().isStatisticsEnabled() ) { - factory.getStatistics().entityCachePut( + if ( put && statistics.isStatisticsEnabled() ) { + statistics.entityCachePut( StatsHelper.INSTANCE.getRootEntityRole( persister ), cache.getRegion().getName() ); @@ -293,8 +321,8 @@ else if ( value != PropertyAccessStrategyBackRefImpl.UNKNOWN ) { ); } - if ( factory.getStatistics().isStatisticsEnabled() ) { - factory.getStatistics().loadEntity( persister.getEntityName() ); + if ( statistics.isStatisticsEnabled() ) { + statistics.loadEntity( persister.getEntityName() ); } } @@ -308,14 +336,17 @@ else if ( value != PropertyAccessStrategyBackRefImpl.UNKNOWN ) { * @return null if there is no overriding, true if it is overridden to eager and false if it is overridden to lazy */ private static Boolean getOverridingEager( - SharedSessionContractImplementor session, - String entityName, - String associationName, - Type type) { - if ( type.isAssociationType() || type.isCollectionType() ) { - Boolean overridingEager = isEagerFetchProfile( session, entityName + "." + associationName ); - - if ( LOG.isDebugEnabled() ) { + final SharedSessionContractImplementor session, + final String entityName, + final String associationName, + final Type type, + final boolean isDebugEnabled) { + // Performance: check type.isCollectionType() first, as type.isAssociationType() is megamorphic + if ( type.isCollectionType() || type.isAssociationType() ) { + final Boolean overridingEager = isEagerFetchProfile( session, entityName, associationName ); + + //This method is very hot, and private so let's piggy back on the fact that the caller already knows the debugging state. + if ( isDebugEnabled ) { if ( overridingEager != null ) { LOG.debugf( "Overriding eager fetching using active fetch profile. EntityName: %s, associationName: %s, eager fetching: %s", @@ -331,17 +362,23 @@ private static Boolean getOverridingEager( return null; } - private static Boolean isEagerFetchProfile(SharedSessionContractImplementor session, String role) { + private static Boolean isEagerFetchProfile(SharedSessionContractImplementor session, String entityName, String associationName) { LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers(); - for ( String fetchProfileName : loadQueryInfluencers.getEnabledFetchProfileNames() ) { - FetchProfile fp = session.getFactory().getFetchProfile( fetchProfileName ); - Fetch fetch = fp.getFetchByRole( role ); - if ( fetch != null && Fetch.Style.JOIN == fetch.getStyle() ) { - return true; + // Performance: avoid concatenating entityName + "." + associationName when there is no need, + // as otherwise this section becomes an hot allocation point. + if ( loadQueryInfluencers.hasEnabledFetchProfiles() ) { + final String role = entityName + '.' + associationName; + final SessionFactoryImplementor factory = session.getFactory(); + for ( String fetchProfileName : loadQueryInfluencers.getEnabledFetchProfileNames() ) { + FetchProfile fp = factory.getFetchProfile( fetchProfileName ); + Fetch fetch = fp.getFetchByRole( role ); + if ( fetch != null && Fetch.Style.JOIN == fetch.getStyle() ) { + return true; + } } } - + return null; } @@ -356,11 +393,13 @@ private static Boolean isEagerFetchProfile(SharedSessionContractImplementor sess * @param entity The entity * @param session The Session * @param postLoadEvent The (re-used) post-load event + * @param postLoadEventListeners thet post-load eventListeners */ public static void postLoad( final Object entity, final SharedSessionContractImplementor session, - final PostLoadEvent postLoadEvent) { + final PostLoadEvent postLoadEvent, + final Iterable postLoadEventListeners) { if ( session.isEventSource() ) { final PersistenceContext persistenceContext @@ -369,16 +408,31 @@ public static void postLoad( postLoadEvent.setEntity( entity ).setId( entityEntry.getId() ).setPersister( entityEntry.getPersister() ); - final EventListenerGroup listenerGroup = session.getFactory() - .getServiceRegistry() - .getService( EventListenerRegistry.class ) - .getEventListenerGroup( EventType.POST_LOAD ); - for ( PostLoadEventListener listener : listenerGroup.listeners() ) { + for ( PostLoadEventListener listener : postLoadEventListeners ) { listener.onPostLoad( postLoadEvent ); } } } + /** + * This method will be removed. + * @deprecated Use {@link #postLoad(Object, SharedSessionContractImplementor, PostLoadEvent, Iterable)} + * instead. + */ + @Deprecated + public static void postLoad( + final Object entity, + final SharedSessionContractImplementor session, + final PostLoadEvent postLoadEvent) { + + final EventListenerGroup listenerGroup = session.getFactory() + .getServiceRegistry() + .getService( EventListenerRegistry.class ) + .getEventListenerGroup( EventType.POST_LOAD ); + + postLoad( entity, session, postLoadEvent, listenerGroup.listeners() ); + } + private static boolean useMinimalPuts(SharedSessionContractImplementor session, EntityEntry entityEntry) { if ( session.getFactory().getSessionFactoryOptions().isMinimalPutsEnabled() ) { return session.getCacheMode() != CacheMode.REFRESH; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableBlobProxy.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableBlobProxy.java index 465a9ffa7833553a25e3d94d3e8a34099eb29e41..efef9593a5bec25360506f893d1498f261e79044 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableBlobProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/SerializableBlobProxy.java @@ -31,7 +31,7 @@ public class SerializableBlobProxy implements InvocationHandler, Serializable { * Builds a serializable {@link Blob} wrapper around the given {@link Blob}. * * @param blob The {@link Blob} to be wrapped. - * @see + * @see #generateProxy(java.sql.Blob) */ private SerializableBlobProxy(Blob blob) { this.blob = blob; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java index b27f6be90a668d280d338140d782e50a404c1011..bef49a41eee7d97d4cb9cf6625a1bc9958914d1a 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java @@ -21,6 +21,7 @@ import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.resource.jdbc.ResourceRegistry; /** * Convenience base class for implementers of the Batch interface. @@ -40,8 +41,8 @@ public abstract class AbstractBatchImpl implements Batch { private final SqlStatementLogger sqlStatementLogger; private final SqlExceptionHelper sqlExceptionHelper; - private LinkedHashMap statements = new LinkedHashMap(); - private LinkedHashSet observers = new LinkedHashSet(); + private LinkedHashMap statements = new LinkedHashMap<>(); + private LinkedHashSet observers = new LinkedHashSet<>(); protected AbstractBatchImpl(BatchKey key, JdbcCoordinator jdbcCoordinator) { if ( key == null ) { @@ -152,17 +153,31 @@ public final void execute() { } protected void releaseStatements() { - for ( PreparedStatement statement : getStatements().values() ) { + final LinkedHashMap statements = getStatements(); + final ResourceRegistry resourceRegistry = jdbcCoordinator.getResourceRegistry(); + for ( PreparedStatement statement : statements.values() ) { clearBatch( statement ); - jdbcCoordinator.getResourceRegistry().release( statement ); - jdbcCoordinator.afterStatementExecution(); + resourceRegistry.release( statement ); } - getStatements().clear(); + // IMPL NOTE: If the statements are not cleared and JTA is being used, then + // jdbcCoordinator.afterStatementExecution() will abort the batch and a + // warning will be logged. To avoid the warning, clear statements first, + // before calling jdbcCoordinator.afterStatementExecution(). + statements.clear(); + jdbcCoordinator.afterStatementExecution(); } protected void clearBatch(PreparedStatement statement) { try { - statement.clearBatch(); + // This code can be called after the connection is released + // and the statement is closed. If the statement is closed, + // then SQLException will be thrown when PreparedStatement#clearBatch + // is called. + // Ensure the statement is not closed before + // calling PreparedStatement#clearBatch. + if ( !statement.isClosed() ) { + statement.clearBatch(); + } } catch ( SQLException e ) { LOG.unableToReleaseBatchStatement(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java index 99310128787ee3a09214c8a613ce8ccb5ee27841..bf5f7e4b07670a1456974665b0917be675a7e499 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java @@ -77,6 +77,7 @@ public void addToBatch() { currentStatement.addBatch(); } catch ( SQLException e ) { + abortBatch(); LOG.debugf( "SQLException escaped proxy", e ); throw sqlExceptionHelper().convert( e, "could not perform addBatch", currentStatementSql ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java index 8089bf3da55eae3ff89d4a5f1647d5f227f6314b..9a4ba49a3bf5ed163086a811a3f7df8f3f963926 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java @@ -61,26 +61,15 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator { private transient LogicalConnectionImplementor logicalConnection; private transient JdbcSessionOwner owner; + private transient JdbcServices jdbcServices; + private transient Batch currentBatch; private transient long transactionTimeOutInstant = -1; - /** - * This is a marker value to insert instead of null values for when a Statement gets registered in xref - * but has no associated ResultSets registered. This is useful to efficiently check against duplicate - * registration but you'll have to check against instance equality rather than null before attempting - * to add elements to this set. - */ - private static final Set EMPTY_RESULTSET = Collections.emptySet(); - - private final HashMap> xref = new HashMap<>(); - private final Set unassociatedResultSets = new HashSet<>(); - private transient SqlExceptionHelper exceptionHelper; - private Statement lastQuery; private final boolean isUserSuppliedConnection; - /** * If true, manually (and temporarily) circumvent aggressive release processing. */ @@ -110,10 +99,9 @@ public JdbcCoordinatorImpl( ); } this.owner = owner; - this.exceptionHelper = owner.getJdbcSessionContext() + this.jdbcServices = owner.getJdbcSessionContext() .getServiceRegistry() - .getService( JdbcServices.class ) - .getSqlExceptionHelper(); + .getService( JdbcServices.class ); } private JdbcCoordinatorImpl( @@ -123,10 +111,9 @@ private JdbcCoordinatorImpl( this.logicalConnection = logicalConnection; this.isUserSuppliedConnection = isUserSuppliedConnection; this.owner = owner; - this.exceptionHelper = owner.getJdbcSessionContext() + this.jdbcServices = owner.getJdbcSessionContext() .getServiceRegistry() - .getService( JdbcServices.class ) - .getSqlExceptionHelper(); + .getService( JdbcServices.class ); } @Override @@ -148,7 +135,7 @@ protected BatchBuilder batchBuilder() { * @return The SqlExceptionHelper */ public SqlExceptionHelper sqlExceptionHelper() { - return exceptionHelper; + return jdbcServices.getSqlExceptionHelper(); } private int flushDepth; @@ -183,7 +170,6 @@ public Connection close() { LOG.closingUnreleasedBatch(); currentBatch.release(); } - cleanup(); } finally { connection = logicalConnection.close(); @@ -227,7 +213,7 @@ public void abortBatch() { @Override public StatementPreparer getStatementPreparer() { if ( statementPreparer == null ) { - statementPreparer = new StatementPreparerImpl( this ); + statementPreparer = new StatementPreparerImpl( this, jdbcServices ); } return statementPreparer; } @@ -348,12 +334,17 @@ public void registerLastQuery(Statement statement) { @Override public void cancelLastQuery() { try { - if (lastQuery != null) { + if ( lastQuery != null ) { lastQuery.cancel(); } } catch (SQLException sqle) { - throw exceptionHelper.convert( sqle, "Cannot cancel query" ); + SqlExceptionHelper sqlExceptionHelper = jdbcServices.getSqlExceptionHelper(); + //Should always be non-null, but to make sure as the implementation is lazy: + if ( sqlExceptionHelper != null ) { + sqlExceptionHelper = new SqlExceptionHelper( false ); + } + throw sqlExceptionHelper.convert( sqle, "Cannot cancel query" ); } finally { lastQuery = null; @@ -370,23 +361,6 @@ public void disableReleases() { releasesEnabled = false; } - private void cleanup() { - for ( Map.Entry> entry : xref.entrySet() ) { - closeAll( entry.getValue() ); - close( entry.getKey() ); - } - xref.clear(); - - closeAll( unassociatedResultSets ); - } - - protected void closeAll(Set resultSets) { - for ( ResultSet resultSet : resultSets ) { - close( resultSet ); - } - resultSets.clear(); - } - @SuppressWarnings({ "unchecked" }) protected void close(Statement statement) { LOG.tracev( "Closing prepared statement [{0}]", statement ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/StatementPreparerImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/StatementPreparerImpl.java index a6b44fc50b7f55c75da5523ca6747b271d928738..2bbbe6da0a4bad536e83e0fc60adeee76a377e29 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/StatementPreparerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/StatementPreparerImpl.java @@ -18,6 +18,7 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.jdbc.spi.StatementPreparer; +import org.hibernate.resource.jdbc.spi.JdbcObserver; import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor; /** @@ -28,15 +29,17 @@ * @author Brett Meyer */ class StatementPreparerImpl implements StatementPreparer { - private JdbcCoordinatorImpl jdbcCoordinator; + private final JdbcCoordinatorImpl jdbcCoordinator; + private final JdbcServices jdbcServices; /** * Construct a StatementPreparerImpl * * @param jdbcCoordinator The JdbcCoordinatorImpl */ - StatementPreparerImpl(JdbcCoordinatorImpl jdbcCoordinator) { + StatementPreparerImpl(JdbcCoordinatorImpl jdbcCoordinator, JdbcServices jdbcServices) { this.jdbcCoordinator = jdbcCoordinator; + this.jdbcServices = jdbcServices; } protected final SessionFactoryOptions settings() { @@ -52,7 +55,7 @@ protected final LogicalConnectionImplementor logicalConnection() { } protected final SqlExceptionHelper sqlExceptionHelper() { - return getJdbcService().getSqlExceptionHelper(); + return jdbcServices.getSqlExceptionHelper(); } @Override @@ -164,16 +167,17 @@ protected StatementPreparationTemplate(String incomingSql) { public PreparedStatement prepareStatement() { try { - getJdbcService().getSqlStatementLogger().logStatement( sql ); + jdbcServices.getSqlStatementLogger().logStatement( sql ); final PreparedStatement preparedStatement; + final JdbcObserver observer = jdbcCoordinator.getJdbcSessionOwner().getJdbcSessionContext().getObserver(); try { - jdbcCoordinator.getJdbcSessionOwner().getJdbcSessionContext().getObserver().jdbcPrepareStatementStart(); + observer.jdbcPrepareStatementStart(); preparedStatement = doPrepare(); setStatementTimeout( preparedStatement ); } finally { - jdbcCoordinator.getJdbcSessionOwner().getJdbcSessionContext().getObserver().jdbcPrepareStatementEnd(); + observer.jdbcPrepareStatementEnd(); } postProcess( preparedStatement ); return preparedStatement; @@ -198,14 +202,6 @@ private void setStatementTimeout(PreparedStatement preparedStatement) throws SQL } } - private JdbcServices getJdbcService() { - return jdbcCoordinator - .getJdbcSessionOwner() - .getJdbcSessionContext() - .getServiceRegistry() - .getService( JdbcServices.class ); - } - private abstract class QueryStatementPreparationTemplate extends StatementPreparationTemplate { protected QueryStatementPreparationTemplate(String sql) { super( sql ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java index dc6936723ed62abde427a6dc6c4ba4133705189a..3a2ddbde0a327bef372a6db6f7add68addb901aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java @@ -55,7 +55,11 @@ public boolean isLogToStdout() { * Enable (true) or disable (false) logging to stdout. * * @param logToStdout True to enable logging to stdout; false to disable. + * + * @deprecated Will likely be removed: + * Should either become immutable or threadsafe. */ + @Deprecated public void setLogToStdout(boolean logToStdout) { this.logToStdout = logToStdout; } @@ -64,6 +68,11 @@ public boolean isFormat() { return format; } + /** + * @deprecated Will likely be removed: + * Should either become immutable or threadsafe. + */ + @Deprecated public void setFormat(boolean format) { this.format = format; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java b/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java index 4a448e8bb4886631a90a4862d4f442f7278b6d9e..3aa6e60399b49763c8d6c8440f53e741ae19f555 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/CollectionLoadContext.java @@ -93,8 +93,7 @@ public LoadContexts getLoadContext() { * @return The loading collection (see discussion above). */ public PersistentCollection getLoadingCollection(final CollectionPersister persister, final Serializable key) { - final EntityMode em = persister.getOwnerEntityPersister().getEntityMetamodel().getEntityMode(); - final CollectionKey collectionKey = new CollectionKey( persister, key, em ); + final CollectionKey collectionKey = new CollectionKey( persister, key ); if ( LOG.isTraceEnabled() ) { LOG.tracev( "Starting attempt to find loading collection [{0}]", MessageHelper.collectionInfoString( persister.getRole(), key ) ); @@ -179,8 +178,7 @@ else if ( lce.getResultSet() == resultSet && lce.getPersister() == persister ) { session.getPersistenceContext().addUnownedCollection( new CollectionKey( persister, - lce.getKey(), - persister.getOwnerEntityPersister().getEntityMetamodel().getEntityMode() + lce.getKey() ), lce.getCollection() ); @@ -256,7 +254,7 @@ private void endLoadingCollection(LoadingCollectionEntry lce, CollectionPersiste // If the owner is bytecode-enhanced and the owner's collection value is uninitialized, // then go ahead and set it to the newly initialized collection. final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = - persister.getOwnerEntityPersister().getInstrumentationMetadata(); + persister.getOwnerEntityPersister().getBytecodeEnhancementMetadata(); if ( bytecodeEnhancementMetadata.isEnhancedForLazyLoading() ) { // Lazy properties in embeddables/composites are not currently supported for embeddables (HHH-10480), // so check to make sure the collection is not in an embeddable before checking to see if @@ -321,7 +319,7 @@ private void addCollectionToCache(LoadingCollectionEntry lce, CollectionPersiste LOG.debugf( "Caching collection: %s", MessageHelper.collectionInfoString( persister, lce.getCollection(), lce.getKey(), session ) ); } - if ( !session.getLoadQueryInfluencers().getEnabledFilters().isEmpty() && persister.isAffectedByEnabledFilters( session ) ) { + if ( session.getLoadQueryInfluencers().hasEnabledFilters() && persister.isAffectedByEnabledFilters( session ) ) { // some filters affecting the collection are enabled on the session, so do not do the put into the cache. if ( debugEnabled ) { LOG.debug( "Refusing to add to cache due to enabled filters" ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/LoadContexts.java b/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/LoadContexts.java index 4412dfbf799fb2418a8c684c3976be46d674b2a3..53e6452c2786efb457d09015846294992d761167 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/LoadContexts.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/loading/internal/LoadContexts.java @@ -159,20 +159,20 @@ public CollectionLoadContext getCollectionLoadContext(ResultSet resultSet) { } /** - * Attempt to locate the loading collection given the owner's key. The lookup here + * Attempt to locate the loading collection given the CollectionKey obtained from the owner's key. The lookup here * occurs against all result-set contexts... * * @param persister The collection persister - * @param ownerKey The owner key + * @param key The collection key * @return The loading collection, or null if not found. */ - public PersistentCollection locateLoadingCollection(CollectionPersister persister, Serializable ownerKey) { - final LoadingCollectionEntry lce = locateLoadingCollectionEntry( new CollectionKey( persister, ownerKey ) ); + public PersistentCollection locateLoadingCollection(CollectionPersister persister, CollectionKey key) { + final LoadingCollectionEntry lce = locateLoadingCollectionEntry( key ) ; if ( lce != null ) { if ( LOG.isTraceEnabled() ) { LOG.tracef( "Returning loading collection: %s", - MessageHelper.collectionInfoString( persister, ownerKey, getSession().getFactory() ) + MessageHelper.collectionInfoString( persister, key.getKey(), getSession().getFactory() ) ); } return lce.getCollection(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/AbstractParameterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/AbstractParameterDescriptor.java index aab0351faae17dfa70110ee9669626c51f0522f5..df24844621f0958d2c508c764dbb20aafb7d0849 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/AbstractParameterDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/AbstractParameterDescriptor.java @@ -6,12 +6,16 @@ */ package org.hibernate.engine.query.spi; +import org.hibernate.Incubating; import org.hibernate.query.QueryParameter; import org.hibernate.type.Type; /** + * NOTE: Consider this contract (and its sub-contracts) as incubating as we transition to 6.0 and SQM + * * @author Steve Ebersole */ +@Incubating public abstract class AbstractParameterDescriptor implements QueryParameter { private final int[] sourceLocations; @@ -38,7 +42,7 @@ public Class getParameterType() { } @Override - public Type getType() { + public Type getHibernateType() { return getExpectedType(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NamedParameterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NamedParameterDescriptor.java index 38de4409d7f39bff0cc3a65e4b239694b32b6ced..4edddeb7dbf894ba10ad08934d96f98863fa3ce3 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NamedParameterDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/NamedParameterDescriptor.java @@ -30,6 +30,7 @@ public NamedParameterDescriptor(String name, Type expectedType, int[] sourceLoca this.name = name; } + @Override public String getName() { return name; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/QueryPlanCache.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/QueryPlanCache.java index f3356a3bf6de5b968630478a41bc2afd9e68c227..3abec770b27ab97c363ee38ec0e1514ac7954d00 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/QueryPlanCache.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/QueryPlanCache.java @@ -220,7 +220,13 @@ public NativeSQLQueryPlan getNativeSQLQueryPlan(final NativeSQLQuerySpecificatio } /** - * clean up QueryPlanCache when SessionFactory is closed + * Clean up the caches when the SessionFactory is closed. + *

    + * Note that depending on the cache strategy implementation chosen, clearing the cache might not reclaim all the + * memory. + *

    + * Typically, when using LIRS, clearing the cache only invalidates the entries but the outdated entries are kept in + * memory until they are replaced by others. It is not considered a memory leak as the cache is bounded. */ public void cleanup() { LOG.trace( "Cleaning QueryPlan Cache" ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java index 253cbdce79b5cbdcd97266eb53569afcaea44882..3d00b68c957c8a3dad1bea8c50621582f8641069 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java @@ -11,13 +11,13 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; -import java.util.HashMap; +import java.util.BitSet; import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; @@ -45,7 +45,7 @@ import org.hibernate.engine.internal.NonNullableTransientDependencies; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.metadata.ClassMetadata; +import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.type.CollectionType; @@ -128,7 +128,7 @@ ExecutableList get(ActionQueue instance) { ExecutableList init(ActionQueue instance) { if ( instance.isOrderInsertsEnabled() ) { return instance.insertions = new ExecutableList( - new InsertActionSorter() + InsertActionSorter.INSTANCE ); } else { @@ -1022,11 +1022,13 @@ private TransactionCompletionProcesses( * directionality of foreign-keys. So even though we will be changing the ordering here, we need to make absolutely * certain that we do not circumvent this FK ordering to the extent of causing constraint violations. *

    - * Sorts the insert actions using more hashes. + * The algorithm first discovers the transitive incoming dependencies for every insert action + * and groups all inserts by the entity name. + * Finally, it schedules these groups one by one, as long as all the dependencies of the groups are fulfilled. *

    - * NOTE: this class is not thread-safe. - * - * @author Jay Erb + * The implementation will only produce an optimal insert order for the insert groups that can be perfectly scheduled serially. + * Scheduling serially means, that there is an order which doesn't violate the FK constraint dependencies. + * The inserts of insert groups which can't be scheduled, are going to be inserted in the original order. */ private static class InsertActionSorter implements ExecutableList.Sorter { /** @@ -1034,106 +1036,140 @@ private static class InsertActionSorter implements ExecutableList.Sorter parentEntityNames = new HashSet<>( ); + private static class InsertInfo { + private final AbstractEntityInsertAction insertAction; + // Inserts in this set must be executed before this insert + private Set transitiveIncomingDependencies; + // Child dependencies of i.e. one-to-many or inverse one-to-one + // It's necessary to have this for unidirectional associations, to propagate incoming dependencies + private Set outgoingDependencies; + // The current index of the insert info within an insert schedule + private int index; + + public InsertInfo(AbstractEntityInsertAction insertAction, int index) { + this.insertAction = insertAction; + this.index = index; + } - private Set childEntityNames = new HashSet<>( ); + public void buildDirectDependencies(IdentityHashMap insertInfosByEntity) { + final Object[] propertyValues = insertAction.getState(); + final Type[] propertyTypes = insertAction.getPersister().getPropertyTypes(); + for (int i = 0, propertyTypesLength = propertyTypes.length; i < propertyTypesLength; i++) { + addDirectDependency(propertyTypes[i], propertyValues[i], insertInfosByEntity); + } + } - private BatchIdentifier parent; + public void propagateChildDependencies() { + if ( outgoingDependencies != null ) { + for (InsertInfo childDependency : outgoingDependencies) { + if (childDependency.transitiveIncomingDependencies == null) { + childDependency.transitiveIncomingDependencies = new HashSet<>(); + } + childDependency.transitiveIncomingDependencies.add(this); + } + } + } - BatchIdentifier(String entityName, String rootEntityName) { - this.entityName = entityName; - this.rootEntityName = rootEntityName; + public void buildTransitiveDependencies(Set visited) { + if (transitiveIncomingDependencies != null) { + visited.addAll(transitiveIncomingDependencies); + for (InsertInfo insertInfo : transitiveIncomingDependencies.toArray(new InsertInfo[0])) { + insertInfo.addTransitiveDependencies(this, visited); + } + visited.clear(); + } } - public BatchIdentifier getParent() { - return parent; + public void addTransitiveDependencies(InsertInfo origin, Set visited) { + if (transitiveIncomingDependencies != null) { + for (InsertInfo insertInfo : transitiveIncomingDependencies) { + if (visited.add(insertInfo)) { + origin.transitiveIncomingDependencies.add(insertInfo); + insertInfo.addTransitiveDependencies(origin, visited); + } + } + } } - public void setParent(BatchIdentifier parent) { - this.parent = parent; + private void addDirectDependency(Type type, Object value, IdentityHashMap insertInfosByEntity) { + if ( type.isEntityType() && value != null ) { + final EntityType entityType = (EntityType) type; + final InsertInfo insertInfo = insertInfosByEntity.get(value); + if (insertInfo != null) { + if (entityType.isOneToOne() && OneToOneType.class.cast(entityType).getForeignKeyDirection() == ForeignKeyDirection.TO_PARENT) { + if (!entityType.isReferenceToPrimaryKey()) { + if (outgoingDependencies == null) { + outgoingDependencies = new HashSet<>(); + } + outgoingDependencies.add(insertInfo); + } + } + else { + if (transitiveIncomingDependencies == null) { + transitiveIncomingDependencies = new HashSet<>(); + } + transitiveIncomingDependencies.add(insertInfo); + } + } + } + else if ( type.isCollectionType() && value != null ) { + CollectionType collectionType = (CollectionType) type; + final CollectionPersister collectionPersister = insertAction.getSession().getFactory().getMetamodel().collectionPersister(collectionType.getRole()); + // We only care about mappedBy one-to-many associations, because for these, the elements depend on the collection owner + if ( collectionPersister.isOneToMany() && collectionPersister.getElementType().isEntityType() ) { + final Iterator elementsIterator = collectionType.getElementsIterator(value, insertAction.getSession()); + while ( elementsIterator.hasNext() ) { + final Object element = elementsIterator.next(); + final InsertInfo insertInfo = insertInfosByEntity.get(element); + if (insertInfo != null) { + if (outgoingDependencies == null) { + outgoingDependencies = new HashSet<>(); + } + outgoingDependencies.add(insertInfo); + } + } + } + } + else if ( type.isComponentType() && value != null ) { + // Support recursive checks of composite type properties for associations and collections. + CompositeType compositeType = (CompositeType) type; + final SharedSessionContractImplementor session = insertAction.getSession(); + Object[] componentValues = compositeType.getPropertyValues( value, session ); + for ( int j = 0; j < componentValues.length; ++j ) { + Type componentValueType = compositeType.getSubtypes()[j]; + Object componentValue = componentValues[j]; + addDirectDependency( componentValueType, componentValue, insertInfosByEntity); + } + } } @Override public boolean equals(Object o) { - if ( this == o ) { + if (this == o) { return true; } - if ( !( o instanceof BatchIdentifier ) ) { + if (o == null || getClass() != o.getClass()) { return false; } - BatchIdentifier that = (BatchIdentifier) o; - return Objects.equals( entityName, that.entityName ); - } - - @Override - public int hashCode() { - return Objects.hash( entityName ); - } - - String getEntityName() { - return entityName; - } - - String getRootEntityName() { - return rootEntityName; - } - - Set getParentEntityNames() { - return parentEntityNames; - } - - Set getChildEntityNames() { - return childEntityNames; - } - boolean hasAnyParentEntityNames(BatchIdentifier batchIdentifier) { - return parentEntityNames.contains( batchIdentifier.getEntityName() ) || - parentEntityNames.contains( batchIdentifier.getRootEntityName() ); - } + InsertInfo that = (InsertInfo) o; - boolean hasAnyChildEntityNames(BatchIdentifier batchIdentifier) { - return childEntityNames.contains( batchIdentifier.getEntityName() ); + return insertAction.equals(that.insertAction); } - /** - * Check if the this {@link BatchIdentifier} has a parent or grand parent - * matching the given {@link BatchIdentifier reference. - * - * @param batchIdentifier {@link BatchIdentifier} reference - * - * @return This {@link BatchIdentifier} has a parent matching the given {@link BatchIdentifier reference - */ - boolean hasParent(BatchIdentifier batchIdentifier) { - return ( - parent == batchIdentifier - || ( parentEntityNames.contains( batchIdentifier.getEntityName() ) ) - || parent != null && parent.hasParent( batchIdentifier, new ArrayList<>() ) - ); + @Override + public int hashCode() { + return insertAction.hashCode(); } - private boolean hasParent(BatchIdentifier batchIdentifier, List stack) { - if ( !stack.contains( this ) && parent != null ) { - stack.add( this ); - return parent.hasParent( batchIdentifier, stack ); - } - return ( - parent == batchIdentifier - || parentEntityNames.contains( batchIdentifier.getEntityName() ) - ); + @Override + public String toString() { + return "InsertInfo{" + + "insertAction=" + insertAction + + '}'; } } - // the mapping of entity names to their latest batch numbers. - private List latestBatches; - - // the map of batch numbers to EntityInsertAction lists - private Map> actionBatches; - public InsertActionSorter() { } @@ -1141,181 +1177,144 @@ public InsertActionSorter() { * Sort the insert actions. */ public void sort(List insertions) { - // optimize the hash size to eliminate a rehash. - this.latestBatches = new ArrayList<>( ); - this.actionBatches = new HashMap<>(); - - for ( AbstractEntityInsertAction action : insertions ) { - BatchIdentifier batchIdentifier = new BatchIdentifier( - action.getEntityName(), - action.getSession() - .getFactory() - .getMetamodel() - .entityPersister( action.getEntityName() ) - .getRootEntityName() - ); - - // the entity associated with the current action. - Object currentEntity = action.getInstance(); - int index = latestBatches.indexOf( batchIdentifier ); - - if ( index != -1 ) { - batchIdentifier = latestBatches.get( index ); - } - else { - latestBatches.add( batchIdentifier ); - } - addParentChildEntityNames( action, batchIdentifier ); - addToBatch( batchIdentifier, action ); + final int insertInfoCount = insertions.size(); + // Build up dependency metadata for insert actions + final InsertInfo[] insertInfos = new InsertInfo[insertInfoCount]; + // A map of all insert infos keyed by the entity instance + // This is needed to discover insert infos for direct dependencies + final IdentityHashMap insertInfosByEntity = new IdentityHashMap<>( insertInfos.length ); + // Construct insert infos and build a map for that, keyed by entity instance + for (int i = 0; i < insertInfoCount; i++) { + final AbstractEntityInsertAction insertAction = insertions.get(i); + final InsertInfo insertInfo = new InsertInfo(insertAction, i); + insertInfosByEntity.put(insertAction.getInstance(), insertInfo); + insertInfos[i] = insertInfo; } - insertions.clear(); - - // Examine each entry in the batch list, and build the dependency graph. - for ( int i = 0; i < latestBatches.size(); i++ ) { - BatchIdentifier batchIdentifier = latestBatches.get( i ); - - for ( int j = i - 1; j >= 0; j-- ) { - BatchIdentifier prevBatchIdentifier = latestBatches.get( j ); - if ( prevBatchIdentifier.hasAnyParentEntityNames( batchIdentifier ) ) { - prevBatchIdentifier.parent = batchIdentifier; - } - if ( batchIdentifier.hasAnyChildEntityNames( prevBatchIdentifier ) ) { - prevBatchIdentifier.parent = batchIdentifier; - } - } - - for ( int j = i + 1; j < latestBatches.size(); j++ ) { - BatchIdentifier nextBatchIdentifier = latestBatches.get( j ); - - if ( nextBatchIdentifier.hasAnyParentEntityNames( batchIdentifier ) ) { - nextBatchIdentifier.parent = batchIdentifier; - } - if ( batchIdentifier.hasAnyChildEntityNames( nextBatchIdentifier ) ) { - nextBatchIdentifier.parent = batchIdentifier; - } + // First we must discover the direct dependencies + for (int i = 0; i < insertInfoCount; i++) { + insertInfos[i].buildDirectDependencies(insertInfosByEntity); + } + // Then we can propagate child dependencies to the insert infos incoming dependencies + for (int i = 0; i < insertInfoCount; i++) { + insertInfos[i].propagateChildDependencies(); + } + // Finally, we add all the transitive incoming dependencies + // and then group insert infos into EntityInsertGroup keyed by entity name + final Set visited = new HashSet<>(); + final Map insertInfosByEntityName = new LinkedHashMap<>(); + for (int i = 0; i < insertInfoCount; i++) { + final InsertInfo insertInfo = insertInfos[i]; + insertInfo.buildTransitiveDependencies( visited ); + + final String entityName = insertInfo.insertAction.getPersister().getEntityName(); + EntityInsertGroup entityInsertGroup = insertInfosByEntityName.get(entityName); + if (entityInsertGroup == null) { + insertInfosByEntityName.put(entityName, entityInsertGroup = new EntityInsertGroup(entityName)); } + entityInsertGroup.add(insertInfo); } - - boolean sorted = false; - - long maxIterations = latestBatches.size() * latestBatches.size(); - long iterations = 0; - - sort: + // Now we can go through the EntityInsertGroups and schedule all the ones + // for which we have already scheduled all the dependentEntityNames + final Set scheduledEntityNames = new HashSet<>(insertInfosByEntityName.size()); + int schedulePosition = 0; + int lastScheduleSize; do { - // Examine each entry in the batch list, sorting them based on parent/child association - // as depicted by the dependency graph. - iterations++; - - for ( int i = 0; i < latestBatches.size(); i++ ) { - BatchIdentifier batchIdentifier = latestBatches.get( i ); - - // Iterate next batches and make sure that children types are after parents. - // Since the outer loop looks at each batch entry individually and the prior loop will reorder - // entries as well, we need to look and verify if the current batch is a child of the next - // batch or if the current batch is seen as a parent or child of the next batch. - for ( int j = i + 1; j < latestBatches.size(); j++ ) { - BatchIdentifier nextBatchIdentifier = latestBatches.get( j ); - - if ( batchIdentifier.hasParent( nextBatchIdentifier ) && !nextBatchIdentifier.hasParent( batchIdentifier ) ) { - latestBatches.remove( batchIdentifier ); - latestBatches.add( j, batchIdentifier ); - - continue sort; - } + lastScheduleSize = scheduledEntityNames.size(); + final Iterator iterator = insertInfosByEntityName.values().iterator(); + while (iterator.hasNext()) { + final EntityInsertGroup insertGroup = iterator.next(); + if (scheduledEntityNames.containsAll(insertGroup.dependentEntityNames)) { + schedulePosition = schedule(insertInfos, insertGroup.insertInfos, schedulePosition); + scheduledEntityNames.add(insertGroup.entityName); + iterator.remove(); } } - sorted = true; - } - while ( !sorted && iterations <= maxIterations); - - if ( iterations > maxIterations ) { - LOG.warn( "The batch containing " + latestBatches.size() + " statements could not be sorted after " + maxIterations + " iterations. " + - "This might indicate a circular entity relationship." ); + // we try to schedule entity groups over and over again, until we can't schedule any further + } while (lastScheduleSize != scheduledEntityNames.size()); + if ( !insertInfosByEntityName.isEmpty() ) { + LOG.warn("The batch containing " + insertions.size() + " statements could not be sorted. " + + "This might indicate a circular entity relationship."); } - - // Now, rebuild the insertions list. There is a batch for each entry in the name list. - for ( BatchIdentifier rootIdentifier : latestBatches ) { - List batch = actionBatches.get( rootIdentifier ); - insertions.addAll( batch ); + insertions.clear(); + for (InsertInfo insertInfo : insertInfos) { + insertions.add(insertInfo.insertAction); } } - /** - * Add parent and child entity names so that we know how to rearrange dependencies - * - * @param action The action being sorted - * @param batchIdentifier The batch identifier of the entity affected by the action - */ - private void addParentChildEntityNames(AbstractEntityInsertAction action, BatchIdentifier batchIdentifier) { - Object[] propertyValues = action.getState(); - ClassMetadata classMetadata = action.getPersister().getClassMetadata(); - if ( classMetadata != null ) { - Type[] propertyTypes = classMetadata.getPropertyTypes(); - - for ( int i = 0; i < propertyValues.length; i++ ) { - Object value = propertyValues[i]; - Type type = propertyTypes[i]; - addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, type, value ); + private int schedule(InsertInfo[] insertInfos, List insertInfosToSchedule, int schedulePosition) { + final InsertInfo[] newInsertInfos = new InsertInfo[insertInfos.length]; + // The bitset is there to quickly query if an index is already scheduled + final BitSet bitSet = new BitSet(insertInfos.length); + // Remember the smallest index of the insertInfosToSchedule to check if we actually need to reorder anything + int smallestScheduledIndex = -1; + // The biggestScheduledIndex is needed as upper bound for shifting elements that were replaced by insertInfosToSchedule + int biggestScheduledIndex = -1; + for (int i = 0; i < insertInfosToSchedule.size(); i++) { + final int index = insertInfosToSchedule.get(i).index; + bitSet.set(index); + smallestScheduledIndex = Math.min(smallestScheduledIndex, index); + biggestScheduledIndex = Math.max(biggestScheduledIndex, index); + } + final int nextSchedulePosition = schedulePosition + insertInfosToSchedule.size(); + if (smallestScheduledIndex == schedulePosition && biggestScheduledIndex == nextSchedulePosition) { + // In this case, the order is already correct and we can skip some copying + return nextSchedulePosition; + } + // The index to which we start to shift elements that appear within the range of [schedulePosition, nextSchedulePosition) + int shiftSchedulePosition = nextSchedulePosition; + for (int i = 0; i < insertInfosToSchedule.size(); i++) { + final InsertInfo insertInfoToSchedule = insertInfosToSchedule.get(i); + final int targetSchedulePosition = schedulePosition + i; + newInsertInfos[targetSchedulePosition] = insertInfoToSchedule; + insertInfoToSchedule.index = targetSchedulePosition; + final InsertInfo oldInsertInfo = insertInfos[targetSchedulePosition]; + // Move the insert info previously located at the target schedule position to the current shift position + if (!bitSet.get(targetSchedulePosition)) { + oldInsertInfo.index = shiftSchedulePosition; + // Also set this index in the bitset to skip copying the value later, as it is considered scheduled + bitSet.set(targetSchedulePosition); + newInsertInfos[shiftSchedulePosition++]= oldInsertInfo; } } + // We have to shift all the elements up to the biggestMovedIndex + 1 + biggestScheduledIndex++; + for (int i = bitSet.nextClearBit(schedulePosition); i < biggestScheduledIndex; i++) { + // Only copy the old insert info over if it wasn't already scheduled + if (!bitSet.get(i)) { + final InsertInfo insertInfo = insertInfos[i]; + insertInfo.index = shiftSchedulePosition; + newInsertInfos[shiftSchedulePosition++] = insertInfo; + } + } + // Copy over the newly reordered array part into the main array + System.arraycopy(newInsertInfos, schedulePosition, insertInfos, schedulePosition, biggestScheduledIndex - schedulePosition); + return nextSchedulePosition; } - private void addParentChildEntityNameByPropertyAndValue(AbstractEntityInsertAction action, BatchIdentifier batchIdentifier, Type type, Object value) { - if ( type.isEntityType() && value != null ) { - final EntityType entityType = (EntityType) type; - final String entityName = entityType.getName(); - final String rootEntityName = action.getSession().getFactory().getMetamodel().entityPersister( entityName ).getRootEntityName(); + public static class EntityInsertGroup { + private final String entityName; + private final List insertInfos = new ArrayList<>(); + private final Set dependentEntityNames = new HashSet<>(); - if ( entityType.isOneToOne() && OneToOneType.class.cast( entityType ).getForeignKeyDirection() == ForeignKeyDirection.TO_PARENT ) { - if ( !entityType.isReferenceToPrimaryKey() ) { - batchIdentifier.getChildEntityNames().add( entityName ); - } - if ( !rootEntityName.equals( entityName ) ) { - batchIdentifier.getChildEntityNames().add( rootEntityName ); - } - } - else { - batchIdentifier.getParentEntityNames().add( entityName ); - if ( !rootEntityName.equals( entityName ) ) { - batchIdentifier.getParentEntityNames().add( rootEntityName ); - } - } + public EntityInsertGroup(String entityName) { + this.entityName = entityName; } - else if ( type.isCollectionType() && value != null ) { - CollectionType collectionType = (CollectionType) type; - final SessionFactoryImplementor sessionFactory = ( (SessionImplementor) action.getSession() ) - .getSessionFactory(); - if ( collectionType.getElementType( sessionFactory ).isEntityType() ) { - String entityName = collectionType.getAssociatedEntityName( sessionFactory ); - String rootEntityName = action.getSession().getFactory().getMetamodel().entityPersister( entityName ).getRootEntityName(); - batchIdentifier.getChildEntityNames().add( entityName ); - if ( !rootEntityName.equals( entityName ) ) { - batchIdentifier.getChildEntityNames().add( rootEntityName ); + + public void add(InsertInfo insertInfo) { + insertInfos.add(insertInfo); + if (insertInfo.transitiveIncomingDependencies != null) { + for (InsertInfo dependency : insertInfo.transitiveIncomingDependencies) { + dependentEntityNames.add(dependency.insertAction.getEntityName()); } } } - else if ( type.isComponentType() && value != null ) { - // Support recursive checks of composite type properties for associations and collections. - CompositeType compositeType = (CompositeType) type; - final SharedSessionContractImplementor session = action.getSession(); - Object[] componentValues = compositeType.getPropertyValues( value, session ); - for ( int j = 0; j < componentValues.length; ++j ) { - Type componentValueType = compositeType.getSubtypes()[j]; - Object componentValue = componentValues[j]; - addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, componentValueType, componentValue ); - } - } - } - - private void addToBatch(BatchIdentifier batchIdentifier, AbstractEntityInsertAction action) { - List actions = actionBatches.get( batchIdentifier ); - if ( actions == null ) { - actions = new LinkedList<>(); - actionBatches.put( batchIdentifier, actions ); + @Override + public String toString() { + return "EntityInsertGroup{" + + "entityName='" + entityName + '\'' + + '}'; } - actions.add( action ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/BatchFetchQueue.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/BatchFetchQueue.java index 1cd7d0ac5d908715f6fc1b0966bb0c47e9167aaa..9280b087e3fd6d7fa9b4f5f1f5ebc180ecb6a3df 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/BatchFetchQueue.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/BatchFetchQueue.java @@ -40,9 +40,9 @@ public class BatchFetchQueue { /** * A map of {@link SubselectFetch subselect-fetch descriptors} keyed by the - * {@link EntityKey) against which the descriptor is registered. + * {@link EntityKey} against which the descriptor is registered. */ - private final Map subselectsByEntityKey = new HashMap<>( 8 ); + private Map subselectsByEntityKey; /** * Used to hold information about the entities that are currently eligible for batch-fetching. Ultimately @@ -51,13 +51,13 @@ public class BatchFetchQueue { * A Map structure is used to segment the keys by entity type since loading can only be done for a particular entity * type at a time. */ - private final Map > batchLoadableEntityKeys = new HashMap<>( 8 ); + private Map > batchLoadableEntityKeys; /** * Used to hold information about the collections that are currently eligible for batch-fetching. Ultimately * used by {@link #getCollectionBatch} to build collection load batches. */ - private final Map> batchLoadableCollections = new HashMap<>( 8 ); + private Map> batchLoadableCollections; /** * Constructs a queue for the given context. @@ -74,9 +74,9 @@ public BatchFetchQueue(PersistenceContext context) { * Called after flushing or clearing the session. */ public void clear() { - batchLoadableEntityKeys.clear(); - batchLoadableCollections.clear(); - subselectsByEntityKey.clear(); + batchLoadableEntityKeys = null; + batchLoadableCollections = null; + subselectsByEntityKey = null; } @@ -90,16 +90,22 @@ public void clear() { * this entity key. */ public SubselectFetch getSubselect(EntityKey key) { + if ( subselectsByEntityKey == null ) { + return null; + } return subselectsByEntityKey.get( key ); } /** - * Adds a subselect fetch decriptor for the given entity key. + * Adds a subselect fetch descriptor for the given entity key. * * @param key The entity for which to register the subselect fetch. * @param subquery The fetch descriptor. */ public void addSubselect(EntityKey key, SubselectFetch subquery) { + if ( subselectsByEntityKey == null ) { + subselectsByEntityKey = new HashMap<>( 12 ); + } subselectsByEntityKey.put( key, subquery ); } @@ -110,7 +116,9 @@ public void addSubselect(EntityKey key, SubselectFetch subquery) { * need to load its collections) */ public void removeSubselect(EntityKey key) { - subselectsByEntityKey.remove( key ); + if ( subselectsByEntityKey != null ) { + subselectsByEntityKey.remove( key ); + } } // entity batch support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -127,12 +135,15 @@ public void removeSubselect(EntityKey key) { */ public void addBatchLoadableEntityKey(EntityKey key) { if ( key.isBatchLoadable() ) { - LinkedHashSet set = batchLoadableEntityKeys.get( key.getEntityName()); - if (set == null) { - set = new LinkedHashSet<>( 8 ); - batchLoadableEntityKeys.put( key.getEntityName(), set); + if ( batchLoadableEntityKeys == null ) { + batchLoadableEntityKeys = new HashMap<>( 12 ); } - set.add(key); + final LinkedHashSet keysForEntity = batchLoadableEntityKeys.computeIfAbsent( + key.getEntityName(), + k -> new LinkedHashSet<>( 8 ) + ); + + keysForEntity.add( key ); } } @@ -143,9 +154,9 @@ public void addBatchLoadableEntityKey(EntityKey key) { * if necessary */ public void removeBatchLoadableEntityKey(EntityKey key) { - if ( key.isBatchLoadable() ) { - LinkedHashSet set = batchLoadableEntityKeys.get( key.getEntityName()); - if (set != null) { + if ( batchLoadableEntityKeys != null && key.isBatchLoadable() ) { + LinkedHashSet set = batchLoadableEntityKeys.get( key.getEntityName() ); + if ( set != null ) { set.remove(key); } } @@ -155,8 +166,8 @@ public void removeBatchLoadableEntityKey(EntityKey key) { * Intended for test usage. Really has no use-case in Hibernate proper. */ public boolean containsEntityKey(EntityKey key) { - if ( key.isBatchLoadable() ) { - LinkedHashSet set = batchLoadableEntityKeys.get( key.getEntityName()); + if ( batchLoadableEntityKeys != null && key.isBatchLoadable() ) { + LinkedHashSet set = batchLoadableEntityKeys.get( key.getEntityName() ); if ( set != null ) { return set.contains( key ); } @@ -179,8 +190,14 @@ public Serializable[] getEntityBatch( final Serializable id, final int batchSize, final EntityMode entityMode) { - Serializable[] ids = new Serializable[batchSize]; + + final Serializable[] ids = new Serializable[batchSize]; ids[0] = id; //first element of array is reserved for the actual instance we are loading! + + if ( batchLoadableEntityKeys == null ) { + return ids; + } + int i = 1; int end = -1; boolean checkForEnd = false; @@ -238,20 +255,27 @@ private boolean isCached(EntityKey entityKey, EntityPersister persister) { public void addBatchLoadableCollection(PersistentCollection collection, CollectionEntry ce) { final CollectionPersister persister = ce.getLoadedPersister(); - LinkedHashMap map = batchLoadableCollections.get( persister.getRole() ); - if ( map == null ) { - map = new LinkedHashMap<>( 16 ); - batchLoadableCollections.put( persister.getRole(), map ); + if ( batchLoadableCollections == null ) { + batchLoadableCollections = new HashMap<>( 12 ); } + + final LinkedHashMap map = batchLoadableCollections.computeIfAbsent( + persister.getRole(), + k -> new LinkedHashMap<>( 16 ) + ); + map.put( ce, collection ); } - + /** * After a collection was initialized or evicted, we don't * need to batch fetch it anymore, remove it from the queue * if necessary */ public void removeBatchLoadableCollection(CollectionEntry ce) { + if ( batchLoadableCollections == null ) { + return; + } LinkedHashMap map = batchLoadableCollections.get( ce.getLoadedPersister().getRole() ); if ( map != null ) { map.remove( ce ); @@ -271,9 +295,13 @@ public Serializable[] getCollectionBatch( final Serializable id, final int batchSize) { - Serializable[] keys = new Serializable[batchSize]; + final Serializable[] keys = new Serializable[batchSize]; keys[0] = id; + if ( batchLoadableCollections == null ) { + return keys; + } + int i = 1; int end = -1; boolean checkForEnd = false; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadeStyles.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadeStyles.java index d0703ac60640a48784494c36942b820c7fe2a450..679be575aed98a7308960bfdee0de6f239e1e32b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadeStyles.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadeStyles.java @@ -17,7 +17,8 @@ /** * @author Steve Ebersole */ -public class CascadeStyles { +public final class CascadeStyles { + private static final Logger log = Logger.getLogger( CascadeStyles.class ); /** diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java index 7839d99dd8cd1c58a738baae6fc5558258ff57ec..aa332bf96bf3c423bf9108aac2c32a622d770187 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java @@ -294,7 +294,7 @@ public void cascade( Object anything, boolean isCascadeDeleteEnabled) throws HibernateException { - LOG.tracev( "Cascading to persist: {0}" + entityName ); + LOG.tracev( "Cascading to persist: {0}", entityName ); session.persist( entityName, child, (Map) anything ); } @@ -369,21 +369,20 @@ public void noCascade( int propertyIndex) { if ( propertyType.isEntityType() ) { Object child = persister.getPropertyValue( parent, propertyIndex ); - String childEntityName = ((EntityType) propertyType).getAssociatedEntityName( session.getFactory() ); - if ( child != null && !isInManagedState( child, session ) - && !(child instanceof HibernateProxy) //a proxy cannot be transient and it breaks ForeignKeys.isTransient - && ForeignKeys.isTransient( childEntityName, child, null, session ) ) { - String parentEntiytName = persister.getEntityName(); - String propertyName = persister.getPropertyNames()[propertyIndex]; - throw new TransientPropertyValueException( + && !(child instanceof HibernateProxy) ) { //a proxy cannot be transient and it breaks ForeignKeys.isTransient + final String childEntityName = ((EntityType) propertyType).getAssociatedEntityName(session.getFactory()); + if (ForeignKeys.isTransient(childEntityName, child, null, session)) { + String parentEntityName = persister.getEntityName(); + String propertyName = persister.getPropertyNames()[propertyIndex]; + throw new TransientPropertyValueException( "object references an unsaved transient instance - save the transient instance before flushing", childEntityName, - parentEntiytName, + parentEntityName, propertyName - ); - + ); + } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionKey.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionKey.java index 091539351eaac8e8642738488a7fcf275ec892dd..5db19a612df71682c4cb1c1c7ca4b8a7ca7285fc 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionKey.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionKey.java @@ -27,32 +27,33 @@ public final class CollectionKey implements Serializable { private final Type keyType; private final SessionFactoryImplementor factory; private final int hashCode; - private EntityMode entityMode; public CollectionKey(CollectionPersister persister, Serializable key) { this( persister.getRole(), key, persister.getKeyType(), - persister.getOwnerEntityPersister().getEntityMetamodel().getEntityMode(), persister.getFactory() ); } + /** + * The EntityMode parameter is now ignored. Use the other constructor. + * @deprecated Use {@link #CollectionKey(CollectionPersister, Serializable)} + */ + @Deprecated public CollectionKey(CollectionPersister persister, Serializable key, EntityMode em) { - this( persister.getRole(), key, persister.getKeyType(), em, persister.getFactory() ); + this( persister.getRole(), key, persister.getKeyType(), persister.getFactory() ); } private CollectionKey( String role, Serializable key, Type keyType, - EntityMode entityMode, SessionFactoryImplementor factory) { this.role = role; this.key = key; this.keyType = keyType; - this.entityMode = entityMode; this.factory = factory; //cache the hash-code this.hashCode = generateHashCode(); @@ -65,7 +66,6 @@ private int generateHashCode() { return result; } - public String getRole() { return role; } @@ -112,7 +112,6 @@ public void serialize(ObjectOutputStream oos) throws IOException { oos.writeObject( role ); oos.writeObject( key ); oos.writeObject( keyType ); - oos.writeObject( entityMode.toString() ); } /** @@ -134,7 +133,6 @@ public static CollectionKey deserialize( (String) ois.readObject(), (Serializable) ois.readObject(), (Type) ois.readObject(), - EntityMode.parse( (String) ois.readObject() ), (session == null ? null : session.getFactory()) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntry.java index c6e373a50d53c18117a7f0254c8062eaab0f30f1..0015dc1002dbea64e6b396a6a21b68de16172dfc 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntry.java @@ -21,9 +21,9 @@ * therefore we need to take care of its impact on memory consumption. * * @author Gavin King - * @author Emmanuel Bernard + * @author Emmanuel Bernard * @author Gunnar Morling - * @author Sanne Grinovero + * @author Sanne Grinovero */ public interface EntityEntry { LockMode getLockMode(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntryExtraState.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntryExtraState.java index a7bf427a4dd08e6de387c6d01cf9ca7a7b492a27..035349979edbabf494c139f667451c5564bb0358 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntryExtraState.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntryExtraState.java @@ -10,7 +10,7 @@ /** * Navigation methods for extra state objects attached to {@link org.hibernate.engine.spi.EntityEntry}. * - * @author Emmanuel Bernard + * @author Emmanuel Bernard */ public interface EntityEntryExtraState { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java index 8caa5b77670a01c83362e308029123b96e89ea7a..52a9cd870671c02ec693aecdd43d6cad68d3c382 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java @@ -74,6 +74,10 @@ public String getEntityName() { return persister.getEntityName(); } + public EntityPersister getPersister() { + return persister; + } + @Override public boolean equals(Object other) { if ( this == other ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java index 3822eb870a0d84deeaf157a4bb1203fe76fad26e..317038ba5516e2e6ae27ea1041137c3e6145afb3 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java @@ -7,6 +7,7 @@ package org.hibernate.engine.spi; import java.io.Serializable; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -38,23 +39,21 @@ public class LoadQueryInfluencers implements Serializable { private final SessionFactoryImplementor sessionFactory; private String internalFetchProfile; - private final Map enabledFilters; - private final Set enabledFetchProfileNames; private EntityGraph fetchGraph; private EntityGraph loadGraph; + //Lazily initialized! + private HashSet enabledFetchProfileNames; + + //Lazily initialized! + private HashMap enabledFilters; + public LoadQueryInfluencers() { this( null ); } public LoadQueryInfluencers(SessionFactoryImplementor sessionFactory) { - this( sessionFactory, new HashMap(), new HashSet() ); - } - - private LoadQueryInfluencers(SessionFactoryImplementor sessionFactory, Map enabledFilters, Set enabledFetchProfileNames) { this.sessionFactory = sessionFactory; - this.enabledFilters = enabledFilters; - this.enabledFetchProfileNames = enabledFetchProfileNames; } public SessionFactoryImplementor getSessionFactory() { @@ -81,16 +80,21 @@ public void setInternalFetchProfile(String internalFetchProfile) { // filter support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public boolean hasEnabledFilters() { - return !enabledFilters.isEmpty(); + return enabledFilters != null && !enabledFilters.isEmpty(); } public Map getEnabledFilters() { - // First, validate all the enabled filters... - //TODO: this implementation has bad performance - for ( Filter filter : enabledFilters.values() ) { - filter.validate(); + if ( enabledFilters == null ) { + return Collections.EMPTY_MAP; + } + else { + // First, validate all the enabled filters... + for ( Filter filter : enabledFilters.values() ) { + //TODO: this implementation has bad performance + filter.validate(); + } + return enabledFilters; } - return enabledFilters; } /** @@ -98,25 +102,43 @@ public Map getEnabledFilters() { * @return an unmodifiable Set of enabled filter names. */ public Set getEnabledFilterNames() { - return java.util.Collections.unmodifiableSet( enabledFilters.keySet() ); + if ( enabledFilters == null ) { + return Collections.EMPTY_SET; + } + else { + return java.util.Collections.unmodifiableSet( enabledFilters.keySet() ); + } } public Filter getEnabledFilter(String filterName) { - return enabledFilters.get( filterName ); + if ( enabledFilters == null ) { + return null; + } + else { + return enabledFilters.get( filterName ); + } } public Filter enableFilter(String filterName) { FilterImpl filter = new FilterImpl( sessionFactory.getFilterDefinition( filterName ) ); + if ( enabledFilters == null ) { + this.enabledFilters = new HashMap<>(); + } enabledFilters.put( filterName, filter ); return filter; } public void disableFilter(String filterName) { - enabledFilters.remove( filterName ); + if ( enabledFilters != null ) { + enabledFilters.remove( filterName ); + } } public Object getFilterParameterValue(String filterParameterName) { final String[] parsed = parseFilterParameterName( filterParameterName ); + if ( enabledFilters == null ) { + throw new IllegalArgumentException( "Filter [" + parsed[0] + "] currently not enabled" ); + } final FilterImpl filter = (FilterImpl) enabledFilters.get( parsed[0] ); if ( filter == null ) { throw new IllegalArgumentException( "Filter [" + parsed[0] + "] currently not enabled" ); @@ -154,11 +176,16 @@ public static String[] parseFilterParameterName(String filterParameterName) { // fetch profile support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public boolean hasEnabledFetchProfiles() { - return !enabledFetchProfileNames.isEmpty(); + return enabledFetchProfileNames != null && !enabledFetchProfileNames.isEmpty(); } public Set getEnabledFetchProfileNames() { - return enabledFetchProfileNames; + if ( enabledFetchProfileNames == null ) { + return Collections.EMPTY_SET; + } + else { + return enabledFetchProfileNames; + } } private void checkFetchProfileName(String name) { @@ -169,17 +196,22 @@ private void checkFetchProfileName(String name) { public boolean isFetchProfileEnabled(String name) throws UnknownProfileException { checkFetchProfileName( name ); - return enabledFetchProfileNames.contains( name ); + return enabledFetchProfileNames != null && enabledFetchProfileNames.contains( name ); } public void enableFetchProfile(String name) throws UnknownProfileException { checkFetchProfileName( name ); + if ( enabledFetchProfileNames == null ) { + this.enabledFetchProfileNames = new HashSet<>(); + } enabledFetchProfileNames.add( name ); } public void disableFetchProfile(String name) throws UnknownProfileException { checkFetchProfileName( name ); - enabledFetchProfileNames.remove( name ); + if ( enabledFetchProfileNames != null ) { + enabledFetchProfileNames.remove( name ); + } } public EntityGraph getFetchGraph() { 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 c0746bf6d38e3564645e11879020ad966c428659..b3a4589aa179deb3a87c1332b997e416fd3412a8 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 @@ -344,6 +344,12 @@ Object proxyFor(EntityPersister persister, EntityKey key, Object impl) */ Object proxyFor(Object impl) throws HibernateException; + /** + * Cross between {@link #addEntity(EntityKey, Object)} and {@link #addProxy(EntityKey, Object)} + * for use with enhancement-as-proxy + */ + void addEnhancedProxy(EntityKey key, PersistentAttributeInterceptable entity); + /** * Get the entity that owns this persistent collection */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java index 38bf8090beac595931cecb0b8c7025ece7451608..61e80eca6da2e563291469a2a7189e9e699896de 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistentAttributeInterceptor.java @@ -6,47 +6,76 @@ */ package org.hibernate.engine.spi; +import java.util.Collections; +import java.util.Set; + +import org.hibernate.Incubating; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.InterceptorImplementor; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; /** + * The base contract for interceptors that can be injected into + * enhanced entities for the purpose of intercepting attribute access + * * @author Steve Ebersole + * + * @see PersistentAttributeInterceptable */ +@Incubating +@SuppressWarnings("unused") public interface PersistentAttributeInterceptor extends InterceptorImplementor { + boolean readBoolean(Object obj, String name, boolean oldValue); - public boolean readBoolean(Object obj, String name, boolean oldValue); + boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue); - public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue); + byte readByte(Object obj, String name, byte oldValue); - public byte readByte(Object obj, String name, byte oldValue); + byte writeByte(Object obj, String name, byte oldValue, byte newValue); - public byte writeByte(Object obj, String name, byte oldValue, byte newValue); + char readChar(Object obj, String name, char oldValue); - public char readChar(Object obj, String name, char oldValue); + char writeChar(Object obj, String name, char oldValue, char newValue); - public char writeChar(Object obj, String name, char oldValue, char newValue); + short readShort(Object obj, String name, short oldValue); - public short readShort(Object obj, String name, short oldValue); + short writeShort(Object obj, String name, short oldValue, short newValue); - public short writeShort(Object obj, String name, short oldValue, short newValue); + int readInt(Object obj, String name, int oldValue); - public int readInt(Object obj, String name, int oldValue); + int writeInt(Object obj, String name, int oldValue, int newValue); - public int writeInt(Object obj, String name, int oldValue, int newValue); + float readFloat(Object obj, String name, float oldValue); - public float readFloat(Object obj, String name, float oldValue); + float writeFloat(Object obj, String name, float oldValue, float newValue); - public float writeFloat(Object obj, String name, float oldValue, float newValue); + double readDouble(Object obj, String name, double oldValue); - public double readDouble(Object obj, String name, double oldValue); + double writeDouble(Object obj, String name, double oldValue, double newValue); - public double writeDouble(Object obj, String name, double oldValue, double newValue); + long readLong(Object obj, String name, long oldValue); - public long readLong(Object obj, String name, long oldValue); + long writeLong(Object obj, String name, long oldValue, long newValue); - public long writeLong(Object obj, String name, long oldValue, long newValue); + Object readObject(Object obj, String name, Object oldValue); - public Object readObject(Object obj, String name, Object oldValue); + Object writeObject(Object obj, String name, Object oldValue, Object newValue); - public Object writeObject(Object obj, String name, Object oldValue, Object newValue); + /** + * @deprecated Just as the method it overrides. Interceptors that deal with + * lazy state should implement {@link BytecodeLazyAttributeInterceptor} + */ + @Deprecated + @Override + default Set getInitializedLazyAttributeNames() { + return Collections.emptySet(); + } + /** + * @deprecated Just as the method it overrides. Interceptors that deal with + * lazy state should implement {@link BytecodeLazyAttributeInterceptor} + */ + @Override + @Deprecated + default void attributeInitialized(String name) { + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java index 4ec317d20c59c6aa19f4a6f1270fb8d6d9688095..1b34a49c622a7bfadfdc42ebc1634b7e73722cc6 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java @@ -76,7 +76,7 @@ * API so that only some methods need to be overridden * (Used by Hibernate Search). * - * @author Sanne Grinovero (C) 2012 Red Hat Inc. + * @author Sanne Grinovero (C) 2012 Red Hat Inc. */ @SuppressWarnings("deprecation") public class SessionDelegatorBaseImpl implements SessionImplementor { @@ -107,7 +107,7 @@ public SessionDelegatorBaseImpl(SessionImplementor delegate) { } /** - * Returns the underlying delegate. Be careful that is has a different behavior from the {@link #getDelegate()} + * Returns the underlying delegate. Be careful that it has a different behavior from the {@link #getDelegate()} * method coming from the EntityManager interface which returns the current session. * * @see SessionDelegatorBaseImpl#getDelegate() @@ -156,6 +156,11 @@ public boolean isTransactionInProgress() { return delegate.isTransactionInProgress(); } + @Override + public void checkTransactionNeededForUpdateOperation(String exceptionMessage) { + delegate.checkTransactionNeededForUpdateOperation( exceptionMessage ); + } + @Override public LockOptions getLockRequest(LockModeType lockModeType, Map properties) { return delegate.getLockRequest( lockModeType, properties ); @@ -617,7 +622,7 @@ public StoredProcedureQuery createNamedStoredProcedureQuery(String name) { @Override public StoredProcedureQuery createStoredProcedureQuery(String procedureName) { - return delegate.createNamedStoredProcedureQuery( procedureName ); + return delegate.createStoredProcedureQuery( procedureName ); } @Override 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 dfb934d1e891fe61013e561e188ff2272aede5bf..b3f6593d89b01ee2fdb10104fdc56d6821fce022 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 @@ -277,16 +277,11 @@ default ResultSetMappingDefinition getResultSetMapping(String name) { * * @return The dialect * - * @deprecated (since 5.2) instead, use this factory's {{@link #getServiceRegistry()}} -> - * {@link JdbcServices#getDialect()} + * @deprecated (since 5.2) instead, use {@link JdbcServices#getDialect()} */ @Deprecated default Dialect getDialect() { - if ( getServiceRegistry() == null ) { - throw new IllegalStateException( "Cannot determine dialect because serviceRegistry is null." ); - } - - return getServiceRegistry().getService( JdbcServices.class ).getDialect(); + return getJdbcServices().getDialect(); } /** @@ -299,7 +294,7 @@ default Dialect getDialect() { */ @Deprecated default SQLExceptionConverter getSQLExceptionConverter() { - return getServiceRegistry().getService( JdbcServices.class ).getSqlExceptionHelper().getSqlExceptionConverter(); + return getJdbcServices().getSqlExceptionHelper().getSqlExceptionConverter(); } /** @@ -312,7 +307,7 @@ default SQLExceptionConverter getSQLExceptionConverter() { */ @Deprecated default SqlExceptionHelper getSQLExceptionHelper() { - return getServiceRegistry().getService( JdbcServices.class ).getSqlExceptionHelper(); + return getJdbcServices().getSqlExceptionHelper(); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java index c6b1a5c4f7e33d9beff91107fe411b845be05b2b..52e6f90e943850f1fdcd2ffef0f6785e3a26cf14 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.UUID; import javax.persistence.FlushModeType; +import javax.persistence.TransactionRequiredException; import org.hibernate.CacheMode; import org.hibernate.Criteria; @@ -112,7 +113,7 @@ public interface SharedSessionContractImplementor /** * Checks whether the session is closed. Provided separately from * {@link #isOpen()} as this method does not attempt any JTA synchronization - * registration, where as {@link #isOpen()} does; which makes this one + * registration, whereas {@link #isOpen()} does; which makes this one * nicer to use for most internal purposes. * * @return {@code true} if the session is closed; {@code false} otherwise. @@ -161,7 +162,7 @@ default void checkOpen() { long getTransactionStartTimestamp(); /** - * @deprecated (since 5.3) Use + * @deprecated (since 5.3) Use {@link #getTransactionStartTimestamp()} instead. */ @Deprecated default long getTimestamp() { @@ -180,6 +181,18 @@ default long getTimestamp() { */ boolean isTransactionInProgress(); + /** + * Check if an active Transaction is necessary for the update operation to be executed. + * If an active Transaction is necessary but it is not then a TransactionRequiredException is raised. + * + * @param exceptionMessage the message to use for the TransactionRequiredException + */ + default void checkTransactionNeededForUpdateOperation(String exceptionMessage) { + if ( !isTransactionInProgress() ) { + throw new TransactionRequiredException( exceptionMessage ); + } + } + /** * Provides access to the underlying transaction or creates a new transaction if * one does not already exist or is active. This is primarily for internal or @@ -239,6 +252,7 @@ Object internalLoad(String entityName, Serializable id, boolean eager, boolean n */ Object immediateLoad(String entityName, Serializable id) throws HibernateException; + /** * Execute a find() query */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java index 33d03f6f639d2ac1c5d4c536f30fc0af1375c5bf..42879d569e52893c665e298720c81ab3495e8de3 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java @@ -72,7 +72,13 @@ public void begin() { // per-JPA if ( isActive() ) { - throw new IllegalStateException( "Transaction already active" ); + if ( jpaCompliance.isJpaTransactionComplianceEnabled() + || !transactionCoordinator.getTransactionCoordinatorBuilder().isJta() ) { + throw new IllegalStateException( "Transaction already active" ); + } + else { + return; + } } LOG.debug( "begin" ); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java index 451da405168f3ca8a9815fba154033e8813c7b76..6fd35ae0fb0f3f1ba4602a018bbc21687bff0b8d 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java @@ -193,7 +193,7 @@ private void prepareCollectionFlushes(PersistenceContext persistenceContext) thr LOG.debug( "Dirty checking collections" ); for ( Map.Entry entry : - IdentityMap.concurrentEntries( (Map) persistenceContext.getCollectionEntries() )) { + IdentityMap.concurrentEntries( (Map) persistenceContext.getCollectionEntries() ) ) { entry.getValue().preFlush( entry.getKey() ); } } @@ -269,7 +269,7 @@ private int flushCollections(final EventSource session, final PersistenceContext ActionQueue actionQueue = session.getActionQueue(); for ( Map.Entry me : - IdentityMap.concurrentEntries( (Map) persistenceContext.getCollectionEntries() )) { + IdentityMap.concurrentEntries( (Map) persistenceContext.getCollectionEntries() ) ) { PersistentCollection coll = me.getKey(); CollectionEntry ce = me.getValue(); 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 120f6c4e0d929662a82c642fe67d2ce62c776df5..76ce2d161164b6e3d83c6c26ec47839f9667fad0 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 @@ -9,7 +9,6 @@ import java.io.Serializable; import java.util.Map; -import org.hibernate.FlushMode; import org.hibernate.LockMode; import org.hibernate.NonUniqueObjectException; import org.hibernate.action.internal.AbstractEntityInsertAction; @@ -39,9 +38,6 @@ import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; -import static org.hibernate.FlushMode.COMMIT; -import static org.hibernate.FlushMode.MANUAL; - /** * A convenience base class for listeners responding to save events. * @@ -248,7 +244,8 @@ protected Serializable performSaveOrReplicate( Serializable id = key == null ? null : key.getIdentifier(); - boolean shouldDelayIdentityInserts = shouldDelayIdentityInserts( requiresImmediateIdAccess, source ); + boolean inTrx = source.isTransactionInProgress(); + boolean shouldDelayIdentityInserts = !inTrx && !requiresImmediateIdAccess; // Put a placeholder in entries, so we don't recurse back and try to save() the // same object again. QUESTION: should this be done before onSave() is called? @@ -320,30 +317,6 @@ protected Serializable performSaveOrReplicate( return id; } - private static boolean shouldDelayIdentityInserts(boolean requiresImmediateIdAccess, EventSource source) { - return shouldDelayIdentityInserts( requiresImmediateIdAccess, isPartOfTransaction( source ), source.getHibernateFlushMode() ); - } - - private static boolean shouldDelayIdentityInserts( - boolean requiresImmediateIdAccess, - boolean partOfTransaction, - FlushMode flushMode) { - if ( requiresImmediateIdAccess ) { - // todo : make this configurable? as a way to support this behavior with Session#save etc - return false; - } - - // otherwise we should delay the IDENTITY insertions if either: - // 1) we are not part of a transaction - // 2) we are in FlushMode MANUAL or COMMIT (not AUTO nor ALWAYS) - return !partOfTransaction || flushMode == MANUAL || flushMode == COMMIT; - - } - - private static boolean isPartOfTransaction(EventSource source) { - return source.isTransactionInProgress() && source.getTransactionCoordinator().isJoined(); - } - private AbstractEntityInsertAction addInsertAction( Object[] values, Serializable id, @@ -390,7 +363,7 @@ protected boolean visitCollectionsBeforeSave( Object[] values, Type[] types, EventSource source) { - WrapVisitor visitor = new WrapVisitor( source ); + WrapVisitor visitor = new WrapVisitor( entity, id, source ); // substitutes into values by side-effect visitor.processEntityPropertyValues( values, types ); return visitor.isSubstitutionRequired(); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java index 34dcfbf9ce454d143856d2d2609a5f3239fde815..04e492cd3b14cadea395bba2cd6d1c0b63192709 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java @@ -8,6 +8,7 @@ import org.hibernate.FlushMode; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.ActionQueue; import org.hibernate.event.spi.AutoFlushEvent; import org.hibernate.event.spi.AutoFlushEventListener; import org.hibernate.event.spi.EventSource; @@ -40,10 +41,12 @@ public void onAutoFlush(AutoFlushEvent event) throws HibernateException { if ( flushMightBeNeeded(source) ) { // Need to get the number of collection removals before flushing to executions // (because flushing to executions can add collection removal actions to the action queue). - final int oldSize = source.getActionQueue().numberOfCollectionRemovals(); - flushEverythingToExecutions(event); + final ActionQueue actionQueue = source.getActionQueue(); + final int oldSize = actionQueue.numberOfCollectionRemovals(); + flushEverythingToExecutions( event ); if ( flushIsReallyNeeded(event, source) ) { LOG.trace( "Need to execute flush" ); + event.setFlushRequired( true ); // note: performExecutions() clears all collectionXxxxtion // collections (the collection actions) in the session @@ -58,10 +61,9 @@ public void onAutoFlush(AutoFlushEvent event) throws HibernateException { } else { LOG.trace( "Don't need to execute flush" ); - source.getActionQueue().clearFromFlushNeededCheck( oldSize ); + event.setFlushRequired( false ); + actionQueue.clearFromFlushNeededCheck( oldSize ); } - - event.setFlushRequired( flushIsReallyNeeded( event, source ) ); } } finally { 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 d6c17287f00b06d0090347ef1fb5f2fbbf726424..f04b7504b01008f0774a41ffd3c95d888141a827 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 @@ -131,6 +131,7 @@ public void onDelete(DeleteEvent event, Set transientEntities) throws HibernateE persister, false ); + persister.afterReassociate( entity, source ); } else { LOG.trace( "Deleting a persistent instance" ); @@ -284,8 +285,7 @@ protected final void deleteEntity( cascadeBeforeDelete( session, persister, entity, entityEntry, transientEntities ); - new ForeignKeys.Nullifier( entity, true, false, session ) - .nullifyTransientReferences( entityEntry.getDeletedState(), propTypes ); + new ForeignKeys.Nullifier( entity, true, false, session, persister ).nullifyTransientReferences( entityEntry.getDeletedState() ); new Nullability( session ).checkNullability( entityEntry.getDeletedState(), persister, Nullability.NullabilityCheckType.DELETE ); persistenceContext.getNullifiableEntityKeys().add( key ); 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 1abf516c460af1b43b768865d4673d3f9b273a95..25128a69a4e2c72adac3011b14f357528df1799e 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 @@ -54,7 +54,7 @@ public void onEvict(EvictEvent event) throws HibernateException { if ( object instanceof HibernateProxy ) { final LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer(); - final Serializable id = li.getIdentifier(); + final Serializable id = li.getInternalIdentifier(); if ( id == null ) { throw new IllegalArgumentException( "Could not determine identifier of proxy passed to evict()" ); } 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 93694ebae0e52e93166fcfe546d32e8c5f088c20..0ac4059860ee060b86e0e0c10a6e07548799cffe 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 @@ -17,10 +17,13 @@ import org.hibernate.action.internal.DelayedPostInsertIdentifier; import org.hibernate.action.internal.EntityUpdateAction; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.engine.internal.Nullability; import org.hibernate.engine.internal.Versioning; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; @@ -82,14 +85,24 @@ public void checkId(Object object, EntityPersister persister, Serializable id, S private void checkNaturalId( EntityPersister persister, + Object entity, EntityEntry entry, Object[] current, Object[] loaded, SessionImplementor session) { + if ( entity instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + // EARLY EXIT!!! + // nothing to check - the entity is an un-initialized enhancement-as-proxy reference + return; + } + } + if ( persister.hasNaturalIdentifier() && entry.getStatus() != Status.READ_ONLY ) { if ( !persister.getEntityMetamodel().hasImmutableNaturalId() ) { - // SHORT-CUT: if the natural id is mutable (!immutable), no need to do the below checks // EARLY EXIT!!! + // the natural id is mutable (!immutable), no need to do the below checks return; } @@ -112,7 +125,7 @@ private void checkNaturalId( if ( !propertyType.isEqual( current[naturalIdentifierPropertyIndex], snapshot[i] ) ) { throw new HibernateException( String.format( - "An immutable natural identifier of entity %s was altered from %s to %s", + "An immutable natural identifier of entity %s was altered from `%s` to `%s`", persister.getEntityName(), propertyTypes[naturalIdentifierPropertyIndex].toLoggableString( snapshot[i], @@ -188,7 +201,7 @@ else if ( !mightBeDirty && loadedState != null ) { // grab its current state values = persister.getPropertyValues( entity ); - checkNaturalId( persister, entry, values, loadedState, session ); + checkNaturalId( persister, entity, entry, values, loadedState, session ); } return values; } @@ -332,15 +345,8 @@ protected boolean handleInterception(FlushEntityEvent event) { final boolean intercepted = invokeInterceptor( session, entity, entry, values, persister ); //now we might need to recalculate the dirtyProperties array - if ( intercepted && event.isDirtyCheckPossible() && !event.isDirtyCheckHandledByInterceptor() ) { - int[] dirtyProperties; - if ( event.hasDatabaseSnapshot() ) { - dirtyProperties = persister.findModified( event.getDatabaseSnapshot(), values, entity, session ); - } - else { - dirtyProperties = persister.findDirty( values, entry.getLoadedState(), entity, session ); - } - event.setDirtyProperties( dirtyProperties ); + if ( intercepted && event.isDirtyCheckPossible() ) { + dirtyCheck( event ); } return intercepted; @@ -560,7 +566,8 @@ public void doDirtyChecking(CustomEntityDirtinessStrategy.AttributeChecker attri event.setDatabaseSnapshot( null ); final boolean interceptorHandledDirtyCheck; - boolean cannotDirtyCheck; + //The dirty check is considered possible unless proven otherwise (see below) + boolean dirtyCheckPossible = true; if ( dirtyProperties == null ) { // Interceptor returned null, so do the dirtycheck ourself, if possible @@ -569,8 +576,8 @@ public void doDirtyChecking(CustomEntityDirtinessStrategy.AttributeChecker attri interceptorHandledDirtyCheck = false; // object loaded by update() - cannotDirtyCheck = loadedState == null; - if ( !cannotDirtyCheck ) { + dirtyCheckPossible = loadedState != null; + if ( dirtyCheckPossible ) { // dirty check against the usual snapshot of the entity dirtyProperties = persister.findDirty( values, loadedState, entity, session ); } @@ -592,14 +599,14 @@ else if ( entry.getStatus() == Status.DELETED && !event.getEntityEntry().isModif // - dirtyProperties will only contain properties that refer to transient entities final Object[] currentState = persister.getPropertyValues( event.getEntity() ); dirtyProperties = persister.findDirty( entry.getDeletedState(), currentState, entity, session ); - cannotDirtyCheck = false; + dirtyCheckPossible = true; } else { // dirty check against the database snapshot, if possible/necessary final Object[] databaseSnapshot = getDatabaseSnapshot( session, persister, id ); if ( databaseSnapshot != null ) { dirtyProperties = persister.findModified( databaseSnapshot, values, entity, session ); - cannotDirtyCheck = false; + dirtyCheckPossible = true; event.setDatabaseSnapshot( databaseSnapshot ); } } @@ -609,8 +616,7 @@ else if ( entry.getStatus() == Status.DELETED && !event.getEntityEntry().isModif } } else { - // the Interceptor handled the dirty checking - cannotDirtyCheck = false; + // either the Interceptor, the bytecode enhancement or a custom dirtiness strategy handled the dirty checking interceptorHandledDirtyCheck = true; } @@ -618,7 +624,7 @@ else if ( entry.getStatus() == Status.DELETED && !event.getEntityEntry().isModif event.setDirtyProperties( dirtyProperties ); event.setDirtyCheckHandledByInterceptor( interceptorHandledDirtyCheck ); - event.setDirtyCheckPossible( !cannotDirtyCheck ); + event.setDirtyCheckPossible( dirtyCheckPossible ); } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultInitializeCollectionEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultInitializeCollectionEventListener.java index d3bf556d737ef029a6a61015d9c3247e419710e9..17d0676338446cdadffb5be99a3098ff9296b76d 100755 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultInitializeCollectionEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultInitializeCollectionEventListener.java @@ -103,7 +103,7 @@ private boolean initializeCollectionFromCache( PersistentCollection collection, SessionImplementor source) { - if ( !source.getLoadQueryInfluencers().getEnabledFilters().isEmpty() + if ( source.getLoadQueryInfluencers().hasEnabledFilters() && persister.isAffectedByEnabledFilters( source ) ) { LOG.trace( "Disregarding cached version (if any) of collection due to enabled filters" ); return false; 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 855e5e8a549eb50b4ac6490306d1a94c73ffb334..aec1ebc10c915bbdebd6a2de6f903ef4b4be2cf2 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 @@ -14,6 +14,7 @@ import org.hibernate.PersistentObjectException; import org.hibernate.TypeMismatchException; import org.hibernate.WrongClassException; +import org.hibernate.action.internal.DelayedPostInsertIdentifier; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.cache.spi.entry.CacheEntry; @@ -30,7 +31,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; -import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventType; @@ -45,6 +45,9 @@ import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.stat.internal.StatsHelper; +import org.hibernate.stat.spi.StatisticsImplementor; +import org.hibernate.tuple.IdentifierProperty; +import org.hibernate.tuple.entity.EntityMetamodel; import org.hibernate.type.EmbeddedComponentType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; @@ -69,8 +72,6 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i * Handle the given load event. * * @param event The load event to be handled. - * - * @throws HibernateException */ public void onLoad( final LoadEvent event, @@ -83,24 +84,27 @@ public void onLoad( } final Class idClass = persister.getIdentifierType().getReturnedClass(); - if ( idClass != null && !idClass.isInstance( event.getEntityId() ) ) { + if ( idClass != null && + !idClass.isInstance( event.getEntityId() ) && + !DelayedPostInsertIdentifier.class.isInstance( event.getEntityId() ) ) { checkIdClass( persister, event, loadType, idClass ); } doOnLoad( persister, event, loadType ); } - protected EntityPersister getPersister( final LoadEvent event ) { - if ( event.getInstanceToLoad() != null ) { + protected EntityPersister getPersister(final LoadEvent event) { + final Object instanceToLoad = event.getInstanceToLoad(); + if ( instanceToLoad != null ) { //the load() which takes an entity does not pass an entityName - event.setEntityClassName( event.getInstanceToLoad().getClass().getName() ); + event.setEntityClassName( instanceToLoad.getClass().getName() ); return event.getSession().getEntityPersister( null, - event.getInstanceToLoad() + instanceToLoad ); } else { - return event.getSession().getFactory().getEntityPersister( event.getEntityClassName() ); + return event.getSession().getFactory().getMetamodel().entityPersister( event.getEntityClassName() ); } } @@ -110,7 +114,8 @@ private void doOnLoad( final LoadEventListener.LoadType loadType) { try { - final EntityKey keyToLoad = event.getSession().generateEntityKey( event.getEntityId(), persister ); + final EventSource session = event.getSession(); + final EntityKey keyToLoad = session.generateEntityKey( event.getEntityId(), persister ); if ( loadType.isNakedEntityReturned() ) { //do not return a proxy! //(this option indicates we are initializing a proxy) @@ -122,7 +127,7 @@ private void doOnLoad( event.setResult( proxyOrLoad( event, persister, keyToLoad, loadType ) ); } else { - event.setResult( lockAndLoad( event, persister, keyToLoad, loadType, event.getSession() ) ); + event.setResult( lockAndLoad( event, persister, keyToLoad, loadType, session ) ); } } } @@ -140,14 +145,16 @@ private void checkIdClass( // we may have the kooky jpa requirement of allowing find-by-id where // "id" is the "simple pk value" of a dependent objects parent. This // is part of its generally goofy "derived identity" "feature" - if ( persister.getEntityMetamodel().getIdentifierProperty().isEmbedded() ) { + final IdentifierProperty identifierProperty = persister.getEntityMetamodel().getIdentifierProperty(); + if ( identifierProperty.isEmbedded() ) { final EmbeddedComponentType dependentIdType = - (EmbeddedComponentType) persister.getEntityMetamodel().getIdentifierProperty().getType(); + (EmbeddedComponentType) identifierProperty.getType(); if ( dependentIdType.getSubtypes().length == 1 ) { final Type singleSubType = dependentIdType.getSubtypes()[0]; if ( singleSubType.isEntityType() ) { final EntityType dependentParentType = (EntityType) singleSubType; - final Type dependentParentIdType = dependentParentType.getIdentifierOrUniqueKeyType( event.getSession().getFactory() ); + final SessionFactoryImplementor factory = event.getSession().getFactory(); + final Type dependentParentIdType = dependentParentType.getIdentifierOrUniqueKeyType( factory ); if ( dependentParentIdType.getReturnedClass().isInstance( event.getEntityId() ) ) { // yep that's what we have... loadByDerivedIdentitySimplePkValue( @@ -155,7 +162,7 @@ private void checkIdClass( loadType, persister, dependentIdType, - event.getSession().getFactory().getEntityPersister( dependentParentType.getAssociatedEntityName() ) + factory.getMetamodel().entityPersister( dependentParentType.getAssociatedEntityName() ) ); return; } @@ -174,12 +181,13 @@ private void loadByDerivedIdentitySimplePkValue( EntityPersister dependentPersister, EmbeddedComponentType dependentIdType, EntityPersister parentPersister) { - final EntityKey parentEntityKey = event.getSession().generateEntityKey( event.getEntityId(), parentPersister ); + final EventSource session = event.getSession(); + final EntityKey parentEntityKey = session.generateEntityKey( event.getEntityId(), parentPersister ); final Object parent = doLoad( event, parentPersister, parentEntityKey, options ); - final Serializable dependent = (Serializable) dependentIdType.instantiate( parent, event.getSession() ); + final Serializable dependent = (Serializable) dependentIdType.instantiate( parent, session ); dependentIdType.setPropertyValues( dependent, new Object[] {parent}, dependentPersister.getEntityMode() ); - final EntityKey dependentEntityKey = event.getSession().generateEntityKey( dependent, dependentPersister ); + final EntityKey dependentEntityKey = session.generateEntityKey( dependent, dependentPersister ); event.setEntityId( dependent ); event.setResult( doLoad( event, dependentPersister, dependentEntityKey, options ) ); @@ -194,8 +202,6 @@ private void loadByDerivedIdentitySimplePkValue( * @param options The defined load options * * @return The loaded entity. - * - * @throws HibernateException */ private Object load( final LoadEvent event, @@ -251,34 +257,100 @@ private Object proxyOrLoad( final EntityKey keyToLoad, final LoadEventListener.LoadType options) { + final EventSource session = event.getSession(); + final SessionFactoryImplementor factory = session.getFactory(); + final boolean traceEnabled = LOG.isTraceEnabled(); + if ( traceEnabled ) { LOG.tracev( "Loading entity: {0}", - MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() ) + MessageHelper.infoString( persister, event.getEntityId(), factory ) ); } - // this class has no proxies (so do a shortcut) - if ( !persister.hasProxy() ) { - return load( event, persister, keyToLoad, options ); - } + final PersistenceContext persistenceContext = session.getPersistenceContext(); + + final boolean allowBytecodeProxy = factory + .getSessionFactoryOptions() + .isEnhancementAsProxyEnabled(); + + final EntityMetamodel entityMetamodel = persister.getEntityMetamodel(); + final boolean entityHasHibernateProxyFactory = persister.getEntityMetamodel() + .getTuplizer() + .getProxyFactory() != null; + + // Check for the case where we can use the entity itself as a proxy + if ( options.isAllowProxyCreation() + && allowBytecodeProxy + && persister.getEntityMetamodel().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { + // if there is already a managed entity instance associated with the PC, return it + final Object managed = persistenceContext.getEntity( keyToLoad ); + if ( managed != null ) { + if ( options.isCheckDeleted() ) { + final EntityEntry entry = persistenceContext.getEntry( managed ); + final Status status = entry.getStatus(); + if ( status == Status.DELETED || status == Status.GONE ) { + return null; + } + } + return managed; + } - final PersistenceContext persistenceContext = event.getSession().getPersistenceContext(); + // if the entity defines a HibernateProxy factory, see if there is an + // existing proxy associated with the PC - and if so, use it + if ( entityHasHibernateProxyFactory ) { + final Object proxy = persistenceContext.getProxy( keyToLoad ); - // look for a proxy - Object proxy = persistenceContext.getProxy( keyToLoad ); - if ( proxy != null ) { - return returnNarrowedProxy( event, persister, keyToLoad, options, persistenceContext, proxy ); + if ( proxy != null ) { + if( traceEnabled ) { + LOG.trace( "Entity proxy found in session cache" ); + } + + if ( LOG.isDebugEnabled() && ( (HibernateProxy) proxy ).getHibernateLazyInitializer().isUnwrap() ) { + LOG.debug( "Ignoring NO_PROXY to honor laziness" ); + } + + return persistenceContext.narrowProxy( proxy, persister, keyToLoad, null ); + } + + // specialized handling for entities with subclasses with a HibernateProxy factory + if ( entityMetamodel.hasSubclasses() ) { + // entities with subclasses that define a ProxyFactory can create a HibernateProxy + return createProxy( event, persister, keyToLoad, persistenceContext ); + } + } + if ( !entityMetamodel.hasSubclasses() ) { + if ( keyToLoad.isBatchLoadable() ) { + // Add a batch-fetch entry into the queue for this entity + persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad ); + } + + // This is the crux of HHH-11147 + // create the (uninitialized) entity instance - has only id set + return persister.getBytecodeEnhancementMetadata().createEnhancedProxy( keyToLoad, true, session ); + } + // If we get here, then the entity class has subclasses and there is no HibernateProxy factory. + // The entity will get loaded below. } + else { + if ( persister.hasProxy() ) { + // look for a proxy + Object proxy = persistenceContext.getProxy( keyToLoad ); + if ( proxy != null ) { + return returnNarrowedProxy( event, persister, keyToLoad, options, persistenceContext, proxy ); + } - if ( options.isAllowProxyCreation() ) { - return createProxyIfNecessary( event, persister, keyToLoad, options, persistenceContext ); + if ( options.isAllowProxyCreation() ) { + return createProxyIfNecessary( event, persister, keyToLoad, options, persistenceContext ); + } + } } // return a newly loaded object return load( event, persister, keyToLoad, options ); } + /** * Given a proxy, initialize it and/or narrow it provided either * is necessary. @@ -302,20 +374,31 @@ private Object returnNarrowedProxy( if ( traceEnabled ) { LOG.trace( "Entity proxy found in session cache" ); } + LazyInitializer li = ( (HibernateProxy) proxy ).getHibernateLazyInitializer(); + if ( li.isUnwrap() ) { return li.getImplementation(); } + Object impl = null; if ( !options.isAllowProxyCreation() ) { impl = load( event, persister, keyToLoad, options ); if ( impl == null ) { - event.getSession() - .getFactory() - .getEntityNotFoundDelegate() - .handleEntityNotFound( persister.getEntityName(), keyToLoad.getIdentifier() ); + if ( options == LoadEventListener.INTERNAL_LOAD_NULLABLE ) { + // The proxy is for a non-existing association mapped as @NotFound. + // Don't throw an exeption; just return null. + return null; + } + else { + event.getSession() + .getFactory() + .getEntityNotFoundDelegate() + .handleEntityNotFound( persister.getEntityName(), keyToLoad.getIdentifier() ); + } } } + return persistenceContext.narrowProxy( proxy, persister, keyToLoad, impl ); } @@ -339,6 +422,7 @@ private Object createProxyIfNecessary( final LoadEventListener.LoadType options, final PersistenceContext persistenceContext) { Object existing = persistenceContext.getEntity( keyToLoad ); + final boolean traceEnabled = LOG.isTraceEnabled(); if ( existing != null ) { // return existing object or initialized proxy (unless deleted) if ( traceEnabled ) { @@ -356,6 +440,14 @@ private Object createProxyIfNecessary( if ( traceEnabled ) { LOG.trace( "Creating new proxy for entity" ); } + return createProxy( event, persister, keyToLoad, persistenceContext ); + } + + private Object createProxy( + LoadEvent event, + EntityPersister persister, + EntityKey keyToLoad, + PersistenceContext persistenceContext) { // return new uninitialized proxy Object proxy = persister.createProxy( event.getEntityId(), event.getSession() ); persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad ); @@ -374,8 +466,6 @@ private Object createProxyIfNecessary( * @param source The originating session * * @return The loaded entity - * - * @throws HibernateException */ private Object lockAndLoad( final LoadEvent event, @@ -386,14 +476,15 @@ private Object lockAndLoad( SoftLock lock = null; final Object ck; final EntityDataAccess cache = persister.getCacheAccessStrategy(); - if ( persister.canWriteToCache() ) { + final boolean canWriteToCache = persister.canWriteToCache(); + if ( canWriteToCache ) { ck = cache.generateCacheKey( event.getEntityId(), persister, source.getFactory(), source.getTenantIdentifier() ); - lock = persister.getCacheAccessStrategy().lockItem( source, ck, null ); + lock = cache.lockItem( source, ck, null ); } else { ck = null; @@ -404,7 +495,7 @@ private Object lockAndLoad( entity = load( event, persister, keyToLoad, options ); } finally { - if ( persister.canWriteToCache() ) { + if ( canWriteToCache ) { cache.unlockItem( source, ck, lock ); } } @@ -432,6 +523,8 @@ private Object doLoad( final EntityKey keyToLoad, final LoadEventListener.LoadType options) { + final EventSource session = event.getSession(); + final boolean traceEnabled = LOG.isTraceEnabled(); if ( traceEnabled ) { LOG.tracev( "Attempting to resolve: {0}", @@ -480,17 +573,18 @@ private Object doLoad( } if ( entity != null && persister.hasNaturalIdentifier() ) { - event.getSession().getPersistenceContext().getNaturalIdHelper().cacheNaturalIdCrossReferenceFromLoad( + final PersistenceContext persistenceContext = session.getPersistenceContext(); + final PersistenceContext.NaturalIdHelper naturalIdHelper = persistenceContext.getNaturalIdHelper(); + naturalIdHelper.cacheNaturalIdCrossReferenceFromLoad( persister, event.getEntityId(), - event.getSession().getPersistenceContext().getNaturalIdHelper().extractNaturalIdValues( + naturalIdHelper.extractNaturalIdValues( entity, persister ) ); } - return entity; } @@ -503,6 +597,7 @@ private Object doLoad( * * @return The object loaded from the datasource, or null if not found. */ + @SuppressWarnings("WeakerAccess") protected Object loadFromDatasource( final LoadEvent event, final EntityPersister persister) { @@ -513,8 +608,9 @@ protected Object loadFromDatasource( event.getSession() ); - if ( event.isAssociationFetch() && event.getSession().getFactory().getStatistics().isStatisticsEnabled() ) { - event.getSession().getFactory().getStatistics().fetchEntity( event.getEntityClassName() ); + final StatisticsImplementor statistics = event.getSession().getFactory().getStatistics(); + if ( event.isAssociationFetch() && statistics.isStatisticsEnabled() ) { + statistics.fetchEntity( event.getEntityClassName() ); } return entity; @@ -642,23 +738,25 @@ private Object getFromSharedCache( final EntityPersister persister, SessionImplementor source ) { final EntityDataAccess cache = persister.getCacheAccessStrategy(); + final SessionFactoryImplementor factory = source.getFactory(); final Object ck = cache.generateCacheKey( event.getEntityId(), persister, - source.getFactory(), + factory, source.getTenantIdentifier() ); final Object ce = CacheHelper.fromSharedCache( source, ck, persister.getCacheAccessStrategy() ); - if ( source.getFactory().getStatistics().isStatisticsEnabled() ) { + final StatisticsImplementor statistics = factory.getStatistics(); + if ( statistics.isStatisticsEnabled() ) { if ( ce == null ) { - source.getFactory().getStatistics().entityCacheMiss( + statistics.entityCacheMiss( StatsHelper.INSTANCE.getRootEntityRole( persister ), cache.getRegion().getName() ); } else { - source.getFactory().getStatistics().entityCacheHit( + statistics.entityCacheHit( StatsHelper.INSTANCE.getRootEntityRole( persister ), cache.getRegion().getName() ); 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 7f587cb585f0cb11ec2c326a207b4300673f2d06..d835188ea94480e428e9ee23250ae26893cd038b 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 @@ -17,12 +17,15 @@ import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.engine.internal.Cascade; import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.spi.CascadingAction; import org.hibernate.engine.spi.CascadingActions; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; @@ -109,26 +112,41 @@ public void onMerge(MergeEvent event, Map copiedAlready) throws HibernateExcepti final EventSource source = event.getSession(); final Object original = event.getOriginal(); - if ( original != null ) { + // NOTE : `original` is the value being merged + if ( original != null ) { final Object entity; if ( original instanceof HibernateProxy ) { LazyInitializer li = ( (HibernateProxy) original ).getHibernateLazyInitializer(); if ( li.isUninitialized() ) { LOG.trace( "Ignoring uninitialized proxy" ); - event.setResult( source.load( li.getEntityName(), li.getIdentifier() ) ); - return; //EARLY EXIT! + event.setResult( source.load( li.getEntityName(), li.getInternalIdentifier() ) ); + //EARLY EXIT! + return; } else { entity = li.getImplementation(); } } + else if ( original instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) original; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) interceptor; + LOG.trace( "Ignoring uninitialized enhanced-proxy" ); + event.setResult( source.load( proxyInterceptor.getEntityName(), (Serializable) proxyInterceptor.getIdentifier() ) ); + //EARLY EXIT! + return; + } + else { + entity = original; + } + } else { entity = original; } - if ( copyCache.containsKey( entity ) && - ( copyCache.isOperatedOn( entity ) ) ) { + if ( copyCache.containsKey( entity ) && ( copyCache.isOperatedOn( entity ) ) ) { LOG.trace( "Already in merge process" ); event.setResult( entity ); } @@ -210,36 +228,50 @@ protected void entityIsTransient(MergeEvent event, Map copyCache) { LOG.trace( "Merging transient instance" ); final Object entity = event.getEntity(); - final EventSource source = event.getSession(); + final EventSource session = event.getSession(); final String entityName = event.getEntityName(); - final EntityPersister persister = source.getEntityPersister( entityName, entity ); + final EntityPersister persister = session.getEntityPersister( entityName, entity ); - final Serializable id = persister.hasIdentifierProperty() ? - persister.getIdentifier( entity, source ) : - null; - if ( copyCache.containsKey( entity ) ) { - persister.setIdentifier( copyCache.get( entity ), id, source ); + final Serializable id = persister.hasIdentifierProperty() + ? persister.getIdentifier( entity, session ) + : null; + + final Object copy; + final Object existingCopy = copyCache.get( entity ); + if ( existingCopy != null ) { + persister.setIdentifier( copyCache.get( entity ), id, session ); + copy = existingCopy; } else { - ( (MergeContext) copyCache ).put( entity, source.instantiate( persister, id ), true ); //before cascade! + copy = session.instantiate( persister, id ); + + //before cascade! + ( (MergeContext) copyCache ).put( entity, copy, true ); } - final Object copy = copyCache.get( entity ); // cascade first, so that all unsaved objects get their // copy created before we actually copy //cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE); - super.cascadeBeforeSave( source, persister, entity, copyCache ); - copyValues( persister, entity, copy, source, copyCache, ForeignKeyDirection.FROM_PARENT ); + super.cascadeBeforeSave( session, persister, entity, copyCache ); + copyValues( persister, entity, copy, session, copyCache, ForeignKeyDirection.FROM_PARENT ); - saveTransientEntity( copy, entityName, event.getRequestedId(), source, copyCache ); + saveTransientEntity( copy, entityName, event.getRequestedId(), session, copyCache ); // cascade first, so that all unsaved objects get their // copy created before we actually copy - super.cascadeAfterSave( source, persister, entity, copyCache ); - copyValues( persister, entity, copy, source, copyCache, ForeignKeyDirection.TO_PARENT ); + super.cascadeAfterSave( session, persister, entity, copyCache ); + copyValues( persister, entity, copy, session, copyCache, ForeignKeyDirection.TO_PARENT ); event.setResult( copy ); + + if ( copy instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) copy; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor == null ) { + persister.getBytecodeEnhancementMetadata().injectInterceptor( copy, id, session ); + } + } } private void saveTransientEntity( @@ -283,11 +315,12 @@ protected void entityIsDetached(MergeEvent event, Map copyCache) { String previousFetchProfile = source.getLoadQueryInfluencers().getInternalFetchProfile(); source.getLoadQueryInfluencers().setInternalFetchProfile( "merge" ); + //we must clone embedded composite identifiers, or //we will get back the same instance that we pass in - final Serializable clonedIdentifier = (Serializable) persister.getIdentifierType() - .deepCopy( id, source.getFactory() ); + final Serializable clonedIdentifier = (Serializable) persister.getIdentifierType().deepCopy( id, source.getFactory() ); final Object result = source.get( entityName, clonedIdentifier ); + source.getLoadQueryInfluencers().setInternalFetchProfile( previousFetchProfile ); if ( result == null ) { @@ -301,9 +334,11 @@ protected void entityIsDetached(MergeEvent event, Map copyCache) { entityIsTransient( event, copyCache ); } else { - ( (MergeContext) copyCache ).put( entity, result, true ); //before cascade! + // before cascade! + ( (MergeContext) copyCache ).put( entity, result, true ); + + final Object target = unproxyManagedForDetachedMerging( entity, result, persister, source ); - final Object target = source.getPersistenceContext().unproxy( result ); if ( target == entity ) { throw new AssertionFailure( "entity was not detached" ); } @@ -334,6 +369,43 @@ else if ( isVersionChanged( entity, source, persister, target ) ) { } + private Object unproxyManagedForDetachedMerging( + Object incoming, + Object managed, + EntityPersister persister, + EventSource source) { + if ( managed instanceof HibernateProxy ) { + return source.getPersistenceContext().unproxy( managed ); + } + + if ( incoming instanceof PersistentAttributeInterceptable + && persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() + && source.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled() ) { + + final PersistentAttributeInterceptor incomingInterceptor = ( (PersistentAttributeInterceptable) incoming ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor managedInterceptor = ( (PersistentAttributeInterceptable) managed ).$$_hibernate_getInterceptor(); + + // todo - do we need to specially handle the case where both `incoming` and `managed` are initialized, but + // with different attributes initialized? + // - for now, assume we do not... + + // if the managed entity is not a proxy, we can just return it + if ( ! ( managedInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) ) { + return managed; + } + + // if the incoming entity is still a proxy there is no need to force initialization of the managed one + if ( incomingInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return managed; + } + + // otherwise, force initialization + persister.initializeEnhancedEntityUsedAsProxy( managed, null, source ); + } + + return managed; + } + private void markInterceptorDirty(final Object entity, final Object target, EntityPersister persister) { // for enhanced entities, copy over the dirty attributes if ( entity instanceof SelfDirtinessTracker && target instanceof SelfDirtinessTracker ) { 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 dfbb3ffab9508c2d23ccf813ab889f05d780fc02..fedbc270fc3e535ac39ac4469a0bc0426c1d1aa0 100755 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java @@ -200,14 +200,16 @@ private void entityIsDeleted(PersistEvent event, Map createCache) { final Object entity = source.getPersistenceContext().unproxy( event.getObject() ); final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); - LOG.tracef( + if ( LOG.isTraceEnabled() ) { + LOG.tracef( "un-scheduling entity deletion [%s]", MessageHelper.infoString( - persister, - persister.getIdentifier( entity, source ), - source.getFactory() + persister, + persister.getIdentifier( entity, source ), + source.getFactory() ) - ); + ); + } if ( createCache.put( entity, entity ) == null ) { 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 5385cf13d045e099c794be0326b66f9fcae743b4..cbd3449a22bba0cc704bec27947d64ad855cf79e 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 @@ -57,11 +57,11 @@ public void onPostLoad(PostLoadEvent event) { entry.forceLocked( entity, nextVersion ); } else if ( LockMode.OPTIMISTIC_FORCE_INCREMENT.equals( lockMode ) ) { - final EntityIncrementVersionProcess incrementVersion = new EntityIncrementVersionProcess( entity, entry ); + final EntityIncrementVersionProcess incrementVersion = new EntityIncrementVersionProcess( entity ); event.getSession().getActionQueue().registerProcess( incrementVersion ); } else if ( LockMode.OPTIMISTIC.equals( lockMode ) ) { - final EntityVerifyVersionProcess verifyVersion = new EntityVerifyVersionProcess( entity, entry ); + final EntityVerifyVersionProcess verifyVersion = new EntityVerifyVersionProcess( entity ); event.getSession().getActionQueue().registerProcess( verifyVersion ); } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java index a506004105a064fb668cd6f6c8621b233ec48ee1..c5cb4caada6a51a84a9b9d4e4bb78a7e31512d7a 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/FlushVisitor.java @@ -21,17 +21,20 @@ * @author Gavin King */ public class FlushVisitor extends AbstractVisitor { - private Object owner; - Object processCollection(Object collection, CollectionType type) - throws HibernateException { + FlushVisitor(EventSource session, Object owner) { + super(session); + this.owner = owner; + } + + Object processCollection(Object collection, CollectionType type) throws HibernateException { - if (collection==CollectionType.UNFETCHED_COLLECTION) { + if ( collection == CollectionType.UNFETCHED_COLLECTION ) { return null; } - if (collection!=null) { + if ( collection != null ) { final PersistentCollection coll; if ( type.hasHolder() ) { coll = getSession().getPersistenceContext().getCollectionHolder(collection); @@ -55,9 +58,4 @@ boolean includeEntityProperty(Object[] values, int i) { return true; } - FlushVisitor(EventSource session, Object owner) { - super(session); - this.owner = owner; - } - } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/MergeContext.java b/hibernate-core/src/main/java/org/hibernate/event/internal/MergeContext.java index a63cb7807f9565e29ad283428560f572c841bcd7..0a5ae927f06610eb6cc3665975a417857f30101c 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/MergeContext.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/MergeContext.java @@ -46,7 +46,7 @@ *
      *
    • Methods that return collections (e.g., {@link #keySet()}, * {@link #values()}, {@link #entrySet()}) return an - * unnmodifiable view of the collection;
    • + * unmodifiable view of the collection; *
    • If {@link #put(Object mergeEntity, Object) managedEntity} or * {@link #put(Object mergeEntity, Object managedEntity, boolean isOperatedOn)} * is executed and this MergeMap already contains a cross-reference for @@ -73,11 +73,11 @@ * The following method is intended to be used by a merge event listener (and other * classes) in the same package to indicate whether the merge operation is being * performed on a merge entity already in the MergeContext: - * {@link MergeContext#setOperatedOn(Object mergeEntity, boolean isOperatedOn) + * {@link MergeContext#setOperatedOn(Object mergeEntity, boolean isOperatedOn)} * * @author Gail Badner */ -class MergeContext implements Map { +public class MergeContext implements Map { private static final Logger LOG = Logger.getLogger( MergeContext.class ); private final EventSource session; @@ -149,7 +149,7 @@ public boolean containsValue(Object managedEntity) { * Returns an unmodifiable set view of the merge-to-managed entity cross-references contained in this MergeContext. * @return an unmodifiable set view of the merge-to-managed entity cross-references contained in this MergeContext * - * @see {@link Collections#unmodifiableSet(java.util.Set)} + * @see Collections#unmodifiableSet(java.util.Set) */ public Set entrySet() { return Collections.unmodifiableSet( mergeToManagedEntityXref.entrySet() ); @@ -180,7 +180,7 @@ public boolean isEmpty() { * Returns an unmodifiable set view of the merge entities contained in this MergeContext * @return an unmodifiable set view of the merge entities contained in this MergeContext * - * @see {@link Collections#unmodifiableSet(java.util.Set)} + * @see Collections#unmodifiableSet(java.util.Set) */ public Set keySet() { return Collections.unmodifiableSet( mergeToManagedEntityXref.keySet() ); @@ -319,7 +319,7 @@ public int size() { * Returns an unmodifiable Set view of managed entities contained in this MergeContext. * @return an unmodifiable Set view of managed entities contained in this MergeContext * - * @see {@link Collections#unmodifiableSet(java.util.Set)} + * @see Collections#unmodifiableSet(java.util.Set) */ public Collection values() { return Collections.unmodifiableSet( managedToMergeEntityXref.keySet() ); @@ -367,7 +367,7 @@ public boolean isOperatedOn(Object mergeEntity) { * * @return an unmodifiable map view of the managed-to-merge entity cross-references. * - * @see {@link Collections#unmodifiableMap(java.util.Map)} + * @see Collections#unmodifiableMap(java.util.Map) */ public Map invertMap() { return Collections.unmodifiableMap( managedToMergeEntityXref ); 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 923d9389d1c1047e4c2d26607feebafa3d73f72e..0ddbc19b8bdf13962405f1f5d840a596aa4fb669 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 @@ -6,8 +6,11 @@ */ package org.hibernate.event.internal; +import java.io.Serializable; + import org.hibernate.EntityMode; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionImplementor; @@ -25,10 +28,19 @@ * * @author Gavin King */ +@SuppressWarnings("WeakerAccess") public class WrapVisitor extends ProxyVisitor { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( WrapVisitor.class ); + private Object entity; + private Serializable id; + + private boolean substitute; - boolean substitute; + public WrapVisitor(Object entity, Serializable id, EventSource session) { + super( session ); + this.entity = entity; + this.id = id; + } boolean isSubstitutionRequired() { return substitute; @@ -42,20 +54,26 @@ boolean isSubstitutionRequired() { Object processCollection(Object collection, CollectionType collectionType) throws HibernateException { - if ( collection != null && ( collection instanceof PersistentCollection ) ) { + if ( collection == null ) { + return null; + } + if ( collection == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { + return null; + } + + if ( collection instanceof PersistentCollection ) { + final PersistentCollection coll = (PersistentCollection) collection; final SessionImplementor session = getSession(); - PersistentCollection coll = (PersistentCollection) collection; + if ( coll.setCurrentSession( session ) ) { reattachCollection( coll, collectionType ); } - return null; - } - else { - return processArrayOrNewCollection( collection, collectionType ); + return null; } + return processArrayOrNewCollection( collection, collectionType ); } final Object processArrayOrNewCollection(Object collection, CollectionType collectionType) diff --git a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java index 2e9b8f9931900b687681ddbc4438a6b91b45efe5..21ab4cd9f388fa105dd941436992fb8478e6cd23 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java @@ -80,8 +80,12 @@ public void addDuplicationStrategy(DuplicationStrategy strategy) { duplicationStrategies.add( strategy ); } - public Iterable listeners() { - return listeners == null ? Collections.emptyList() : listeners; + /** + * Implementation note: should be final for performance reasons. + */ + @Override + public final Iterable listeners() { + return listeners == null ? Collections.EMPTY_LIST : listeners; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractPreDatabaseOperationEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractPreDatabaseOperationEvent.java index 797cc91b39204ec7c2f14b580e023a1098e82a6f..8b4e5561d112bc609d7b8beaaee2b2cf5ed969b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractPreDatabaseOperationEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractPreDatabaseOperationEvent.java @@ -47,8 +47,11 @@ public AbstractPreDatabaseOperationEvent( * Retrieves the entity involved in the database operation. * * @return The entity. + * + * @deprecated Support for JACC will be removed in 6.0 */ @Override + @Deprecated public Object getEntity() { return entity; } @@ -88,12 +91,20 @@ public EventSource getSource() { return getSession(); } + /** + * @deprecated Support for JACC will be removed in 6.0 + */ @Override + @Deprecated public String getEntityName() { return persister.getEntityName(); } + /** + * @deprecated Support for JACC will be removed in 6.0 + */ @Override + @Deprecated public Serializable getIdentifier() { return id; } 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 ed186ab3952706eba8f2758b1e54f1ad5b698a65..bd33e6082477544751f8ab6d83d135c6e54b6864 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 @@ -87,10 +87,10 @@ private LoadEvent( boolean isAssociationFetch, EventSource source) { - super(source); + super( source ); if ( entityId == null ) { - throw new IllegalArgumentException("id to load is required for loading"); + throw new IllegalArgumentException( "id to load is required for loading" ); } if ( lockOptions.getLockMode() == LockMode.WRITE ) { diff --git a/hibernate-core/src/main/java/org/hibernate/graph/spi/AttributeNodeImplementor.java b/hibernate-core/src/main/java/org/hibernate/graph/spi/AttributeNodeImplementor.java index 3c294aeedc75a9b9117927295ab515a271f33767..1ddf93939d5609b4e42094e31fcc91919dfe13ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/spi/AttributeNodeImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/spi/AttributeNodeImplementor.java @@ -10,7 +10,9 @@ import javax.persistence.metamodel.Attribute; /** - * @author Strong Liu + * Integration version of the AttributeNode contract + * + * @author Strong Liu */ public interface AttributeNodeImplementor extends AttributeNode { public Attribute getAttribute(); diff --git a/hibernate-core/src/main/java/org/hibernate/graph/spi/GraphNodeImplementor.java b/hibernate-core/src/main/java/org/hibernate/graph/spi/GraphNodeImplementor.java index 67abd78569c6b92d4ce585762a0ab61875ce0b13..4ab779bb10bbcf568a7acae0c1b93960e65d290c 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/spi/GraphNodeImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/spi/GraphNodeImplementor.java @@ -10,7 +10,9 @@ import javax.persistence.AttributeNode; /** - * @author Strong Liu + * Integration version of the GraphNode contract + * + * @author Strong Liu */ public interface GraphNodeImplementor { List> attributeImplementorNodes(); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlToken.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlToken.java index 6cc68c8c17016ce4baaf2b9830f2f91d3c4147b9..396f02050297d13c24b78029dc96decc1ecdbe19 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlToken.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlToken.java @@ -8,7 +8,7 @@ /** * A custom token class for the HQL grammar. - *

      NOTE: This class must be public becuase it is instantiated by the ANTLR library. Ignore any suggestions + *

      NOTE: This class must be public because it is instantiated by the ANTLR library. Ignore any suggestions * by various code 'analyzers' about this class being package local.

      */ public class HqlToken extends antlr.CommonToken { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlASTFactory.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlASTFactory.java index acf4146b1fcc07a01f981262cec0716262e633de..7b038ccc7bb0ac3fdf9c12e390e94862e500ed6d 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlASTFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlASTFactory.java @@ -15,6 +15,7 @@ import org.hibernate.hql.internal.ast.tree.BinaryLogicOperatorNode; import org.hibernate.hql.internal.ast.tree.BooleanLiteralNode; import org.hibernate.hql.internal.ast.tree.CastFunctionNode; +import org.hibernate.hql.internal.ast.tree.FkRefNode; import org.hibernate.hql.internal.ast.tree.NullNode; import org.hibernate.hql.internal.ast.tree.SearchedCaseNode; import org.hibernate.hql.internal.ast.tree.SimpleCaseNode; @@ -196,6 +197,9 @@ public Class getASTNodeType(int tokenType) { case NULL : { return NullNode.class; } + case FK_REF: { + return FkRefNode.class; + } default: return SqlNode.class; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java index 14086dd770e3d93d9ba01fac6a394c3ac6ec98bf..65b7c64b39ada88637a7e2e57899b33ff64dbeaa 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java @@ -6,14 +6,10 @@ */ package org.hibernate.hql.internal.ast.tree; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - import org.hibernate.QueryException; import org.hibernate.engine.internal.JoinSequence; import org.hibernate.hql.internal.CollectionProperties; +import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; import org.hibernate.hql.internal.antlr.SqlTokenTypes; import org.hibernate.hql.internal.ast.util.ASTUtil; import org.hibernate.hql.internal.ast.util.ColumnHelper; @@ -21,7 +17,7 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.StringHelper; -import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.loader.plan.spi.EntityQuerySpace; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; @@ -265,7 +261,7 @@ private void initText() { } private Type prepareLhs() throws SemanticException { - FromReferenceNode lhs = getLhs(); + final FromReferenceNode lhs = getLhs(); lhs.prepareForDot( propertyName ); return getDataType(); } @@ -395,12 +391,16 @@ private void dereferenceEntity( final boolean joinIsNeeded; if ( isDotNode( parent ) ) { - // our parent is another dot node, meaning we are being further dereferenced. - // thus we need to generate a join unless the parent refers to the associated - // entity's PK (because 'our' table would know the FK). parentAsDotNode = (DotNode) parent; + + // our parent is another dot node, meaning we are being further de-referenced. + // depending on the exact de-reference we may need to generate a physical join. + property = parentAsDotNode.propertyName; - joinIsNeeded = generateJoin && !isReferenceToPrimaryKey( parentAsDotNode.propertyName, entityType ); + joinIsNeeded = generateJoin && ( + entityType.isNullable() || + !isReferenceToPrimaryKey( parentAsDotNode.propertyName, entityType ) + ); } else if ( !getWalker().isSelectStatement() ) { // in non-select queries, the only time we should need to join is if we are in a subquery from clause @@ -504,7 +504,7 @@ private void dereferenceEntityJoin(String classAlias, EntityType propertyType, b JoinSequence joinSequence; - if ( joinColumns.length == 0 ) { + if ( joinColumns.length == 0 && lhsFromElement instanceof EntityQuerySpace ) { // When no columns are available, this is a special join that involves multiple subtypes String lhsTableAlias = getLhs().getFromElement().getTableAlias(); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FkRefNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FkRefNode.java new file mode 100644 index 0000000000000000000000000000000000000000..9e5e2c4d919f48510197183af4b23eaf0d8625cc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FkRefNode.java @@ -0,0 +1,133 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.hql.internal.ast.tree; + +import org.hibernate.QueryException; +import org.hibernate.hql.internal.ast.InvalidPathException; +import org.hibernate.type.BasicType; +import org.hibernate.type.CompositeType; +import org.hibernate.type.ManyToOneType; +import org.hibernate.type.Type; + +import antlr.SemanticException; +import antlr.collections.AST; + +/** + * Represents a `fk()` pseudo-function + * + * @author Steve Ebersole + */ +public class FkRefNode + extends HqlSqlWalkerNode + implements ResolvableNode, DisplayableNode, PathNode { + private FromReferenceNode toOnePath; + + private Type fkType; + private String[] columns; + + private FromReferenceNode resolveToOnePath() { + if ( toOnePath == null ) { + try { + resolve( false, true ); + } + catch (SemanticException e) { + final String msg = "Unable to resolve to-one path `fk(" + toOnePath.getPath() + "`)"; + throw new QueryException( msg, new InvalidPathException( msg ) ); + } + } + + assert toOnePath != null; + return toOnePath; + } + + @Override + public String getDisplayText() { + final FromReferenceNode toOnePath = resolveToOnePath(); + return "fk(`" + toOnePath.getDisplayText() + "` )"; + } + + @Override + public String getPath() { + return toOnePath.getDisplayText() + ".{fk}"; + } + + @Override + public void resolve( + boolean generateJoin, + boolean implicitJoin) throws SemanticException { + if ( toOnePath != null ) { + return; + } + + final AST firstChild = getFirstChild(); + assert firstChild instanceof FromReferenceNode; + + toOnePath = (FromReferenceNode) firstChild; + toOnePath.resolve( false, true, null, toOnePath.getFromElement() ); + + final Type sourcePathDataType = toOnePath.getDataType(); + if ( ! ( sourcePathDataType instanceof ManyToOneType ) ) { + throw new InvalidPathException( + "Argument to fk() function must be a to-one path, but found " + sourcePathDataType + ); + } + final ManyToOneType toOneType = (ManyToOneType) sourcePathDataType; + final FromElement fromElement = toOnePath.getFromElement(); + + fkType = toOneType.getIdentifierOrUniqueKeyType( getSessionFactoryHelper().getFactory() ); + assert fkType instanceof BasicType + || fkType instanceof CompositeType; + + columns = fromElement.toColumns( + fromElement.getTableAlias(), + toOneType.getPropertyName(), + getWalker().isInSelect() + ); + assert columns != null && columns.length > 0; + + setText( String.join( ", ", columns ) ); + } + + @Override + public void resolve( + boolean generateJoin, + boolean implicitJoin, + String classAlias, + AST parent, + AST parentPredicate) throws SemanticException { + resolve( false, true ); + } + + @Override + public void resolve( + boolean generateJoin, + boolean implicitJoin, + String classAlias, + AST parent) throws SemanticException { + resolve( false, true ); + } + + @Override + public void resolve( + boolean generateJoin, + boolean implicitJoin, + String classAlias) throws SemanticException { + resolve( false, true ); + } + + @Override + public void resolveInFunctionCall( + boolean generateJoin, + boolean implicitJoin) throws SemanticException { + resolve( false, true ); + } + + @Override + public void resolveIndex(AST parent) throws SemanticException { + throw new InvalidPathException( "fk() paths cannot be de-referenced as indexed path" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java index bb47d0e090ca569888c8bca7a59207703859a4f4..77d7dbcb025fd0a4e34cc8875be6c15997837a38 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java @@ -339,14 +339,18 @@ public String[] getIdentityColumns() { throw new IllegalStateException( "No table alias for node " + this ); } - final String propertyName = getIdentifierPropertyName(); - - if ( getWalker().getStatementType() == HqlSqlTokenTypes.SELECT ) { - return getPropertyMapping( propertyName ).toColumns( table, propertyName ); - } - else { - return getPropertyMapping( propertyName ).toColumns( propertyName ); + final String[] propertyNames = getIdentifierPropertyNames(); + List columns = new ArrayList<>(); + for ( int i = 0; i < propertyNames.length; i++ ) { + String[] propertyNameColumns = toColumns( + table, propertyNames[i], + getWalker().getStatementType() == HqlSqlTokenTypes.SELECT + ); + for ( int j = 0; j < propertyNameColumns.length; j++ ) { + columns.add( propertyNameColumns[j] ); + } } + return columns.toArray( new String[columns.size()] ); } public void setCollectionJoin(boolean collectionJoin) { @@ -527,8 +531,8 @@ public CollectionPropertyReference getCollectionPropertyReference(String propert return elementType.getCollectionPropertyReference( propertyName ); } - public String getIdentifierPropertyName() { - return elementType.getIdentifierPropertyName(); + public String[] getIdentifierPropertyNames() { + return elementType.getIdentifierPropertyNames(); } public void setFetch(boolean fetch) { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java index 7c815b0e02061409270dd06be95bcde3a30915f1..9fe8f365e48cb46d9aa633a9d78ff577db23b826 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java @@ -14,17 +14,15 @@ import org.hibernate.MappingException; import org.hibernate.QueryException; import org.hibernate.engine.internal.JoinSequence; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.hql.internal.CollectionProperties; import org.hibernate.hql.internal.CollectionSubqueryFactory; import org.hibernate.hql.internal.NameGenerator; import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; -import org.hibernate.hql.internal.ast.HqlSqlWalker; -import org.hibernate.hql.internal.ast.util.SessionFactoryHelper; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.loader.PropertyPath; +import org.hibernate.loader.internal.AliasConstantsHelper; import org.hibernate.param.ParameterSpecification; import org.hibernate.persister.collection.CollectionPropertyMapping; import org.hibernate.persister.collection.CollectionPropertyNames; @@ -33,6 +31,8 @@ import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.entity.PropertyMapping; import org.hibernate.persister.entity.Queryable; +import org.hibernate.tuple.IdentifierProperty; +import org.hibernate.type.EmbeddedComponentType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; @@ -130,18 +130,22 @@ public Queryable getQueryable() { String renderScalarIdentifierSelect(int i) { checkInitialized(); - final String idPropertyName = getIdentifierPropertyName(); - String[] cols = getPropertyMapping( idPropertyName ).toColumns( getTableAlias(), idPropertyName ); - + final String[] idPropertyName = getIdentifierPropertyNames(); StringBuilder buf = new StringBuilder(); - // For property references generate . as - for ( int j = 0; j < cols.length; j++ ) { - String column = cols[j]; - if ( j > 0 ) { - buf.append( ", " ); + int counter = 0; + for ( int j = 0; j < idPropertyName.length; j++ ) { + String propertyName = idPropertyName[j]; + String[] toColumns = getPropertyMapping( propertyName ).toColumns( getTableAlias(), propertyName ); + for ( int h = 0; h < toColumns.length; h++, counter++ ) { + String column = toColumns[h]; + if ( j + h > 0 ) { + buf.append( ", " ); + } + buf.append( column ).append( " as " ).append( NameGenerator.scalarName( i, counter ) ); } - buf.append( column ).append( " as " ).append( NameGenerator.scalarName( i, j ) ); } + + LOG.debug( "Rendered scalar ID select column(s): " + buf ); return buf.toString(); } @@ -188,7 +192,7 @@ private String getSuffix(int size, int sequence) { } private static String generateSuffix(int size, int k) { - return size == 1 ? "" : Integer.toString( k ) + '_'; + return size == 1 ? "" : AliasConstantsHelper.get( k ); } private void checkInitialized() { @@ -453,11 +457,16 @@ String[] toColumns(String tableAlias, String path, boolean inSelect, boolean for // executors being used (as this subquery will // actually be used in the "id select" phase // of that multi-table executor) + // for update queries, the real table name of the updated table must be used if not in the top level where + // clause, typically in a SET clause // B) otherwise, we need to use the persister's // table name as the column qualification // 2) otherwise (not correlated), use the given alias if ( isCorrelation() ) { - if ( isMultiTable() || isInsertQuery() ) { + if ( isMultiTable() && ( !isUpdateQuery() || inWhereClause() ) ) { + return propertyMapping.toColumns( tableAlias, path ); + } + else if ( isInsertQuery() ) { return propertyMapping.toColumns( tableAlias, path ); } return propertyMapping.toColumns( extractTableName(), path ); @@ -502,6 +511,10 @@ private boolean isInsertQuery() { return fromElement.getWalker().getStatementType() == HqlSqlTokenTypes.INSERT; } + private boolean isUpdateQuery() { + return fromElement.getWalker().getStatementType() == HqlSqlTokenTypes.UPDATE; + } + private boolean isManipulationQuery() { return fromElement.getWalker().getStatementType() == HqlSqlTokenTypes.UPDATE || fromElement.getWalker().getStatementType() == HqlSqlTokenTypes.DELETE; @@ -682,13 +695,25 @@ public String[] toColumns(String propertyName) throws QueryException, Unsupporte } } - public String getIdentifierPropertyName() { - if ( getEntityPersister() != null && getEntityPersister().getEntityMetamodel() != null - && getEntityPersister().getEntityMetamodel().hasNonIdentifierPropertyNamedId() ) { - return getEntityPersister().getIdentifierPropertyName(); - } - else { - return EntityPersister.ENTITY_ID; + public String[] getIdentifierPropertyNames() { + if ( getEntityPersister() != null ) { + String identifierPropertyName = getEntityPersister().getIdentifierPropertyName(); + if ( identifierPropertyName != null ) { + return new String[] { identifierPropertyName }; + } + else { + final IdentifierProperty identifierProperty = getEntityPersister().getEntityMetamodel() + .getIdentifierProperty(); + if ( identifierProperty.hasIdentifierMapper() && !identifierProperty.isEmbedded() ) { + return new String[] { PropertyPath.IDENTIFIER_MAPPER_PROPERTY }; + } + else { + if ( EmbeddedComponentType.class.isInstance( identifierProperty.getType() ) ) { + return ( (EmbeddedComponentType) identifierProperty.getType() ).getPropertyNames(); + } + } + } } + return new String[] { EntityPersister.ENTITY_ID }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IdentNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IdentNode.java index 81a26186c3f6026c0d7f0fc73a574383a2c41ae6..1574c57feb9b1908479b8fa0ffe85003ecaa0d16 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IdentNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IdentNode.java @@ -197,14 +197,6 @@ private boolean resolveAsAlias() { String[] columnExpressions = element.getIdentityColumns(); - // determine whether to apply qualification (table alias) to the column(s)... - if ( ! isFromElementUpdateOrDeleteRoot( element ) ) { - if ( StringHelper.isNotEmpty( element.getTableAlias() ) ) { - // apparently we also need to check that they are not already qualified. Ugh! - columnExpressions = StringHelper.qualifyIfNot( element.getTableAlias(), columnExpressions ); - } - } - final Dialect dialect = getWalker().getSessionFactoryHelper().getFactory().getDialect(); final boolean isInCount = getWalker().isInCount(); final boolean isInDistinctCount = isInCount && getWalker().isInCountDistinct(); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/QueryTranslatorImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/QueryTranslatorImpl.java index 24126fe58d27f7056195097e8763e452d16c87ec..1f4be5a6b15584c9ad40bcae8e6ed00cfef7b580 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/QueryTranslatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/QueryTranslatorImpl.java @@ -48,6 +48,7 @@ import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.loader.BasicLoader; +import org.hibernate.loader.internal.AliasConstantsHelper; import org.hibernate.loader.spi.AfterLoadAction; import org.hibernate.param.CollectionFilterKeyParameterSpecification; import org.hibernate.param.ParameterBinder; @@ -659,7 +660,7 @@ private void renderSQL() throws QueryException, MappingException { //if ( !isName(name) ) throw new QueryException("unknown type: " + name); persisters[i] = getEntityPersisterForName( name ); // TODO: cannot use generateSuffixes() - it handles the initial suffix differently. - suffixes[i] = ( size == 1 ) ? "" : Integer.toString( i ) + '_'; + suffixes[i] = ( size == 1 ) ? "" : AliasConstantsHelper.get( i ); names[i] = name; includeInSelect[i] = !entitiesToFetch.contains( name ); if ( includeInSelect[i] ) { @@ -758,7 +759,7 @@ private void renderIdentifierSelect(QuerySelect sql) { for ( int k = 0; k < size; k++ ) { String name = (String) returnedTypes.get( k ); - String suffix = size == 1 ? "" : Integer.toString( k ) + '_'; + String suffix = size == 1 ? "" : AliasConstantsHelper.get( k ); sql.addSelectFragmentString( persisters[k].identifierSelectFragment( name, suffix ) ); } @@ -783,7 +784,7 @@ private void renderIdentifierSelect(QuerySelect sql) { private void renderPropertiesSelect(QuerySelect sql) { int size = returnedTypes.size(); for ( int k = 0; k < size; k++ ) { - String suffix = size == 1 ? "" : Integer.toString( k ) + '_'; + String suffix = size == 1 ? "" : AliasConstantsHelper.get( k ); String name = (String) returnedTypes.get( k ); sql.addSelectFragmentString( persisters[k].propertySelectFragment( name, suffix, false ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/id/AbstractPostInsertGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/AbstractPostInsertGenerator.java index ca57bc0dd3c93d5535a44da75362305745657712..3dc3649dc37662701d539f06520215b497e5e1da 100755 --- a/hibernate-core/src/main/java/org/hibernate/id/AbstractPostInsertGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/AbstractPostInsertGenerator.java @@ -32,4 +32,9 @@ public boolean supportsBulkInsertionIdentifierGeneration() { public String determineBulkInsertionIdentifierGenerationSelectFragment(Dialect dialect) { return null; } + + @Override + public boolean supportsJdbcBatchInserts() { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/IdentifierGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/IdentifierGenerator.java index aa157fe052df318265d87675e48511bbba0968c2..51bc8a355e5b4144b0d1caf21e09e55f92a22fb8 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/IdentifierGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/IdentifierGenerator.java @@ -7,7 +7,6 @@ package org.hibernate.id; import java.io.Serializable; - import javax.persistence.GeneratedValue; import org.hibernate.HibernateException; @@ -60,4 +59,13 @@ public interface IdentifierGenerator { * @throws HibernateException Indicates trouble generating the identifier */ Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException; + + /** + * Check if JDBC batch inserts are supported. + * + * @return JDBC batch inserts are supported. + */ + default boolean supportsJdbcBatchInserts() { + return true; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/factory/internal/MutableIdentifierGeneratorFactoryInitiator.java b/hibernate-core/src/main/java/org/hibernate/id/factory/internal/MutableIdentifierGeneratorFactoryInitiator.java index 1b139223d94d6723a66717760dafcdb04b6bd8b3..956ffa5b50809e32782615671cebf3f797dfce3c 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/factory/internal/MutableIdentifierGeneratorFactoryInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/factory/internal/MutableIdentifierGeneratorFactoryInitiator.java @@ -13,7 +13,7 @@ import org.hibernate.service.spi.ServiceRegistryImplementor; /** - * @author Emmanuel Bernard + * @author Emmanuel Bernard */ public class MutableIdentifierGeneratorFactoryInitiator implements StandardServiceInitiator { public static final MutableIdentifierGeneratorFactoryInitiator INSTANCE = new MutableIdentifierGeneratorFactoryInitiator(); diff --git a/hibernate-core/src/main/java/org/hibernate/id/factory/spi/MutableIdentifierGeneratorFactory.java b/hibernate-core/src/main/java/org/hibernate/id/factory/spi/MutableIdentifierGeneratorFactory.java index c4fa36b3f72eddb08ebdbe816331e8f2c2e96ddf..0ac31f1bf5c5e0d9db0852a80a84a47435392577 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/factory/spi/MutableIdentifierGeneratorFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/id/factory/spi/MutableIdentifierGeneratorFactory.java @@ -12,7 +12,7 @@ /** * Let people register strategies * - * @author Emmanuel Bernard + * @author Emmanuel Bernard */ public interface MutableIdentifierGeneratorFactory extends IdentifierGeneratorFactory, Service { public void register(String strategy, Class generatorClass); 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 ea3c5925e2f812c6e77d127d29e7441cc1c7022d..dca926eb8ce5178b6fa1bcba30641f49a6c2f47d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -10,12 +10,12 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; -import java.lang.reflect.Array; import java.sql.SQLException; import java.util.List; import java.util.TimeZone; import java.util.UUID; import javax.persistence.FlushModeType; +import javax.persistence.TransactionRequiredException; import javax.persistence.Tuple; import org.hibernate.AssertionFailure; @@ -81,7 +81,6 @@ import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl; import org.hibernate.resource.transaction.spi.TransactionCoordinator; import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; -import org.hibernate.transform.BasicTransformerAdapter; import org.hibernate.type.Type; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; @@ -130,6 +129,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont protected boolean closed; protected boolean waitingForAutoClose; + private transient boolean disallowOutOfTransactionUpdateOperations; // transient & non-final for Serialization purposes - ugh private transient SessionEventListenerManagerImpl sessionEventsManager = new SessionEventListenerManagerImpl(); @@ -143,6 +143,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont public AbstractSharedSessionContract(SessionFactoryImpl factory, SessionCreationOptions options) { this.factory = factory; this.cacheTransactionSync = factory.getCache().getRegionFactory().createTransactionContext( this ); + this.disallowOutOfTransactionUpdateOperations = !factory.getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); this.flushMode = options.getInitialSessionFlushMode(); @@ -392,20 +393,33 @@ public boolean isTransactionInProgress() { } @Override - public Transaction getTransaction() throws HibernateException { - if ( getFactory().getSessionFactoryOptions().getJpaCompliance().isJpaTransactionComplianceEnabled() ) { - // JPA requires that we throw IllegalStateException if this is called - // on a JTA EntityManager - if ( getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta() ) { - if ( !getFactory().getSessionFactoryOptions().isJtaTransactionAccessEnabled() ) { - throw new IllegalStateException( "A JTA EntityManager cannot use getTransaction()" ); - } - } + public void checkTransactionNeededForUpdateOperation(String exceptionMessage) { + if ( disallowOutOfTransactionUpdateOperations && !isTransactionInProgress() ) { + throw new TransactionRequiredException( exceptionMessage ); } + } + @Override + public Transaction getTransaction() throws HibernateException { + if ( !isTransactionAccessible() ) { + throw new IllegalStateException( + "Transaction is not accessible when using JTA with JPA-compliant transaction access enabled" + ); + } return accessTransaction(); } + protected boolean isTransactionAccessible() { + // JPA requires that access not be provided to the transaction when using JTA. + // This is overridden when SessionFactoryOptions isJtaTransactionAccessEnabled() is true. + if ( getFactory().getSessionFactoryOptions().getJpaCompliance().isJpaTransactionComplianceEnabled() && + getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta() && + !getFactory().getSessionFactoryOptions().isJtaTransactionAccessEnabled() ) { + return false; + } + return true; + } + @Override public Transaction accessTransaction() { if ( this.currentHibernateTransaction == null ) { @@ -480,7 +494,7 @@ protected TransactionImplementor getCurrentTransaction() { @Override public boolean isConnected() { - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); return jdbcCoordinator.getLogicalConnection().isOpen(); } @@ -604,7 +618,7 @@ protected NativeSQLQueryPlan getNativeQueryPlan(NativeSQLQuerySpecification spec @Override public QueryImplementor getNamedQuery(String name) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); delayedAfterCompletion(); // look as HQL/JPQL first @@ -697,7 +711,7 @@ protected void initQueryFromNamedDefinition(Query query, NamedQueryDefinition nq @Override public QueryImplementor createQuery(String queryString) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); delayedAfterCompletion(); try { @@ -723,7 +737,7 @@ protected void applyQuerySettingsAndHints(Query query) { @SuppressWarnings("unchecked") public QueryImplementor createQuery(String queryString, Class resultClass) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); delayedAfterCompletion(); try { @@ -796,7 +810,7 @@ public QueryImplementor createNamedQuery(String name) { protected QueryImplementor buildQueryFromName(String name, Class resultType) { checkOpen(); try { - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); delayedAfterCompletion(); // todo : apply stored setting at the JPA Query level too @@ -920,7 +934,7 @@ public NativeQueryImplementor createNativeQuery(String sqlString) { @Override public NativeQueryImplementor createNativeQuery(String sqlString, Class resultClass) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); delayedAfterCompletion(); try { @@ -934,44 +948,18 @@ public NativeQueryImplementor createNativeQuery(String sqlString, Class resultCl } private void handleNativeQueryResult(NativeQueryImplementor query, Class resultClass) { - boolean isObjectArray = Object[].class.equals( resultClass ); - if ( Tuple.class.equals( resultClass ) ) { query.setResultTransformer( new NativeQueryTupleTransformer() ); } - else if ( resultClass.isArray() && !isObjectArray ) { - Class elementClass = resultClass.getComponentType(); - - query.setResultTransformer( new BasicTransformerAdapter() { - @Override - public Object transformTuple(Object[] tuple, String[] aliases) { - Object[] result = (Object[]) Array.newInstance( elementClass, tuple.length ); - for ( int i = 0; i < tuple.length; i++ ) { - result[i] = elementClass.cast( tuple[i] ); - } - return result; - } - } ); - } - else if ( this.getFactory().getMetamodel().getEntities() - .stream() - .anyMatch( entityType -> entityType.getJavaType().isAssignableFrom( resultClass ) ) ) { + else { query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); } - else if ( !isObjectArray ) { - query.setResultTransformer( new BasicTransformerAdapter() { - @Override - public Object transformTuple(Object[] tuple, String[] aliases) { - return resultClass.cast( tuple[0] ); - } - } ); - } } @Override public NativeQueryImplementor createNativeQuery(String sqlString, String resultSetMapping) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); delayedAfterCompletion(); try { @@ -987,7 +975,7 @@ public NativeQueryImplementor createNativeQuery(String sqlString, String resultS @Override public NativeQueryImplementor getNamedNativeQuery(String name) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); delayedAfterCompletion(); final NamedSQLQueryDefinition nativeQueryDefinition = factory.getNamedQueryRepository().getNamedSQLQueryDefinition( name ); @@ -1007,7 +995,7 @@ protected NativeQueryImplementor getNativeQueryImplementor( String queryString, boolean isOrdinalParameterZeroBased) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); delayedAfterCompletion(); try { @@ -1161,5 +1149,7 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound entityNameResolver = new CoordinatingEntityNameResolver( factory, interceptor ); exceptionConverter = new ExceptionConverterImpl( this ); + this.disallowOutOfTransactionUpdateOperations = !getFactory().getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); + } } 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 d3b576ab4af0c4708b83b30b6f3fa5398707483b..d9a68fcbfc3ac2e53c6b4e4de1a0d5bdc0086200 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -343,12 +343,6 @@ void expectedType(String name, @Message(value = "Found mapping document in jar: %s", id = 109) void foundMappingDocument(String name); - @LogMessage(level = ERROR) - @Message(value = "Getters of lazy classes cannot be final: %s.%s", id = 112) - void gettersOfLazyClassesCannotBeFinal( - String entityName, - String name); - @LogMessage(level = WARN) @Message(value = "GUID identifier generated: %s", id = 113) void guidGenerated(String result); @@ -774,12 +768,6 @@ void scopingTypesToSessionFactoryAfterAlreadyScoped( @Message(value = "Sessions opened: %s", id = 242) void sessionsOpened(long sessionOpenCount); - @LogMessage(level = ERROR) - @Message(value = "Setters of lazy classes cannot be final: %s.%s", id = 243) - void settersOfLazyClassesCannotBeFinal( - String entityName, - String name); - @LogMessage(level = WARN) @Message(value = "@Sort not allowed for an indexed collection, annotation ignored.", id = 244) void sortAnnotationIndexedCollection(); @@ -1811,4 +1799,27 @@ void attemptToAssociateProxyWithTwoOpenSessions( + "in the same package as class %1$s. In this case, the class should be opened and exported to Hibernate ORM.", id = 488) String bytecodeEnhancementFailedUnableToGetPrivateLookupFor(String className); + @LogMessage(level = WARN) + @Message(value = "Setting " + AvailableSettings.NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE + "=true is not valid with JPA bootstrapping; setting will be ignored.", id = 489 ) + void nativeExceptionHandling51ComplianceJpaBootstrapping(); + + @LogMessage(level = WARN) + @Message(value = "Attempt to merge an uninitialized collection with queued operations; queued operations will be ignored: %s", id = 494) + void ignoreQueuedOperationsOnMerge(String collectionInfoString); + + @LogMessage(level = WARN) + @Message(value = "Attaching an uninitialized collection with queued operations to a session: %s", id = 495) + void queuedOperationWhenAttachToSession(String collectionInfoString); + + @LogMessage(level = INFO) + @Message(value = "Detaching an uninitialized collection with queued operations from a session: %s", id = 496) + void queuedOperationWhenDetachFromSession(String collectionInfoString); + + @LogMessage(level = DEBUG) + @Message(value = "Detaching an uninitialized collection with queued operations from a session due to rollback: %s", id = 498) + void queuedOperationWhenDetachFromSessionOnRollback(String collectionInfoString); + + @LogMessage(level = WARN) + @Message(value = "Using @AttributeOverride or @AttributeOverrides in conjunction with entity inheritance is not supported: %s. The overriding definitions are ignored.", id = 499) + void unsupportedAttributeOverrideWithEntityInheritance(String entityName); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/EntityManagerMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/EntityManagerMessageLogger.java index ee4d301c7a0694df4be8f89b74a3df3a4de8e398..085299a517e40504302805534f36b1aad2bc20c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/EntityManagerMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/EntityManagerMessageLogger.java @@ -114,7 +114,7 @@ void unableToLocateStaticMetamodelField( @LogMessage(level = WARN) @Message( id = 15016, - value = "Encountered a deprecated javax.persistence.spi.PersistenceProvider [%s]; use [%s] instead." + value = "Encountered a deprecated javax.persistence.spi.PersistenceProvider [%s]; [%s] will be used instead." ) void deprecatedPersistenceProvider(String deprecated, String replacement); 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 a5b3401663fea424aa559af9cfc36ec44bab1e36..ae00fb7916761931f7c2695bdc69942eeec94471 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java @@ -43,14 +43,18 @@ public class ExceptionConverterImpl implements ExceptionConverter { private static final EntityManagerMessageLogger log = HEMLogging.messageLogger( ExceptionConverterImpl.class ); private final SharedSessionContractImplementor sharedSessionContract; + private final boolean isJpaBootstrap; + private final boolean nativeExceptionHandling51Compliance; public ExceptionConverterImpl(SharedSessionContractImplementor sharedSessionContract) { this.sharedSessionContract = sharedSessionContract; + isJpaBootstrap = sharedSessionContract.getFactory().getSessionFactoryOptions().isJpaBootstrap(); + nativeExceptionHandling51Compliance = sharedSessionContract.getFactory().getSessionFactoryOptions().nativeExceptionHandling51Compliance(); } @Override public RuntimeException convertCommitException(RuntimeException e) { - if ( sharedSessionContract.getFactory().getSessionFactoryOptions().isJpaBootstrap() ) { + if ( isJpaBootstrap ) { Throwable wrappedException; if ( e instanceof HibernateException ) { wrappedException = convert( (HibernateException) e ); @@ -83,72 +87,92 @@ else if ( e instanceof PersistenceException ) { @Override public RuntimeException convert(HibernateException e, LockOptions lockOptions) { - Throwable cause = e; - if ( cause instanceof StaleStateException ) { - final PersistenceException converted = wrapStaleStateException( (StaleStateException) cause ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof LockAcquisitionException ) { - final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof LockingStrategyException ) { - final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof org.hibernate.PessimisticLockException ) { - final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof org.hibernate.QueryTimeoutException ) { - final QueryTimeoutException converted = new QueryTimeoutException( cause.getMessage(), cause ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof ObjectNotFoundException ) { - final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof org.hibernate.NonUniqueObjectException ) { - final EntityExistsException converted = new EntityExistsException( cause.getMessage() ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof org.hibernate.NonUniqueResultException ) { - final NonUniqueResultException converted = new NonUniqueResultException( cause.getMessage() ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof UnresolvableObjectException ) { - final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() ); - handlePersistenceException( converted ); - return converted; - } - else if ( cause instanceof QueryException ) { - return new IllegalArgumentException( cause ); - } - else if ( cause instanceof MultipleBagFetchException ) { - return new IllegalArgumentException( cause ); - } - else if ( cause instanceof TransientObjectException ) { - try { - sharedSessionContract.markForRollbackOnly(); + if ( !nativeExceptionHandling51Compliance ) { + Throwable cause = e; + if ( cause instanceof StaleStateException ) { + final PersistenceException converted = wrapStaleStateException( (StaleStateException) cause ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof LockAcquisitionException ) { + final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof LockingStrategyException ) { + final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof org.hibernate.PessimisticLockException ) { + final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof org.hibernate.QueryTimeoutException ) { + final QueryTimeoutException converted = new QueryTimeoutException( cause.getMessage(), cause ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof ObjectNotFoundException ) { + final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() ); + handlePersistenceException( converted ); + return converted; } - catch (Exception ne) { - //we do not want the subsequent exception to swallow the original one - log.unableToMarkForRollbackOnTransientObjectException( ne ); + else if ( cause instanceof org.hibernate.NonUniqueObjectException ) { + final EntityExistsException converted = new EntityExistsException( cause.getMessage() ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof org.hibernate.NonUniqueResultException ) { + final NonUniqueResultException converted = new NonUniqueResultException( cause.getMessage() ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof UnresolvableObjectException ) { + final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() ); + handlePersistenceException( converted ); + return converted; + } + else if ( cause instanceof QueryException ) { + return new IllegalArgumentException( cause ); + } + else if ( cause instanceof MultipleBagFetchException ) { + return new IllegalArgumentException( cause ); + } + else if ( cause instanceof TransientObjectException ) { + try { + sharedSessionContract.markForRollbackOnly(); + } + catch (Exception ne) { + //we do not want the subsequent exception to swallow the original one + log.unableToMarkForRollbackOnTransientObjectException( ne ); + } + return new IllegalStateException( e ); //Spec 3.2.3 Synchronization rules + } + else { + final PersistenceException converted = new PersistenceException( cause ); + handlePersistenceException( converted ); + return converted; } - return new IllegalStateException( e ); //Spec 3.2.3 Synchronization rules } else { - final PersistenceException converted = new PersistenceException( cause ); - handlePersistenceException( converted ); - return converted; + if ( e instanceof QueryException ) { + return e; + } + else if ( e instanceof MultipleBagFetchException ) { + return e; + } + else { + try { + sharedSessionContract.markForRollbackOnly(); + } + catch (Exception ne) { + //we do not want the subsequent exception to swallow the original one + log.unableToMarkForRollbackOnTransientObjectException( ne ); + } + return e; + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FilterHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/FilterHelper.java index 8ae733f2335443c4ec45bcef6cd65fb38fac8e6c..f027f8a9759da2be534f1f4623f87e50d43bf525 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FilterHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FilterHelper.java @@ -14,6 +14,8 @@ import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.sql.Template; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * Implementation of FilterHelper. * @@ -44,23 +46,27 @@ public FilterHelper(List filters, SessionFactoryImplementor filterCount = 0; for ( final FilterConfiguration filter : filters ) { filterAutoAliasFlags[filterCount] = false; - filterNames[filterCount] = filter.getName(); - filterConditions[filterCount] = filter.getCondition(); + filterNames[filterCount] = safeInterning( filter.getName() ); + filterConditions[filterCount] = safeInterning( filter.getCondition() ); filterAliasTableMaps[filterCount] = filter.getAliasTableMap( factory ); if ( ( filterAliasTableMaps[filterCount].isEmpty() || isTableFromPersistentClass( filterAliasTableMaps[filterCount] ) ) && filter .useAutoAliasInjection() ) { - filterConditions[filterCount] = Template.renderWhereStringTemplate( - filter.getCondition(), - FilterImpl.MARKER, - factory.getDialect(), - factory.getSqlFunctionRegistry() + filterConditions[filterCount] = safeInterning( + Template.renderWhereStringTemplate( + filter.getCondition(), + FilterImpl.MARKER, + factory.getDialect(), + factory.getSqlFunctionRegistry() + ) ); filterAutoAliasFlags[filterCount] = true; } - filterConditions[filterCount] = StringHelper.replace( - filterConditions[filterCount], - ":", - ":" + filterNames[filterCount] + "." + filterConditions[filterCount] = safeInterning( + StringHelper.replace( + filterConditions[filterCount], + ":", + ":" + filterNames[filterCount] + "." + ) ); filterCount++; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java index f0350117446fc6e2ce1151afdd13a2a0fb0796e0..63a0d933756cd563fdbe06b2ae3f06eb5ccc38df 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java @@ -13,6 +13,7 @@ import org.hibernate.HibernateException; import org.hibernate.JDBCException; import org.hibernate.ScrollableResults; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.hql.internal.HolderInstantiator; @@ -189,21 +190,28 @@ private void prepareCurrentRow(boolean underlyingScrollSuccessful) { return; } - final Object result = getLoader().loadSingleRow( - getResultSet(), - getSession(), - getQueryParameters(), - true - ); - if ( result != null && result.getClass().isArray() ) { - currentRow = (Object[]) result; - } - else { - currentRow = new Object[] {result}; - } - - if ( getHolderInstantiator() != null ) { - currentRow = new Object[] {getHolderInstantiator().instantiate( currentRow )}; + final PersistenceContext persistenceContext = getSession().getPersistenceContext(); + persistenceContext.beforeLoad(); + try { + final Object result = getLoader().loadSingleRow( + getResultSet(), + getSession(), + getQueryParameters(), + true + ); + if ( result != null && result.getClass().isArray() ) { + currentRow = (Object[]) result; + } + else { + currentRow = new Object[] {result}; + } + + if ( getHolderInstantiator() != null ) { + currentRow = new Object[] { getHolderInstantiator().instantiate( currentRow ) }; + } + } + finally { + persistenceContext.afterLoad(); } afterScrollOperation(); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java index 19dcb6c9e7d798263272f7e8849fa0bd7348cef4..631d8604904de9f89d3b0ac00bce61392ff52928 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -1101,7 +1101,15 @@ public Type resolveParameterBindType(Class clazz){ } } + /** + * @deprecated use {@link #configuredInterceptor(Interceptor, boolean, SessionFactoryOptions)} + */ + @Deprecated public static Interceptor configuredInterceptor(Interceptor interceptor, SessionFactoryOptions options) { + return configuredInterceptor( interceptor, false, options ); + } + + public static Interceptor configuredInterceptor(Interceptor interceptor, boolean explicitNoInterceptor, SessionFactoryOptions options) { // NOTE : DO NOT return EmptyInterceptor.INSTANCE from here as a "default for the Session" // we "filter" that one out here. The return from here should represent the // explicitly configured Interceptor (if one). Return null from here instead; Session @@ -1116,6 +1124,12 @@ public static Interceptor configuredInterceptor(Interceptor interceptor, Session return options.getInterceptor(); } + // If explicitly asking for no interceptor and there is no SessionFactory-scoped interceptors, then + // no need to inherit from the configured stateless session ones. + if ( explicitNoInterceptor ) { + return null; + } + // then check the Session-scoped interceptor prototype if ( options.getStatelessInterceptorImplementor() != null && options.getStatelessInterceptorImplementorSupplier() != null ) { throw new HibernateException( @@ -1157,6 +1171,7 @@ static class SessionBuilderImpl implements SessionBuil private String tenantIdentifier; private TimeZone jdbcTimeZone; private boolean queryParametersValidationEnabled; + private boolean explicitNoInterceptor; private List listeners; @@ -1258,7 +1273,7 @@ public Connection getConnection() { @Override public Interceptor getInterceptor() { - return configuredInterceptor( interceptor, sessionFactory.getSessionFactoryOptions() ); + return configuredInterceptor( interceptor, explicitNoInterceptor, sessionFactory.getSessionFactoryOptions() ); } @Override @@ -1307,6 +1322,7 @@ public T owner(SessionOwner sessionOwner) { @SuppressWarnings("unchecked") public T interceptor(Interceptor interceptor) { this.interceptor = interceptor; + this.explicitNoInterceptor = false; return (T) this; } @@ -1314,6 +1330,7 @@ public T interceptor(Interceptor interceptor) { @SuppressWarnings("unchecked") public T noInterceptor() { this.interceptor = EmptyInterceptor.INSTANCE; + this.explicitNoInterceptor = true; return (T) this; } @@ -1473,7 +1490,7 @@ public Connection getConnection() { @Override public Interceptor getInterceptor() { - return configuredInterceptor( EmptyInterceptor.INSTANCE, sessionFactory.getSessionFactoryOptions() ); + return configuredInterceptor( EmptyInterceptor.INSTANCE, false, sessionFactory.getSessionFactoryOptions() ); } 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 dca0c0a0ea90f03b06324d4c35f9bc13727e7eb5..6c46d7ee51695680eb3fa1910d24eecd5f640e60 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -69,6 +69,7 @@ import org.hibernate.SessionException; import org.hibernate.SharedSessionBuilder; import org.hibernate.SimpleNaturalIdLoadAccess; +import org.hibernate.Transaction; import org.hibernate.TransientObjectException; import org.hibernate.TypeHelper; import org.hibernate.TypeMismatchException; @@ -238,7 +239,6 @@ public final class SessionImpl private transient int dontFlushFromFind; - private transient boolean disallowOutOfTransactionUpdateOperations; private transient ExceptionMapper exceptionMapper; private transient ManagedFlushChecker managedFlushChecker; @@ -249,6 +249,7 @@ public final class SessionImpl private transient boolean discardOnClose; private transient TransactionObserver transactionObserver; + private transient EventListenerRegistry eventListenerRegistry; public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { super( factory, options ); @@ -262,7 +263,7 @@ public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { this.autoClear = options.shouldAutoClear(); this.autoClose = options.shouldAutoClose(); this.queryParametersValidationEnabled = options.isQueryParametersValidationEnabled(); - this.disallowOutOfTransactionUpdateOperations = !factory.getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); + this.discardOnClose = getFactory().getSessionFactoryOptions().isReleaseResourcesOnCloseEnabled(); if ( options instanceof SharedSessionCreationOptions && ( (SharedSessionCreationOptions) options ).isTransactionCoordinatorShared() ) { @@ -323,12 +324,12 @@ protected void applyQuerySettingsAndHints(Query query) { if ( lockOptions.getLockMode() != LockMode.NONE ) { query.setLockMode( getLockMode( lockOptions.getLockMode() ) ); } - Object queryTimeout; - if ( (queryTimeout = getProperties().get( QueryHints.SPEC_HINT_TIMEOUT ) ) != null ) { + final Object queryTimeout; + if ( ( queryTimeout = properties.get( QueryHints.SPEC_HINT_TIMEOUT ) ) != null ) { query.setHint( QueryHints.SPEC_HINT_TIMEOUT, queryTimeout ); } - Object lockTimeout; - if( (lockTimeout = getProperties().get( JPA_LOCK_TIMEOUT ))!=null){ + final Object lockTimeout; + if ( ( lockTimeout = properties.get( JPA_LOCK_TIMEOUT ) ) != null ) { query.setHint( JPA_LOCK_TIMEOUT, lockTimeout ); } } @@ -685,7 +686,10 @@ private Iterable listeners(EventType type) { } private EventListenerGroup eventListenerGroup(EventType type) { - return getFactory().getServiceRegistry().getService( EventListenerRegistry.class ).getEventListenerGroup( type ); + if ( this.eventListenerRegistry == null ) { + this.eventListenerRegistry = getFactory().getServiceRegistry().getService( EventListenerRegistry.class ); + } + return eventListenerRegistry.getEventListenerGroup( type ); } @@ -763,7 +767,7 @@ private void fireLock(Object object, LockOptions options) { private void fireLock(LockEvent event) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( LockEventListener listener : listeners( EventType.LOCK ) ) { listener.onLock( event ); } @@ -817,7 +821,7 @@ private void firePersist(PersistEvent event) { } private void firePersist(Map copiedAlready, PersistEvent event) { - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); try { for ( PersistEventListener listener : listeners( EventType.PERSIST ) ) { @@ -855,7 +859,7 @@ public void persistOnFlush(String entityName, Object object, Map copiedAlready) private void firePersistOnFlush(Map copiedAlready, PersistEvent event) { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( PersistEventListener listener : listeners( EventType.PERSIST_ONFLUSH ) ) { listener.onPersist( event, copiedAlready ); } @@ -918,7 +922,7 @@ private Object fireMerge(MergeEvent event) { private void fireMerge(Map copiedAlready, MergeEvent event) { try { - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( MergeEventListener listener : listeners( EventType.MERGE ) ) { listener.onMerge( event, copiedAlready ); } @@ -1006,7 +1010,7 @@ private void logRemoveOrphanBeforeUpdates(String timing, String entityName, Obje private void fireDelete(DeleteEvent event) { try{ - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( DeleteEventListener listener : listeners( EventType.DELETE ) ) { listener.onDelete( event ); } @@ -1028,10 +1032,10 @@ private void fireDelete(DeleteEvent event) { private void fireDelete(DeleteEvent event, Set transientEntities) { try{ - checkTransactionSynchStatus(); - for ( DeleteEventListener listener : listeners( EventType.DELETE ) ) { - listener.onDelete( event, transientEntities ); - } + pulseTransactionCoordinator(); + for ( DeleteEventListener listener : listeners( EventType.DELETE ) ) { + listener.onDelete( event, transientEntities ); + } } catch ( ObjectDeletedException sse ) { throw exceptionConverter.convert( new IllegalArgumentException( sse ) ); @@ -1112,7 +1116,7 @@ public Object immediateLoad(String entityName, Serializable id) throws Hibernate LoadEvent event = loadEvent; loadEvent = null; event = recycleEventInstance( event, id, entityName ); - fireLoad( event, LoadEventListener.IMMEDIATE_LOAD ); + fireLoadNoChecks( event, LoadEventListener.IMMEDIATE_LOAD ); Object result = event.getResult(); if ( loadEvent == null ) { event.setEntityClassName( null ); @@ -1123,25 +1127,31 @@ public Object immediateLoad(String entityName, Serializable id) throws Hibernate } return result; } - @Override - public final Object internalLoad(String entityName, Serializable id, boolean eager, boolean nullable) - throws HibernateException { - // todo : remove - LoadEventListener.LoadType type = nullable - ? LoadEventListener.INTERNAL_LOAD_NULLABLE - : eager - ? LoadEventListener.INTERNAL_LOAD_EAGER - : LoadEventListener.INTERNAL_LOAD_LAZY; + public final Object internalLoad( + String entityName, + Serializable id, + boolean eager, + boolean nullable) { + final LoadEventListener.LoadType type; + if ( nullable ) { + type = LoadEventListener.INTERNAL_LOAD_NULLABLE; + } + else { + type = eager + ? LoadEventListener.INTERNAL_LOAD_EAGER + : LoadEventListener.INTERNAL_LOAD_LAZY; + } LoadEvent event = loadEvent; loadEvent = null; event = recycleEventInstance( event, id, entityName ); - fireLoad( event, type ); + fireLoadNoChecks( event, type ); Object result = event.getResult(); if ( !nullable ) { UnresolvableObjectException.throwIfNull( result, id, entityName ); } + if ( loadEvent == null ) { event.setEntityClassName( null ); event.setEntityId( null ); @@ -1252,16 +1262,25 @@ public SimpleNaturalIdLoadAccess bySimpleNaturalId(Class entityClass) private void fireLoad(LoadEvent event, LoadType loadType) { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + fireLoadNoChecks( event, loadType ); + delayedAfterCompletion(); + } + + //Performance note: + // This version of #fireLoad is meant to be invoked by internal methods only, + // so to skip the session open, transaction synch, etc.. checks, + // which have been proven to be not particularly cheap: + // it seems they prevent these hot methods from being inlined. + private void fireLoadNoChecks(LoadEvent event, LoadType loadType) { + pulseTransactionCoordinator(); for ( LoadEventListener listener : listeners( EventType.LOAD ) ) { listener.onLoad( event, loadType ); } - delayedAfterCompletion(); } private void fireResolveNaturalId(ResolveNaturalIdEvent event) { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( ResolveNaturalIdEventListener listener : listeners( EventType.RESOLVE_NATURAL_ID ) ) { listener.onResolveNaturalId( event ); } @@ -1321,7 +1340,7 @@ private void fireRefresh(RefreshEvent event) { } } } - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( RefreshEventListener listener : listeners( EventType.REFRESH ) ) { listener.onRefresh( event ); } @@ -1342,11 +1361,10 @@ private void fireRefresh(RefreshEvent event) { private void fireRefresh(Map refreshedAlready, RefreshEvent event) { try { - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( RefreshEventListener listener : listeners( EventType.REFRESH ) ) { listener.onRefresh( event, refreshedAlready ); } - delayedAfterCompletion(); } catch (RuntimeException e) { throw exceptionConverter.convert( e ); @@ -1372,7 +1390,7 @@ public void replicate(String entityName, Object obj, ReplicationMode replication private void fireReplicate(ReplicateEvent event) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( ReplicateEventListener listener : listeners( EventType.REPLICATE ) ) { listener.onReplicate( event ); } @@ -1393,7 +1411,7 @@ public void evict(Object object) throws HibernateException { private void fireEvict(EvictEvent event) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); for ( EvictEventListener listener : listeners( EventType.EVICT ) ) { listener.onEvict( event ); } @@ -1420,7 +1438,7 @@ protected boolean autoFlushIfRequired(Set querySpaces) throws HibernateException @Override public boolean isDirty() throws HibernateException { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); log.debug( "Checking session dirtiness" ); if ( actionQueue.areInsertionsOrDeletionsQueued() ) { log.debug( "Session dirty (scheduled updates and insertions)" ); @@ -1441,8 +1459,8 @@ public void flush() throws HibernateException { } private void doFlush() { - checkTransactionNeeded(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); + checkTransactionNeededForUpdateOperation(); try { if ( persistenceContext.getCascadeLevel() > 0 ) { @@ -1490,7 +1508,7 @@ public void forceFlush(EntityEntry entityEntry) throws HibernateException { @Override public List list(String query, QueryParameters queryParameters) throws HibernateException { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); queryParameters.validateParameters(); HQLQueryPlan plan = queryParameters.getQueryPlan(); @@ -1500,7 +1518,7 @@ public List list(String query, QueryParameters queryParameters) throws Hibernate autoFlushIfRequired( plan.getQuerySpaces() ); - List results = Collections.EMPTY_LIST; + final List results; boolean success = false; dontFlushFromFind++; //stops flush being called multiple times if this method is recursively called @@ -1519,7 +1537,7 @@ public List list(String query, QueryParameters queryParameters) throws Hibernate @Override public int executeUpdate(String query, QueryParameters queryParameters) throws HibernateException { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); queryParameters.validateParameters(); HQLQueryPlan plan = getQueryPlan( query, false ); autoFlushIfRequired( plan.getQuerySpaces() ); @@ -1580,7 +1598,7 @@ public int executeNativeUpdate( NativeSQLQuerySpecification nativeQuerySpecification, QueryParameters queryParameters) throws HibernateException { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); queryParameters.validateParameters(); NativeSQLQueryPlan plan = getNativeQueryPlan( nativeQuerySpecification ); @@ -1603,7 +1621,7 @@ public int executeNativeUpdate( @Override public Iterator iterate(String query, QueryParameters queryParameters) throws HibernateException { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); queryParameters.validateParameters(); HQLQueryPlan plan = queryParameters.getQueryPlan(); @@ -1626,7 +1644,7 @@ public Iterator iterate(String query, QueryParameters queryParameters) throws Hi @Override public ScrollableResultsImplementor scroll(String query, QueryParameters queryParameters) throws HibernateException { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); HQLQueryPlan plan = queryParameters.getQueryPlan(); if ( plan == null ) { @@ -1648,7 +1666,7 @@ public ScrollableResultsImplementor scroll(String query, QueryParameters queryPa @Override public org.hibernate.query.Query createFilter(Object collection, String queryString) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); CollectionFilterImpl filter = new CollectionFilterImpl( queryString, collection, @@ -1672,7 +1690,7 @@ public Object instantiate(String entityName, Serializable id) throws HibernateEx @Override public Object instantiate(EntityPersister persister, Serializable id) throws HibernateException { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); Object result = getInterceptor().instantiate( persister.getEntityName(), persister.getEntityMetamodel().getEntityMode(), @@ -1721,7 +1739,7 @@ public Serializable getIdentifier(Object object) throws HibernateException { if ( li.getSession() != this ) { throw new TransientObjectException( "The proxy was not associated with this session" ); } - return li.getIdentifier(); + return li.getInternalIdentifier(); } else { EntityEntry entry = persistenceContext.getEntry( object ); @@ -1749,7 +1767,7 @@ public Serializable getContextEntityIdentifier(Object object) { } private Serializable getProxyIdentifier(Object proxy) { - return ( (HibernateProxy) proxy ).getHibernateLazyInitializer().getIdentifier(); + return ( (HibernateProxy) proxy ).getHibernateLazyInitializer().getInternalIdentifier(); } private FilterQueryPlan getFilterQueryPlan( @@ -1765,6 +1783,8 @@ private FilterQueryPlan getFilterQueryPlan( final CollectionPersister roleBeforeFlush = ( entry == null ) ? null : entry.getLoadedPersister(); FilterQueryPlan plan = null; + final Map enabledFilters = getLoadQueryInfluencers().getEnabledFilters(); + final SessionFactoryImplementor factory = getFactory(); if ( roleBeforeFlush == null ) { // if it was previously unreferenced, we need to flush in order to // get its state into the database in order to execute query @@ -1774,21 +1794,21 @@ private FilterQueryPlan getFilterQueryPlan( if ( roleAfterFlush == null ) { throw new QueryException( "The collection was unreferenced" ); } - plan = getFactory().getQueryPlanCache().getFilterQueryPlan( + plan = factory.getQueryPlanCache().getFilterQueryPlan( filter, roleAfterFlush.getRole(), shallow, - getLoadQueryInfluencers().getEnabledFilters() + enabledFilters ); } else { // otherwise, we only need to flush if there are in-memory changes // to the queried tables - plan = getFactory().getQueryPlanCache().getFilterQueryPlan( + plan = factory.getQueryPlanCache().getFilterQueryPlan( filter, roleBeforeFlush.getRole(), shallow, - getLoadQueryInfluencers().getEnabledFilters() + enabledFilters ); if ( autoFlushIfRequired( plan.getQuerySpaces() ) ) { // might need to run a different filter entirely after the flush @@ -1799,11 +1819,11 @@ private FilterQueryPlan getFilterQueryPlan( if ( roleAfterFlush == null ) { throw new QueryException( "The collection was dereferenced" ); } - plan = getFactory().getQueryPlanCache().getFilterQueryPlan( + plan = factory.getQueryPlanCache().getFilterQueryPlan( filter, roleAfterFlush.getRole(), shallow, - getLoadQueryInfluencers().getEnabledFilters() + enabledFilters ); } } @@ -1825,7 +1845,7 @@ private FilterQueryPlan getFilterQueryPlan( @Override public List listFilter(Object collection, String filter, QueryParameters queryParameters) { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); FilterQueryPlan plan = getFilterQueryPlan( collection, filter, queryParameters, false ); List results = Collections.EMPTY_LIST; @@ -1846,7 +1866,7 @@ public List listFilter(Object collection, String filter, QueryParameters queryPa @Override public Iterator iterateFilter(Object collection, String filter, QueryParameters queryParameters) { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); FilterQueryPlan plan = getFilterQueryPlan( collection, filter, queryParameters, true ); Iterator itr = plan.performIterate( queryParameters, this ); delayedAfterCompletion(); @@ -1891,7 +1911,7 @@ public ScrollableResultsImplementor scroll(Criteria criteria, ScrollMode scrollM CriteriaImpl criteriaImpl = (CriteriaImpl) criteria; checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); String entityName = criteriaImpl.getEntityOrClassName(); CriteriaLoader loader = new CriteriaLoader( @@ -2044,7 +2064,7 @@ private OuterJoinLoadable getOuterJoinLoadable(String entityName) throws Mapping @Override public boolean contains(Object object) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); if ( object == null ) { return false; @@ -2108,7 +2128,7 @@ public boolean contains(Object object) { @Override public boolean contains(String entityName, Object object) { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); if ( object == null ) { return false; @@ -2240,7 +2260,7 @@ public SessionFactoryImplementor getSessionFactory() { @Override public void initializeCollection(PersistentCollection collection, boolean writing) { checkOpenOrWaitingForAutoClose(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); InitializeCollectionEvent event = new InitializeCollectionEvent( collection, this ); for ( InitializeCollectionEventListener listener : listeners( EventType.INIT_COLLECTION ) ) { listener.onInitializeCollection( event ); @@ -2347,13 +2367,13 @@ public PersistenceContext getPersistenceContext() { @Override public SessionStatistics getStatistics() { - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); return new SessionStatisticsImpl( this ); } @Override public boolean isEventSource() { - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); return true; } @@ -2422,21 +2442,21 @@ public LoadQueryInfluencers getLoadQueryInfluencers() { @Override public Filter getEnabledFilter(String filterName) { - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); return loadQueryInfluencers.getEnabledFilter( filterName ); } @Override public Filter enableFilter(String filterName) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); return loadQueryInfluencers.enableFilter( filterName ); } @Override public void disableFilter(String filterName) { checkOpen(); - checkTransactionSynchStatus(); + pulseTransactionCoordinator(); loadQueryInfluencers.disableFilter( filterName ); } @@ -2473,13 +2493,20 @@ public LobHelper getLobHelper() { private transient LobHelperImpl lobHelper; + private Transaction getTransactionIfAccessible() { + // We do not want an exception to be thrown if the transaction + // is not accessible. If the transaction is not accessible, + // then return null. + return isTransactionAccessible() ? accessTransaction() : null; + } + @Override public void beforeTransactionCompletion() { log.tracef( "SessionImpl#beforeTransactionCompletion()" ); flushBeforeTransactionCompletion(); actionQueue.beforeTransactionCompletion(); try { - getInterceptor().beforeTransactionCompletion( getCurrentTransaction() ); + getInterceptor().beforeTransactionCompletion( getTransactionIfAccessible() ); } catch (Throwable t) { log.exceptionInBeforeTransactionCompletionInterceptor( t ); @@ -2507,7 +2534,7 @@ public void afterTransactionCompletion(boolean successful, boolean delayed) { } try { - getInterceptor().afterTransactionCompletion( getCurrentTransaction() ); + getInterceptor().afterTransactionCompletion( getTransactionIfAccessible() ); } catch (Throwable t) { log.exceptionInAfterTransactionCompletionInterceptor( t ); @@ -2728,7 +2755,7 @@ public void beforeCompletion() { } actionQueue.beforeTransactionCompletion(); try { - getInterceptor().beforeTransactionCompletion( getCurrentTransaction() ); + getInterceptor().beforeTransactionCompletion( getTransactionIfAccessible() ); } catch (Throwable t) { log.exceptionInBeforeTransactionCompletionInterceptor( t ); @@ -3270,7 +3297,7 @@ public void startTransactionBoundary() { @Override public void afterTransactionBegin() { checkOpenOrWaitingForAutoClose(); - getInterceptor().afterTransactionBegin( getCurrentTransaction() ); + getInterceptor().afterTransactionBegin( getTransactionIfAccessible() ); } @Override @@ -3474,7 +3501,7 @@ public T find(Class entityClass, Object primaryKey, LockModeType lockMode if ( lockModeType != null ) { if ( !LockModeType.NONE.equals( lockModeType) ) { - checkTransactionNeeded(); + checkTransactionNeededForUpdateOperation(); } lockOptions = buildLockOptions( lockModeType, properties ); loadAccess.with( lockOptions ); @@ -3504,8 +3531,14 @@ public T find(Class entityClass, Object primaryKey, LockModeType lockMode throw exceptionConverter.convert( new IllegalArgumentException( e.getMessage(), e ) ); } catch ( JDBCException e ) { - if ( accessTransaction().getRollbackOnly() ) { - // assume this is the similar to the WildFly / IronJacamar "feature" described under HHH-12472 + if ( accessTransaction().isActive() && accessTransaction().getRollbackOnly() ) { + // Assume this is the similar to the WildFly / IronJacamar "feature" described under HHH-12472. + // Just log the exception and return null. + if ( log.isDebugEnabled() ) { + log.debug( "JDBCException was thrown for a transaction marked for rollback; " + + "this is probably due to an operation failing fast due to the " + + "transaction marked for rollback.", e ); + } return null; } else { @@ -3547,10 +3580,8 @@ private CacheStoreMode determineCacheStoreMode(Map settings) { return ( CacheStoreMode ) settings.get( JPA_SHARED_CACHE_STORE_MODE ); } - private void checkTransactionNeeded() { - if ( disallowOutOfTransactionUpdateOperations && !isTransactionInProgress() ) { - throw new TransactionRequiredException( "no transaction is in progress" ); - } + private void checkTransactionNeededForUpdateOperation() { + checkTransactionNeededForUpdateOperation( "no transaction is in progress" ); } @Override @@ -3576,7 +3607,7 @@ public void lock(Object entity, LockModeType lockModeType) { @Override public void lock(Object entity, LockModeType lockModeType, Map properties) { checkOpen(); - checkTransactionNeeded(); + checkTransactionNeededForUpdateOperation(); if ( !contains( entity ) ) { throw new IllegalArgumentException( "entity not in the persistence context" ); @@ -3618,7 +3649,7 @@ public void refresh(Object entity, LockModeType lockModeType, Map Generic type of values to coalesce + * + * @return The first non-empty value, or null if all values were empty + */ + public static T coalesce(T... values) { + if ( values == null ) { + return null; + } + for ( T value : values ) { + if ( value != null ) { + if ( String.class.isInstance( value ) ) { + if ( !( (String) value ).isEmpty() ) { + return value; + } + } + else { + return value; + } + } + } + return null; + } + + /** + * Find the first non-null value supplied by the given suppliers + */ + public static T coalesceSuppliedValues(Supplier... valueSuppliers) { + if ( valueSuppliers == null ) { + return null; + } + + for ( Supplier valueSupplier : valueSuppliers ) { + final T value = valueSupplier.get(); + if ( value != null ) { + return value; + } + } + + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java index ac6db99f0ff81c1eb0fe73af33f2f7fa07024af0..217767a2e4b2c14ff6d16975d0036c497cad752b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java @@ -18,6 +18,7 @@ import org.hibernate.dialect.Dialect; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.loader.internal.AliasConstantsHelper; public final class StringHelper { @@ -288,7 +289,7 @@ public static String qualifier(String qualifiedName) { /** * Collapses a name. Mainly intended for use with classnames, where an example might serve best to explain. * Imagine you have a class named 'org.hibernate.internal.util.StringHelper'; calling collapse on that - * classname will result in 'o.h.u.StringHelper'. + * classname will result in 'o.h.u.StringHelper'. * * @param name The name to collapse. * @@ -582,8 +583,7 @@ public static String generateAlias(String description) { */ public static String generateAlias(String description, int unique) { return generateAliasRoot( description ) - + Integer.toString( unique ) - + '_'; + + AliasConstantsHelper.get( unique ); } /** @@ -708,12 +708,15 @@ public static String unquote(String name) { * * @return True if quoted, false otherwise */ - public static boolean isQuoted(String name, Dialect dialect) { - return name != null && name.length() != 0 - && ( ( name.charAt( 0 ) == '`' && name.charAt( name.length() - 1 ) == '`' ) - || ( name.charAt( 0 ) == '"' && name.charAt( name.length() - 1 ) == '"' ) - || ( name.charAt( 0 ) == dialect.openQuote() - && name.charAt( name.length() - 1 ) == dialect.closeQuote() ) ); + public static boolean isQuoted(final String name, final Dialect dialect) { + if ( name == null || name.isEmpty() ) { + return false; + } + final char first = name.charAt( 0 ); + final char last = name.charAt( name.length() - 1 ); + + return ( ( first == last ) && ( first == '`' || first == '"' ) ) + || ( first == dialect.openQuote() && last == dialect.closeQuote() ); } /** @@ -736,15 +739,32 @@ public static String unquote(String name, Dialect dialect) { * * @return The unquoted versions. */ - public static String[] unquote(String[] names, Dialect dialect) { + public static String[] unquote(final String[] names, final Dialect dialect) { if ( names == null ) { return null; } - String[] unquoted = new String[names.length]; - for ( int i = 0; i < names.length; i++ ) { - unquoted[i] = unquote( names[i], dialect ); + int failedIndex = -1; + final int length = names.length; + for ( int i = 0; i < length; i++ ) { + if ( isQuoted( names[i], dialect ) ) { + failedIndex = i; + break; + } + } + if ( failedIndex == -1 ) { + //In this case all strings are already unquoted, so return the same array as the input: + //this is a good optimisation to skip an array copy as typically either all names are consistently quoted, or none are; + //yet for safety we need to deal with mixed scenarios as well. + return names; + } + else { + String[] unquoted = new String[length]; + System.arraycopy( names, 0, unquoted, 0, failedIndex ); + for ( int i = failedIndex; i < length; i++ ) { + unquoted[i] = unquote( names[i], dialect ); + } + return unquoted; } - return unquoted; } @@ -852,4 +872,59 @@ public static String join(T[] values, Renderer renderer) { public interface Renderer { String render(T value); } + + /** + * @param firstExpression the first expression + * @param secondExpression the second expression + * @return if {@code firstExpression} and {@code secondExpression} are both non-empty, + * then "( " + {@code firstExpression} + " ) and ( " + {@code secondExpression} + " )" is returned; + * if {@code firstExpression} is non-empty and {@code secondExpression} is empty, + * then {@code firstExpression} is returned; + * if {@code firstExpression} is empty and {@code secondExpression} is non-empty, + * then {@code secondExpression} is returned; + * if both {@code firstExpression} and {@code secondExpression} are empty, then null is returned. + */ + public static String getNonEmptyOrConjunctionIfBothNonEmpty( String firstExpression, String secondExpression ) { + final boolean isFirstExpressionNonEmpty = StringHelper.isNotEmpty( firstExpression ); + final boolean isSecondExpressionNonEmpty = StringHelper.isNotEmpty( secondExpression ); + if ( isFirstExpressionNonEmpty && isSecondExpressionNonEmpty ) { + final StringBuilder buffer = new StringBuilder(); + buffer.append( "( " ) + .append( firstExpression ) + .append( " ) and ( ") + .append( secondExpression ) + .append( " )" ); + return buffer.toString(); + } + else if ( isFirstExpressionNonEmpty ) { + return firstExpression; + } + else if ( isSecondExpressionNonEmpty ) { + return secondExpression; + } + else { + return null; + } + } + + /** + * Return the interned form of a String, or null if the parameter is null. + *

      + * Use with caution: excessive interning is known to cause issues. + * Best to use only with strings which are known to be long lived constants, + * and for which the chances of being actual duplicates is proven. + * (Even better: avoid needing interning by design changes such as reusing + * the known reference) + * @param string The string to intern. + * @return The interned string. + */ + public static String safeInterning(final String string) { + if ( string == null ) { + return null; + } + else { + return string.intern(); + } + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java index 4576328131ebaf06641f99fd73e5fce3389893ca..a7f48dd26e2bfd0deec3f73d475a8ff6e0b45f8a 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java @@ -35,6 +35,12 @@ public static int indexOf(Object[] array, Object object) { return -1; } + public static T[] filledArray(T value, Class valueJavaType, int size) { + final T[] array = (T[]) Array.newInstance( valueJavaType, size ); + Arrays.fill( array, value ); + return array; + } + public static String[] toStringArray(Object[] objects) { int length = objects.length; String[] result = new String[length]; diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/IdentityMap.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/IdentityMap.java index fbd9ac38267e8d19c4324dd3a8fec8776a8fa5bd..eeda7b1ab1e122c251aa71db4dcc8e30c516316c 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/IdentityMap.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/IdentityMap.java @@ -13,16 +13,17 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; /** * A Map where keys are compared by object identity, * rather than equals(). */ public final class IdentityMap implements Map { - private final Map,V> map; + + private final LinkedHashMap,V> map; @SuppressWarnings( {"unchecked"}) - private transient Entry,V>[] entryArray = new Entry[0]; - private transient boolean dirty; + private transient Map.Entry,V>[] entryArray = null; /** * Return a new instance of this class, with iteration @@ -32,7 +33,7 @@ public final class IdentityMap implements Map { * @return The map */ public static IdentityMap instantiateSequenced(int size) { - return new IdentityMap( new LinkedHashMap,V>( size ) ); + return new IdentityMap( new LinkedHashMap<>( size << 1, 0.6f ) ); } /** @@ -40,9 +41,8 @@ public static IdentityMap instantiateSequenced(int size) { * * @param underlyingMap The delegate map. */ - private IdentityMap(Map,V> underlyingMap) { + private IdentityMap(LinkedHashMap,V> underlyingMap) { map = underlyingMap; - dirty = true; } /** @@ -57,6 +57,11 @@ public static Map.Entry[] concurrentEntries(Map map) { return ( (IdentityMap) map ).entryArray(); } + public static void onEachKey(Map map, Consumer consumer) { + final IdentityMap identityMap = (IdentityMap) map; + identityMap.map.forEach( (kIdentityKey, v) -> consumer.accept( kIdentityKey.key ) ); + } + public Iterator keyIterator() { return new KeyIterator( map.keySet().iterator() ); } @@ -79,26 +84,27 @@ public boolean containsKey(Object key) { @Override public boolean containsValue(Object val) { - return map.containsValue(val); + throw new UnsupportedOperationException( "Avoid this operation: does not perform well" ); + //return map.containsValue( val ); } @Override @SuppressWarnings( {"unchecked"}) public V get(Object key) { - return map.get( new IdentityKey(key) ); + return map.get( new IdentityKey( key ) ); } @Override public V put(K key, V value) { - dirty = true; - return map.put( new IdentityKey(key), value ); + this.entryArray = null; + return map.put( new IdentityKey( key ), value ); } @Override @SuppressWarnings( {"unchecked"}) public V remove(Object key) { - dirty = true; - return map.remove( new IdentityKey(key) ); + this.entryArray = null; + return map.remove( new IdentityKey( key ) ); } @Override @@ -110,7 +116,6 @@ public void putAll(Map otherMap) { @Override public void clear() { - dirty = true; entryArray = null; map.clear(); } @@ -118,6 +123,7 @@ public void clear() { @Override public Set keySet() { // would need an IdentitySet for this! + // (and we just don't use this method so it's ok) throw new UnsupportedOperationException(); } @@ -130,22 +136,21 @@ public Collection values() { public Set> entrySet() { Set> set = new HashSet>( map.size() ); for ( Entry, V> entry : map.entrySet() ) { - set.add( new IdentityMapEntry( entry.getKey().getRealKey(), entry.getValue() ) ); + set.add( new IdentityMapEntry( entry.getKey().key, entry.getValue() ) ); } return set; } @SuppressWarnings( {"unchecked"}) public Map.Entry[] entryArray() { - if (dirty) { + if ( entryArray == null ) { entryArray = new Map.Entry[ map.size() ]; - Iterator itr = map.entrySet().iterator(); - int i=0; + final Iterator, V>> itr = map.entrySet().iterator(); + int i = 0; while ( itr.hasNext() ) { - Map.Entry me = (Map.Entry) itr.next(); - entryArray[i++] = new IdentityMapEntry( ( (IdentityKey) me.getKey() ).key, me.getValue() ); + final Entry, V> me = itr.next(); + entryArray[i++] = new IdentityMapEntry( me.getKey().key, me.getValue() ); } - dirty = false; } return entryArray; } @@ -155,7 +160,7 @@ public String toString() { return map.toString(); } - static final class KeyIterator implements Iterator { + private static final class KeyIterator implements Iterator { private final Iterator> identityKeyIterator; private KeyIterator(Iterator> iterator) { @@ -167,7 +172,7 @@ public boolean hasNext() { } public K next() { - return identityKeyIterator.next().getRealKey(); + return identityKeyIterator.next().key; } public void remove() { @@ -175,9 +180,11 @@ public void remove() { } } - public static final class IdentityMapEntry implements java.util.Map.Entry { + + private static final class IdentityMapEntry implements java.util.Map.Entry { + private final K key; - private V value; + private final V value; IdentityMapEntry(final K key, final V value) { this.key=key; @@ -193,21 +200,16 @@ public V getValue() { } public V setValue(final V value) { - V result = this.value; - this.value = value; - return result; + throw new UnsupportedOperationException(); } } /** - * We need to base the identity on {@link System#identityHashCode(Object)} but - * attempt to lazily initialize and cache this value: being a native invocation - * it is an expensive value to retrieve. + * We need to base the identity on {@link System#identityHashCode(Object)} */ - public static final class IdentityKey implements Serializable { + private static final class IdentityKey implements Serializable { private final K key; - private int hash; IdentityKey(K key) { this.key = key; @@ -221,21 +223,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - if ( this.hash == 0 ) { - //We consider "zero" as non-initialized value - final int newHash = System.identityHashCode( key ); - if ( newHash == 0 ) { - //So make sure we don't store zeros as it would trigger initialization again: - //any value is fine as long as we're deterministic. - this.hash = -1; - return -1; - } - else { - this.hash = newHash; - return newHash; - } - } - return hash; + return System.identityHashCode( key ); } @Override @@ -243,9 +231,6 @@ public String toString() { return key.toString(); } - public K getRealKey() { - return key; - } } } diff --git a/hibernate-core/src/main/java/org/hibernate/jmx/internal/JmxServiceImpl.java b/hibernate-core/src/main/java/org/hibernate/jmx/internal/JmxServiceImpl.java index 64b7967e1cec62d7c3b62ef3938bbede5c28f59a..228801887451c4c85baca905825ac6ef37b150b8 100644 --- a/hibernate-core/src/main/java/org/hibernate/jmx/internal/JmxServiceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jmx/internal/JmxServiceImpl.java @@ -19,6 +19,7 @@ import org.hibernate.cfg.Environment; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.jmx.spi.JmxService; import org.hibernate.service.Service; @@ -107,6 +108,12 @@ public void stop() { @Override public void registerService(Manageable service, Class serviceRole) { + if ( service == null ) { + return; + } + + DeprecationLogger.DEPRECATION_LOGGER.deprecatedJmxManageableServiceRegistration( service.getClass().getName() ); + if ( OptionallyManageable.class.isInstance( service ) ) { for ( Manageable realManageable : ( (OptionallyManageable) service ).getRealManageables() ) { registerService( realManageable,serviceRole ); @@ -139,6 +146,8 @@ public void registerService(Manageable service, Class service @Override public void registerMBean(ObjectName objectName, Object mBean) { + DeprecationLogger.DEPRECATION_LOGGER.deprecatedJmxBeanRegistration( mBean.getClass().getName() ); + MBeanServer mBeanServer = findServer(); if ( mBeanServer == null ) { if ( startedServer ) { diff --git a/hibernate-core/src/main/java/org/hibernate/jmx/internal/JmxServiceInitiator.java b/hibernate-core/src/main/java/org/hibernate/jmx/internal/JmxServiceInitiator.java index 6c16036854d7a9a1a55caf0b43a5645e8ea56761..b8c371bb47737bf40156c3f548b25109d8e2fe96 100644 --- a/hibernate-core/src/main/java/org/hibernate/jmx/internal/JmxServiceInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/jmx/internal/JmxServiceInitiator.java @@ -10,6 +10,7 @@ import org.hibernate.boot.registry.StandardServiceInitiator; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.jmx.spi.JmxService; import org.hibernate.service.spi.ServiceRegistryImplementor; @@ -29,8 +30,10 @@ public Class getServiceInitiated() { @Override public JmxService initiateService(Map configurationValues, ServiceRegistryImplementor registry) { - return ConfigurationHelper.getBoolean( AvailableSettings.JMX_ENABLED, configurationValues, false ) - ? new JmxServiceImpl( configurationValues ) - : DisabledJmxServiceImpl.INSTANCE; + if ( ConfigurationHelper.getBoolean( AvailableSettings.JMX_ENABLED, configurationValues, false ) ) { + DeprecationLogger.DEPRECATION_LOGGER.deprecatedJmxSupport( AvailableSettings.JMX_ENABLED ); + return new JmxServiceImpl( configurationValues ); + } + return DisabledJmxServiceImpl.INSTANCE; } } diff --git a/hibernate-core/src/main/java/org/hibernate/jmx/spi/JmxService.java b/hibernate-core/src/main/java/org/hibernate/jmx/spi/JmxService.java index 5050696583c06a54239828ce4b902d2db8bdc5bc..94e3383c3dd4ad3f6d2ce20ab0b52ef5ca8871c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/jmx/spi/JmxService.java +++ b/hibernate-core/src/main/java/org/hibernate/jmx/spi/JmxService.java @@ -15,7 +15,11 @@ * Service providing simplified access to JMX related features needed by Hibernate. * * @author Steve Ebersole + * + * @deprecated Scheduled for removal in 6.0; see https://hibernate.atlassian.net/browse/HHH-14847 + * and https://hibernate.atlassian.net/browse/HHH-14846 */ +@Deprecated public interface JmxService extends Service { /** * Handles registration of a manageable service. diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/jpa/AvailableSettings.java index d8a9a9769599b42e458e5108bac5cda5e7408ccc..705343a4c48d1339d371cb996356d5e4eed3bf2e 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/AvailableSettings.java @@ -236,7 +236,7 @@ public interface AvailableSettings { * enabled - Do the build *

    • *
    • - * disabled - Do not so the build + * disabled - Do not do the build *
    • *
    • * ignoreUnsupported - Do the build, but ignore any non-JPA features that would otherwise @@ -283,14 +283,14 @@ public interface AvailableSettings { /** * Caching configuration should follow the following pattern - * hibernate.ejb.classcache. usage[, region] + * {@code hibernate.ejb.classcache. usage[, region]} * where usage is the cache strategy used and region the cache region name */ String CLASS_CACHE_PREFIX = "hibernate.ejb.classcache"; /** * Caching configuration should follow the following pattern - * hibernate.ejb.collectioncache.. usage[, region] + * {@code hibernate.ejb.collectioncache.. usage[, region]} * where usage is the cache strategy used and region the cache region name */ String COLLECTION_CACHE_PREFIX = "hibernate.ejb.collectioncache"; diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/QueryHints.java b/hibernate-core/src/main/java/org/hibernate/jpa/QueryHints.java index 45832904d4eb463929fb6d3b003bd7da663ae95a..dfc654293dcf963309cdcedebbd0732e235acaac 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/QueryHints.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/QueryHints.java @@ -19,6 +19,7 @@ import static org.hibernate.annotations.QueryHints.FOLLOW_ON_LOCKING; import static org.hibernate.annotations.QueryHints.LOADGRAPH; import static org.hibernate.annotations.QueryHints.NATIVE_LOCKMODE; +import static org.hibernate.annotations.QueryHints.NATIVE_SPACES; import static org.hibernate.annotations.QueryHints.PASS_DISTINCT_THROUGH; import static org.hibernate.annotations.QueryHints.READ_ONLY; import static org.hibernate.annotations.QueryHints.TIMEOUT_HIBERNATE; @@ -26,8 +27,6 @@ /** * Defines the supported JPA query hints - * - * @author Steve Ebersole */ public class QueryHints { /** @@ -105,10 +104,13 @@ public class QueryHints { public static final String HINT_PASS_DISTINCT_THROUGH = PASS_DISTINCT_THROUGH; + public static final String HINT_NATIVE_SPACES = NATIVE_SPACES; + + private static final Set HINTS = buildHintsSet(); private static Set buildHintsSet() { - HashSet hints = new HashSet(); + HashSet hints = new HashSet<>(); hints.add( HINT_TIMEOUT ); hints.add( SPEC_HINT_TIMEOUT ); hints.add( HINT_COMMENT ); @@ -121,6 +123,7 @@ private static Set buildHintsSet() { hints.add( HINT_NATIVE_LOCKMODE ); hints.add( HINT_FETCHGRAPH ); hints.add( HINT_LOADGRAPH ); + hints.add( HINT_NATIVE_SPACES ); return java.util.Collections.unmodifiableSet( hints ); } 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 965cf8c392f0bc01c996d331883bdcdbcd3da359..734b84d75982f14b621e9dae55edafd3cc5cff19 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 @@ -27,11 +27,9 @@ import org.hibernate.SessionFactory; import org.hibernate.SessionFactoryObserver; import org.hibernate.boot.CacheRegionDefinition; -import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.SessionFactoryBuilder; import org.hibernate.boot.archive.scan.internal.StandardScanOptions; -import org.hibernate.boot.cfgxml.internal.ConfigLoader; import org.hibernate.boot.cfgxml.spi.CfgXmlAccessService; import org.hibernate.boot.cfgxml.spi.LoadedConfig; import org.hibernate.boot.cfgxml.spi.MappingReference; @@ -60,7 +58,9 @@ import org.hibernate.id.factory.spi.MutableIdentifierGeneratorFactory; import org.hibernate.integrator.spi.Integrator; import org.hibernate.internal.EntityManagerMessageLogger; +import org.hibernate.internal.util.NullnessHelper; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; import org.hibernate.jpa.boot.spi.IntegratorProvider; @@ -73,6 +73,7 @@ import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl; import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl; +import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; import org.hibernate.secure.spi.GrantedPermission; import org.hibernate.secure.spi.JaccPermissionDeclarations; import org.hibernate.service.ServiceRegistry; @@ -199,24 +200,31 @@ private EntityManagerFactoryBuilderImpl( final BootstrapServiceRegistry bsr = buildBootstrapServiceRegistry( integrationSettings, providedClassLoader, providedClassLoaderService); // merge configuration sources and build the "standard" service registry - final StandardServiceRegistryBuilder ssrBuilder = new StandardServiceRegistryBuilder( bsr ); + final StandardServiceRegistryBuilder ssrBuilder = StandardServiceRegistryBuilder.forJpa( bsr ); + final MergedSettings mergedSettings = mergeSettings( persistenceUnit, integrationSettings, ssrBuilder ); + + // flush before completion validation + if ( "true".equals( mergedSettings.configurationValues.get( Environment.FLUSH_BEFORE_COMPLETION ) ) ) { + LOG.definingFlushBeforeCompletionIgnoredInHem( Environment.FLUSH_BEFORE_COMPLETION ); + mergedSettings.configurationValues.put( Environment.FLUSH_BEFORE_COMPLETION, "false" ); + } + + // keep the merged config values for phase-2 this.configurationValues = mergedSettings.getConfigurationValues(); // Build the "standard" service registry ssrBuilder.applySettings( configurationValues ); - configure( ssrBuilder ); + this.standardServiceRegistry = ssrBuilder.build(); - configure( standardServiceRegistry, mergedSettings ); + + configureIdentifierGenerators( standardServiceRegistry ); final MetadataSources metadataSources = new MetadataSources( bsr ); - List attributeConverterDefinitions = populate( - metadataSources, - mergedSettings, - standardServiceRegistry - ); + List attributeConverterDefinitions = applyMappingResources( metadataSources ); + this.metamodelBuilder = (MetadataBuilderImplementor) metadataSources.getMetadataBuilder( standardServiceRegistry ); - populate( metamodelBuilder, mergedSettings, standardServiceRegistry, attributeConverterDefinitions ); + applyMetamodelBuilderSettings( mergedSettings, attributeConverterDefinitions ); // todo : would be nice to have MetadataBuilder still do the handling of CfgXmlAccessService here // another option is to immediately handle them here (probably in mergeSettings?) as we encounter them... @@ -308,7 +316,6 @@ else if ( metadataBuilderContributorSetting instanceof String ) { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // temporary! - @SuppressWarnings("unchecked") public Map getConfigurationValues() { return Collections.unmodifiableMap( configurationValues ); } @@ -327,7 +334,10 @@ private boolean readBooleanConfigurationValue(String propertyName) { * @param associationManagementEnabled To enable association management feature * @return An enhancement context for classes managed by this EM */ - protected EnhancementContext getEnhancementContext(final boolean dirtyTrackingEnabled, final boolean lazyInitializationEnabled, final boolean associationManagementEnabled ) { + protected EnhancementContext getEnhancementContext( + final boolean dirtyTrackingEnabled, + final boolean lazyInitializationEnabled, + final boolean associationManagementEnabled ) { return new DefaultEnhancementContext() { @Override @@ -455,62 +465,25 @@ else if ( ClassLoader.class.isInstance( classLoadersSetting ) ) { return bsrBuilder.build(); } - @SuppressWarnings("unchecked") private MergedSettings mergeSettings( PersistenceUnitDescriptor persistenceUnit, Map integrationSettings, StandardServiceRegistryBuilder ssrBuilder) { final MergedSettings mergedSettings = new MergedSettings(); - - // first, apply persistence.xml-defined settings - if ( persistenceUnit.getProperties() != null ) { - mergedSettings.configurationValues.putAll( persistenceUnit.getProperties() ); - } - - mergedSettings.configurationValues.put( PERSISTENCE_UNIT_NAME, persistenceUnit.getName() ); - - final ConfigLoader configLoader = new ConfigLoader( ssrBuilder.getBootstrapServiceRegistry() ); + mergedSettings.processPersistenceUnitDescriptorProperties( persistenceUnit ); // see if the persistence.xml settings named a Hibernate config file.... - final String cfgXmlResourceName1 = (String) mergedSettings.configurationValues.remove( CFG_FILE ); - if ( StringHelper.isNotEmpty( cfgXmlResourceName1 ) ) { - final LoadedConfig loadedCfg = configLoader.loadConfigXmlResource( cfgXmlResourceName1 ); - processConfigXml( loadedCfg, mergedSettings, ssrBuilder ); + String cfgXmlResourceName = (String) mergedSettings.configurationValues.remove( CFG_FILE ); + if ( StringHelper.isEmpty( cfgXmlResourceName ) ) { + // see if integration settings named a Hibernate config file.... + cfgXmlResourceName = (String) integrationSettings.get( CFG_FILE ); } - // see if integration settings named a Hibernate config file.... - final String cfgXmlResourceName2 = (String) integrationSettings.get( CFG_FILE ); - if ( StringHelper.isNotEmpty( cfgXmlResourceName2 ) ) { - integrationSettings.remove( CFG_FILE ); - final LoadedConfig loadedCfg = configLoader.loadConfigXmlResource( cfgXmlResourceName2 ); - processConfigXml( loadedCfg, mergedSettings, ssrBuilder ); + if ( StringHelper.isNotEmpty( cfgXmlResourceName ) ) { + processHibernateConfigXmlResources( ssrBuilder, mergedSettings, cfgXmlResourceName ); } - // finally, apply integration-supplied settings (per JPA spec, integration settings should override other sources) - for ( Map.Entry entry : integrationSettings.entrySet() ) { - if ( entry.getKey() == null ) { - continue; - } - - if ( entry.getValue() == null ) { - mergedSettings.configurationValues.remove( entry.getKey() ); - } - else { - mergedSettings.configurationValues.put( entry.getKey(), entry.getValue() ); - } - } - - if ( !mergedSettings.configurationValues.containsKey( JPA_VALIDATION_MODE ) ) { - if ( persistenceUnit.getValidationMode() != null ) { - mergedSettings.configurationValues.put( JPA_VALIDATION_MODE, persistenceUnit.getValidationMode() ); - } - } - - if ( !mergedSettings.configurationValues.containsKey( JPA_SHARED_CACHE_MODE ) ) { - if ( persistenceUnit.getSharedCacheMode() != null ) { - mergedSettings.configurationValues.put( JPA_SHARED_CACHE_MODE, persistenceUnit.getSharedCacheMode() ); - } - } + normalizeSettings( persistenceUnit, integrationSettings, mergedSettings ); final String jaccContextId = (String) mergedSettings.configurationValues.get( JACC_CONTEXT_ID ); @@ -571,22 +544,446 @@ else if ( keyString.startsWith( COLLECTION_CACHE_PREFIX ) ) { return mergedSettings; } + /** + * Handles normalizing the settings coming from multiple sources, applying proper precedences + */ @SuppressWarnings("unchecked") - private void processConfigXml( - LoadedConfig loadedConfig, + private void normalizeSettings( + PersistenceUnitDescriptor persistenceUnit, + Map integrationSettings, + MergedSettings mergedSettings) { + // make a copy so we can remove things as we process them + final HashMap integrationSettingsCopy = new HashMap<>( integrationSettings ); + + normalizeConnectionAccessUserAndPass( integrationSettingsCopy, mergedSettings ); + + normalizeTransactionCoordinator( persistenceUnit, integrationSettingsCopy, mergedSettings ); + + normalizeDataAccess( integrationSettingsCopy, mergedSettings, persistenceUnit ); + + // normalize ValidationMode + final Object intgValidationMode = integrationSettingsCopy.remove( JPA_VALIDATION_MODE ); + if ( intgValidationMode != null ) { + mergedSettings.configurationValues.put( JPA_VALIDATION_MODE, intgValidationMode ); + } + else if ( persistenceUnit.getValidationMode() != null ) { + mergedSettings.configurationValues.put( JPA_VALIDATION_MODE, persistenceUnit.getValidationMode() ); + } + + // normalize SharedCacheMode + final Object intgCacheMode = integrationSettingsCopy.remove( JPA_SHARED_CACHE_MODE ); + if ( intgCacheMode != null ) { + mergedSettings.configurationValues.put( JPA_SHARED_CACHE_MODE, intgCacheMode ); + } + else if ( persistenceUnit.getSharedCacheMode() != null ) { + mergedSettings.configurationValues.put( JPA_SHARED_CACHE_MODE, persistenceUnit.getSharedCacheMode() ); + } + + // Apply all "integration overrides" as the last step. By specification, + // these should have precedence. + // + // NOTE that this occurs after the specialized normalize calls above which remove + // any specially-handled settings. + for ( Map.Entry entry : integrationSettingsCopy.entrySet() ) { + if ( entry.getKey() == null ) { + continue; + } + + if ( entry.getValue() == null ) { + mergedSettings.configurationValues.remove( entry.getKey() ); + } + else { + mergedSettings.configurationValues.put( entry.getKey(), entry.getValue() ); + } + } + } + + /** + * Because a DataSource can be secured (requiring Hibernate to pass the USER/PASSWORD when accessing the DataSource) + * we apply precedence to the USER and PASS separately + */ + private void normalizeConnectionAccessUserAndPass( + HashMap integrationSettingsCopy, + MergedSettings mergedSettings) { + //noinspection unchecked + final Object effectiveUser = NullnessHelper.coalesceSuppliedValues( + () -> integrationSettingsCopy.remove( USER ), + () -> integrationSettingsCopy.remove( JPA_JDBC_USER ), + () -> extractPuProperty( persistenceUnit, USER ), + () -> extractPuProperty( persistenceUnit, JPA_JDBC_USER ) + ); + + //noinspection unchecked + final Object effectivePass = NullnessHelper.coalesceSuppliedValues( + () -> integrationSettingsCopy.remove( PASS ), + () -> integrationSettingsCopy.remove( JPA_JDBC_PASSWORD ), + () -> extractPuProperty( persistenceUnit, PASS ), + () -> extractPuProperty( persistenceUnit, JPA_JDBC_PASSWORD ) + ); + + if ( effectiveUser != null || effectivePass != null ) { + applyUserAndPass( effectiveUser, effectivePass, mergedSettings ); + } + } + + private T extractPuProperty(PersistenceUnitDescriptor persistenceUnit, String propertyName) { + //noinspection unchecked + return persistenceUnit.getProperties() == null ? null : (T) persistenceUnit.getProperties().get( propertyName ); + } + + @SuppressWarnings("unchecked") + private void applyUserAndPass(Object effectiveUser, Object effectivePass, MergedSettings mergedSettings) { + if ( effectiveUser != null ) { + mergedSettings.configurationValues.put( USER, effectiveUser ); + mergedSettings.configurationValues.put( JPA_JDBC_USER, effectiveUser ); + } + + if ( effectivePass != null ) { + mergedSettings.configurationValues.put( PASS, effectivePass ); + mergedSettings.configurationValues.put( JPA_JDBC_PASSWORD, effectivePass ); + } + } + + private static final String IS_JTA_TXN_COORD = "local.setting.IS_JTA_TXN_COORD"; + + @SuppressWarnings("unchecked") + private void normalizeTransactionCoordinator( + PersistenceUnitDescriptor persistenceUnit, + HashMap integrationSettingsCopy, + MergedSettings mergedSettings) { + PersistenceUnitTransactionType txnType = null; + + final Object intgTxnType = integrationSettingsCopy.remove( JPA_TRANSACTION_TYPE ); + + if ( intgTxnType != null ) { + txnType = PersistenceUnitTransactionTypeHelper.interpretTransactionType( intgTxnType ); + } + else if ( persistenceUnit.getTransactionType() != null ) { + txnType = persistenceUnit.getTransactionType(); + } + else { + final Object puPropTxnType = mergedSettings.configurationValues.get( JPA_TRANSACTION_TYPE ); + if ( puPropTxnType != null ) { + txnType = PersistenceUnitTransactionTypeHelper.interpretTransactionType( puPropTxnType ); + } + } + + if ( txnType == null ) { + // is it more appropriate to have this be based on bootstrap entry point (EE vs SE)? + LOG.debugf( "PersistenceUnitTransactionType not specified - falling back to RESOURCE_LOCAL" ); + txnType = PersistenceUnitTransactionType.RESOURCE_LOCAL; + } + + boolean hasTxStrategy = mergedSettings.configurationValues.containsKey( TRANSACTION_COORDINATOR_STRATEGY ); + final Boolean definiteJtaCoordinator; + + if ( hasTxStrategy ) { + LOG.overridingTransactionStrategyDangerous( TRANSACTION_COORDINATOR_STRATEGY ); + + // see if we can tell whether it is a JTA coordinator + final Object strategy = mergedSettings.configurationValues.get( TRANSACTION_COORDINATOR_STRATEGY ); + if ( strategy instanceof TransactionCoordinatorBuilder ) { + definiteJtaCoordinator = ( (TransactionCoordinatorBuilder) strategy ).isJta(); + } + else { + definiteJtaCoordinator = false; + } + } + else { + if ( txnType == PersistenceUnitTransactionType.JTA ) { + mergedSettings.configurationValues.put( TRANSACTION_COORDINATOR_STRATEGY, JtaTransactionCoordinatorBuilderImpl.class ); + definiteJtaCoordinator = true; + } + else if ( txnType == PersistenceUnitTransactionType.RESOURCE_LOCAL ) { + mergedSettings.configurationValues.put( TRANSACTION_COORDINATOR_STRATEGY, JdbcResourceLocalTransactionCoordinatorBuilderImpl.class ); + definiteJtaCoordinator = false; + } + else { + throw new IllegalStateException( "Could not determine TransactionCoordinator strategy to use" ); + } + } + + mergedSettings.configurationValues.put( IS_JTA_TXN_COORD, definiteJtaCoordinator ); + } + + private void normalizeDataAccess( + HashMap integrationSettingsCopy, MergedSettings mergedSettings, - StandardServiceRegistryBuilder ssrBuilder) { - if ( ! mergedSettings.configurationValues.containsKey( SESSION_FACTORY_NAME ) ) { - // there is not already a SF-name in the merged settings - final String sfName = loadedConfig.getSessionFactoryName(); - if ( sfName != null ) { - // but the cfg.xml file we are processing named one.. - mergedSettings.configurationValues.put( SESSION_FACTORY_NAME, sfName ); + PersistenceUnitDescriptor persistenceUnit) { + if ( dataSource != null ) { + applyDataSource( + dataSource, + // we don't explicitly know + null, + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + + if ( integrationSettingsCopy.containsKey( DATASOURCE ) ) { + final Object dataSourceRef = integrationSettingsCopy.remove( DATASOURCE ); + if ( dataSourceRef != null ) { + applyDataSource( + dataSourceRef, + null, + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + } + + if ( integrationSettingsCopy.containsKey( JPA_JTA_DATASOURCE ) ) { + final Object dataSourceRef = integrationSettingsCopy.remove( JPA_JTA_DATASOURCE ); + if ( dataSourceRef != null ) { + applyDataSource( + dataSourceRef, + true, + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; } } - mergedSettings.configurationValues.putAll( loadedConfig.getConfigurationValues() ); - ssrBuilder.configure( loadedConfig ); + if ( integrationSettingsCopy.containsKey( JPA_NON_JTA_DATASOURCE ) ) { + final Object dataSourceRef = integrationSettingsCopy.remove( JPA_NON_JTA_DATASOURCE ); + + applyDataSource( + dataSourceRef, + false, + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + + if ( integrationSettingsCopy.containsKey( URL ) ) { + // these have precedence over the JPA ones + final Object integrationJdbcUrl = integrationSettingsCopy.get( URL ); + if ( integrationJdbcUrl != null ) { + //noinspection unchecked + applyJdbcSettings( + integrationJdbcUrl, + NullnessHelper.coalesceSuppliedValues( + () -> ConfigurationHelper.getString( DRIVER, integrationSettingsCopy ), + () -> ConfigurationHelper.getString( JPA_JDBC_DRIVER, integrationSettingsCopy ), + () -> ConfigurationHelper.getString( DRIVER, mergedSettings.configurationValues ), + () -> ConfigurationHelper.getString( JPA_JDBC_DRIVER, mergedSettings.configurationValues ) + ), + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + } + + if ( integrationSettingsCopy.containsKey( JPA_JDBC_URL ) ) { + final Object integrationJdbcUrl = integrationSettingsCopy.get( JPA_JDBC_URL ); + + if ( integrationJdbcUrl != null ) { + //noinspection unchecked + applyJdbcSettings( + integrationJdbcUrl, + NullnessHelper.coalesceSuppliedValues( + () -> ConfigurationHelper.getString( JPA_JDBC_DRIVER, integrationSettingsCopy ), + () -> ConfigurationHelper.getString( JPA_JDBC_DRIVER, mergedSettings.configurationValues ) + ), + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + } + + if ( persistenceUnit.getJtaDataSource() != null ) { + applyDataSource( + persistenceUnit.getJtaDataSource(), + true, + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + + if ( persistenceUnit.getNonJtaDataSource() != null ) { + applyDataSource( + persistenceUnit.getNonJtaDataSource(), + false, + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + + if ( mergedSettings.configurationValues.containsKey( URL ) ) { + final Object url = mergedSettings.configurationValues.get( URL ); + + if ( url != null && ( ! ( url instanceof String ) || StringHelper.isNotEmpty( (String) url ) ) ) { + applyJdbcSettings( + url, + ConfigurationHelper.getString( DRIVER, mergedSettings.configurationValues ), + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + } + + if ( mergedSettings.configurationValues.containsKey( JPA_JDBC_URL ) ) { + final Object url = mergedSettings.configurationValues.get( JPA_JDBC_URL ); + + if ( url != null && ( ! ( url instanceof String ) || StringHelper.isNotEmpty( (String) url ) ) ) { + applyJdbcSettings( + url, + ConfigurationHelper.getString( JPA_JDBC_DRIVER, mergedSettings.configurationValues ), + integrationSettingsCopy, + mergedSettings + ); + + // EARLY EXIT!! + return; + } + } + + // any other conditions to account for? + } + + @SuppressWarnings("unchecked") + private void applyDataSource( + Object dataSourceRef, + Boolean useJtaDataSource, + HashMap integrationSettingsCopy, + MergedSettings mergedSettings) { + + // `IS_JTA_TXN_COORD` is a value set during `#normalizeTransactionCoordinator` to indicate whether + // the execution environment "is JTA" as best as it can tell.. + // + // we use this value when JTA was not explicitly specified in regards the DataSource + final boolean isJtaTransactionCoordinator = (boolean) mergedSettings.configurationValues.remove( IS_JTA_TXN_COORD ); + final boolean isJta = useJtaDataSource == null ? isJtaTransactionCoordinator : useJtaDataSource; + + // add to EMF properties (questionable - see HHH-13432) + final String emfKey; + final String inverseEmfKey; + if ( isJta ) { + emfKey = JPA_JTA_DATASOURCE; + inverseEmfKey = JPA_NON_JTA_DATASOURCE; + } + else { + emfKey = JPA_NON_JTA_DATASOURCE; + inverseEmfKey = JPA_JTA_DATASOURCE; + } + mergedSettings.configurationValues.put( emfKey, dataSourceRef ); + + // clear any settings logically overridden by this datasource + cleanUpConfigKeys( + integrationSettingsCopy, + mergedSettings, + inverseEmfKey, + JPA_JDBC_DRIVER, + DRIVER, + JPA_JDBC_URL, + URL + ); + + + // clean-up the entries in the "integration overrides" so they do not get get picked + // up in the general "integration overrides" handling + cleanUpConfigKeys( integrationSettingsCopy, DATASOURCE, JPA_JTA_DATASOURCE, JPA_NON_JTA_DATASOURCE ); + + // add under Hibernate's DATASOURCE setting where the ConnectionProvider will find it + mergedSettings.configurationValues.put( DATASOURCE, dataSourceRef ); + } + + private void cleanUpConfigKeys(HashMap integrationSettingsCopy, MergedSettings mergedSettings, String... keys) { + for ( String key : keys ) { + final Object removedIntgSetting = integrationSettingsCopy.remove( key ); + if ( removedIntgSetting != null ) { + LOG.debugf( "Removed integration override setting [%s] due to normalization", key ); + } + + final Object removedMergedSetting = mergedSettings.configurationValues.remove( key ); + if ( removedMergedSetting != null ) { + LOG.debugf( "Removed merged setting [%s] due to normalization", key ); + } + } + } + + private void cleanUpConfigKeys(Map settings, String... keys) { + for ( String key : keys ) { + settings.remove( key ); + } + } + + @SuppressWarnings("unchecked") + private void applyJdbcSettings( + Object url, + String driver, + HashMap integrationSettingsCopy, + MergedSettings mergedSettings) { + mergedSettings.configurationValues.put( URL, url ); + mergedSettings.configurationValues.put( JPA_JDBC_URL, url ); + + if ( driver != null ) { + mergedSettings.configurationValues.put( DRIVER, driver ); + mergedSettings.configurationValues.put( JPA_JDBC_DRIVER, driver ); + } + else { + mergedSettings.configurationValues.remove( DRIVER ); + mergedSettings.configurationValues.remove( JPA_JDBC_DRIVER ); + } + + // clean up the integration-map values + cleanUpConfigKeys( + integrationSettingsCopy, + DRIVER, + JPA_JDBC_DRIVER, + URL, + JPA_JDBC_URL, + USER, + JPA_JDBC_USER, + PASS, + JPA_JDBC_PASSWORD + ); + + cleanUpConfigKeys( + integrationSettingsCopy, + mergedSettings, + DATASOURCE, + JPA_JTA_DATASOURCE, + JPA_NON_JTA_DATASOURCE + ); + } + + private void processHibernateConfigXmlResources( + StandardServiceRegistryBuilder ssrBuilder, + MergedSettings mergedSettings, + String cfgXmlResourceName) { + final LoadedConfig loadedConfig = ssrBuilder.getConfigLoader().loadConfigXmlResource( cfgXmlResourceName ); + + mergedSettings.processHibernateConfigXmlResources( loadedConfig ); + + ssrBuilder.getAggregatedCfgXml().merge( loadedConfig ); } private GrantedPermission parseJaccConfigEntry(String keyString, String valueString) { @@ -642,90 +1039,7 @@ private CacheRegionDefinition parseCacheRegionDefinitionEntry(String role, Strin return new CacheRegionDefinition( cacheType, role, usage, region, lazyProperty ); } - private void configure(StandardServiceRegistryBuilder ssrBuilder) { - - applyJdbcConnectionProperties( ssrBuilder ); - applyTransactionProperties( ssrBuilder ); - - // flush before completion validation - if ( "true".equals( configurationValues.get( Environment.FLUSH_BEFORE_COMPLETION ) ) ) { - ssrBuilder.applySetting( Environment.FLUSH_BEFORE_COMPLETION, "false" ); - LOG.definingFlushBeforeCompletionIgnoredInHem( Environment.FLUSH_BEFORE_COMPLETION ); - } - -// final StrategySelector strategySelector = ssrBuilder.getBootstrapServiceRegistry().getService( StrategySelector.class ); -// final Object interceptorSetting = configurationValues.remove( AvailableSettings.SESSION_INTERCEPTOR ); -// if ( interceptorSetting != null ) { -// settings.setSessionInterceptorClass( -// loadSessionInterceptorClass( interceptorSetting, strategySelector ) -// ); -// } - } - - private void applyJdbcConnectionProperties(StandardServiceRegistryBuilder ssrBuilder) { - if ( dataSource != null ) { - ssrBuilder.applySetting( DATASOURCE, dataSource ); - } - else if ( persistenceUnit.getJtaDataSource() != null ) { - if ( ! ssrBuilder.getSettings().containsKey( DATASOURCE ) ) { - ssrBuilder.applySetting( DATASOURCE, persistenceUnit.getJtaDataSource() ); - // HHH-8121 : make the PU-defined value available to EMF.getProperties() - configurationValues.put( JPA_JTA_DATASOURCE, persistenceUnit.getJtaDataSource() ); - } - } - else if ( persistenceUnit.getNonJtaDataSource() != null ) { - if ( ! ssrBuilder.getSettings().containsKey( DATASOURCE ) ) { - ssrBuilder.applySetting( DATASOURCE, persistenceUnit.getNonJtaDataSource() ); - // HHH-8121 : make the PU-defined value available to EMF.getProperties() - configurationValues.put( JPA_NON_JTA_DATASOURCE, persistenceUnit.getNonJtaDataSource() ); - } - } - else { - final String driver = (String) configurationValues.get( JPA_JDBC_DRIVER ); - if ( StringHelper.isNotEmpty( driver ) ) { - ssrBuilder.applySetting( DRIVER, driver ); - } - final String url = (String) configurationValues.get( JPA_JDBC_URL ); - if ( StringHelper.isNotEmpty( url ) ) { - ssrBuilder.applySetting( URL, url ); - } - final String user = (String) configurationValues.get( JPA_JDBC_USER ); - if ( StringHelper.isNotEmpty( user ) ) { - ssrBuilder.applySetting( USER, user ); - } - final String pass = (String) configurationValues.get( JPA_JDBC_PASSWORD ); - if ( StringHelper.isNotEmpty( pass ) ) { - ssrBuilder.applySetting( PASS, pass ); - } - } - } - - private void applyTransactionProperties(StandardServiceRegistryBuilder ssrBuilder) { - PersistenceUnitTransactionType txnType = PersistenceUnitTransactionTypeHelper.interpretTransactionType( - configurationValues.get( JPA_TRANSACTION_TYPE ) - ); - if ( txnType == null ) { - txnType = persistenceUnit.getTransactionType(); - } - if ( txnType == null ) { - // is it more appropriate to have this be based on bootstrap entry point (EE vs SE)? - txnType = PersistenceUnitTransactionType.RESOURCE_LOCAL; - } - boolean hasTxStrategy = configurationValues.containsKey( TRANSACTION_COORDINATOR_STRATEGY ); - if ( hasTxStrategy ) { - LOG.overridingTransactionStrategyDangerous( TRANSACTION_COORDINATOR_STRATEGY ); - } - else { - if ( txnType == PersistenceUnitTransactionType.JTA ) { - ssrBuilder.applySetting( TRANSACTION_COORDINATOR_STRATEGY, JtaTransactionCoordinatorBuilderImpl.class ); - } - else if ( txnType == PersistenceUnitTransactionType.RESOURCE_LOCAL ) { - ssrBuilder.applySetting( TRANSACTION_COORDINATOR_STRATEGY, JdbcResourceLocalTransactionCoordinatorBuilderImpl.class ); - } - } - } - - private void configure(StandardServiceRegistry ssr, MergedSettings mergedSettings) { + private void configureIdentifierGenerators(StandardServiceRegistry ssr) { final StrategySelector strategySelector = ssr.getService( StrategySelector.class ); // apply id generators @@ -747,10 +1061,9 @@ private void configure(StandardServiceRegistry ssr, MergedSettings mergedSetting } @SuppressWarnings("unchecked") - protected List populate( - MetadataSources metadataSources, - MergedSettings mergedSettings, - StandardServiceRegistry ssr) { + private List applyMappingResources(MetadataSources metadataSources) { + // todo : where in the heck are `org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor.getManagedClassNames` handled?!? + // final ClassLoaderService classLoaderService = ssr.getService( ClassLoaderService.class ); // // // todo : make sure MetadataSources/Metadata are capable of handling duplicate sources @@ -827,12 +1140,10 @@ protected List populate( return attributeConverterDefinitions; } - protected void populate( - MetadataBuilder metamodelBuilder, + private void applyMetamodelBuilderSettings( MergedSettings mergedSettings, - StandardServiceRegistry ssr, List attributeConverterDefinitions) { - ( (MetadataBuilderImplementor) metamodelBuilder ).getBootstrapContext().markAsJpaBootstrap(); + metamodelBuilder.getBootstrapContext().markAsJpaBootstrap(); if ( persistenceUnit.getTempClassLoader() != null ) { metamodelBuilder.applyTempClassLoader( persistenceUnit.getTempClassLoader() ); @@ -916,7 +1227,7 @@ public void generateSchema() { // Metamodel will clean this up... try { SessionFactoryBuilder sfBuilder = metadata().getSessionFactoryBuilder(); - populate( sfBuilder, standardServiceRegistry ); + populateSfBuilder( sfBuilder, standardServiceRegistry ); SchemaManagementToolCoordinator.process( metadata, standardServiceRegistry, configurationValues, DelayedDropRegistryNotAvailableImpl.INSTANCE @@ -930,10 +1241,10 @@ public void generateSchema() { cancel(); } - @SuppressWarnings("unchecked") + @Override public EntityManagerFactory build() { - SessionFactoryBuilder sfBuilder = metadata().getSessionFactoryBuilder(); - populate( sfBuilder, standardServiceRegistry ); + final SessionFactoryBuilder sfBuilder = metadata().getSessionFactoryBuilder(); + populateSfBuilder( sfBuilder, standardServiceRegistry ); try { return sfBuilder.build(); @@ -943,7 +1254,7 @@ public EntityManagerFactory build() { } } - protected void populate(SessionFactoryBuilder sfBuilder, StandardServiceRegistry ssr) { + protected void populateSfBuilder(SessionFactoryBuilder sfBuilder, StandardServiceRegistry ssr) { final StrategySelector strategySelector = ssr.getService( StrategySelector.class ); @@ -1028,7 +1339,36 @@ private static class MergedSettings { private Map jaccPermissionsByContextId; private List cacheRegionDefinitions; + /** + * MergedSettings is initialized with hibernate.properties + */ private MergedSettings() { + configurationValues.putAll( Environment.getProperties() ); + } + + public void processPersistenceUnitDescriptorProperties(PersistenceUnitDescriptor persistenceUnit) { + if ( persistenceUnit.getProperties() != null ) { + configurationValues.putAll( persistenceUnit.getProperties() ); + } + + configurationValues.put( PERSISTENCE_UNIT_NAME, persistenceUnit.getName() ); + + } + + public void processHibernateConfigXmlResources(LoadedConfig loadedConfig){ + if ( ! configurationValues.containsKey( SESSION_FACTORY_NAME ) ) { + // there is not already a SF-name in the merged settings + final String sfName = loadedConfig.getSessionFactoryName(); + if ( sfName != null ) { + // but the cfg.xml file we are processing named one.. + configurationValues.put( SESSION_FACTORY_NAME, sfName ); + } + } + else { + // make sure they match? + } + + configurationValues.putAll( loadedConfig.getConfigurationValues() ); } public Map getConfigurationValues() { diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java b/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java index 81aed3aa1bee6def77449f6361d188c295e2642e..a88d09bc55f85ec5959c42c42801efb60356ba5a 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/ProviderChecker.java @@ -9,6 +9,8 @@ import java.util.Map; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.internal.EntityManagerMessageLogger; +import org.hibernate.internal.HEMLogging; import org.hibernate.jpa.HibernatePersistenceProvider; import org.jboss.logging.Logger; @@ -20,6 +22,7 @@ * @author Steve Ebersole */ public final class ProviderChecker { + private static final Logger log = Logger.getLogger( ProviderChecker.class ); /** @@ -49,6 +52,15 @@ public static boolean hibernateProviderNamesContain(String requestedProviderName "Checking requested PersistenceProvider name [%s] against Hibernate provider names", requestedProviderName ); + final String deprecatedPersistenceProvider = "org.hibernate.ejb.HibernatePersistence"; + if ( deprecatedPersistenceProvider.equals( requestedProviderName) ) { + HEMLogging.messageLogger( ProviderChecker.class ) + .deprecatedPersistenceProvider( + deprecatedPersistenceProvider, + HibernatePersistenceProvider.class.getName() + ); + return true; + } return HibernatePersistenceProvider.class.getName().equals( requestedProviderName ); } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EmbeddableCallback.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EmbeddableCallback.java index 8111e0b2f6f1edd56afefa97f5d1fa72fc612dbf..e5fc177e0c933519970b061c15a23acb9a32f113 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EmbeddableCallback.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/EmbeddableCallback.java @@ -32,7 +32,9 @@ final class EmbeddableCallback extends AbstractCallback { public boolean performCallback(Object entity) { try { Object embeddable = embeddableGetter.get( entity ); - callbackMethod.invoke( embeddable ); + if ( embeddable != null ) { + callbackMethod.invoke( embeddable ); + } return true; } catch (InvocationTargetException e) { diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/internal/PersistenceUnitUtilImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/internal/PersistenceUnitUtilImpl.java index a39f57e52a67a22953f4cd1f3135b95010a72328..7d26f631be4ca747c11a6b62fe4532b74c479269 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/internal/PersistenceUnitUtilImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/internal/PersistenceUnitUtilImpl.java @@ -68,7 +68,7 @@ public Object getIdentifier(Object entity) { } if ( entity instanceof HibernateProxy ) { - return ((HibernateProxy) entity).getHibernateLazyInitializer().getIdentifier(); + return ((HibernateProxy) entity).getHibernateLazyInitializer().getInternalIdentifier(); } else if ( entity instanceof ManagedEntity ) { EntityEntry entityEntry = ((ManagedEntity) entity).$$_hibernate_getEntityEntry(); diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerFactoryAware.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerFactoryAware.java index a50aad84fd3b49720cef8b5e9ee75bb56208013e..9d2e605238ef4156ac7ad95a4bc32e9b08867e11 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerFactoryAware.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerFactoryAware.java @@ -11,7 +11,7 @@ /** * Internal contact for things that have {@link HibernateEntityManagerFactory} access. * - * @author Strong Liu + * @author Strong Liu * * @deprecated (since 5.2) Why do we need an over-arching access to HibernateEntityManagerFactory across * multiple contract hierarchies? diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerImplementor.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerImplementor.java index 0891f98bd324de909951093b594f2ffb082e93ae..2bd4e68d5ff4d59ca6ebb3c62af0b1de3d36cd86 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/HibernateEntityManagerImplementor.java @@ -15,8 +15,8 @@ import org.hibernate.HibernateException; import org.hibernate.LockOptions; import org.hibernate.StaleStateException; +import org.hibernate.ejb.HibernateEntityManager; import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.jpa.HibernateEntityManager; import org.hibernate.query.Query; import org.hibernate.query.criteria.internal.ValueHandlerFactory; import org.hibernate.type.Type; diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/IdentifierGeneratorStrategyProvider.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/IdentifierGeneratorStrategyProvider.java index 1aa17d0239bc8c1917220d4fba5aebb82a372f6c..cf1f8659b8c15a6aa086ff99d3c63b7e56f9e9ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/spi/IdentifierGeneratorStrategyProvider.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/IdentifierGeneratorStrategyProvider.java @@ -11,7 +11,7 @@ /** * Provide a set of IdentifierGenerator strategies allowing to override the Hibernate Core default ones * - * @author Emmanuel Bernard + * @author Emmanuel Bernard */ public interface IdentifierGeneratorStrategyProvider { /** diff --git a/hibernate-core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java b/hibernate-core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java index 7b22952ee83b5a6bc1de4e9da94607c0499503b4..d102859fee74b0a56b1051191bcf8d32f26b8672 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java @@ -151,7 +151,7 @@ protected final boolean isJoinFetchEnabledByProfile(OuterJoinLoadable persister, String relativePropertyPath = pos >= 0 ? fullPath.substring( pos ) : rootPropertyName; - String fetchRole = persister.getEntityName() + "." + relativePropertyPath; + String fetchRole = persister.getEntityName() + '.' + relativePropertyPath; for ( String profileName : getLoadQueryInfluencers().getEnabledFetchProfileNames() ) { final FetchProfile profile = getFactory().getFetchProfile( profileName ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/BasicLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/BasicLoader.java index 537c6600d039e9439ece99a98c162e5676e05ed3..456a72fe4137c630265b0475fb8995321646a58f 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/BasicLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/BasicLoader.java @@ -9,6 +9,7 @@ import java.util.List; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.loader.internal.AliasConstantsHelper; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.Loadable; import org.hibernate.type.BagType; @@ -100,7 +101,7 @@ public static String[] generateSuffixes(int seed, int length) { String[] suffixes = new String[length]; for ( int i = 0; i < length; i++ ) { - suffixes[i] = (Integer.toString( i + seed ) + "_").intern(); + suffixes[i] = AliasConstantsHelper.get( i + seed ); } return suffixes; } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/DefaultEntityAliases.java b/hibernate-core/src/main/java/org/hibernate/loader/DefaultEntityAliases.java index 73472276c4af6d5f0db4a2bbf49438929ee7547d..72e47f77fa2fddd78cbc785fb39cc8aa915ec5ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/DefaultEntityAliases.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/DefaultEntityAliases.java @@ -9,6 +9,7 @@ import java.util.Collections; import java.util.Map; +import org.hibernate.dialect.Dialect; import org.hibernate.internal.util.StringHelper; import org.hibernate.persister.entity.Loadable; @@ -42,18 +43,31 @@ public DefaultEntityAliases( Map userProvidedAliases, Loadable persister, String suffix) { - this.suffix = suffix.intern(); - this.userProvidedAliases = userProvidedAliases; + this( userProvidedAliases, persister, suffix, false ); + } + + public DefaultEntityAliases(Loadable persister, String suffix) { + this( Collections.EMPTY_MAP, persister, suffix, true ); + } + private DefaultEntityAliases( + Map userProvidedAliases, + Loadable persister, + String suffix, + boolean interns) { + if ( interns ) { + this.suffix = suffix.intern(); + this.rowIdAlias = (Loadable.ROWID_ALIAS + suffix).intern(); // TODO: not visible to the user! + } + else { + this.suffix = suffix; + this.rowIdAlias = (Loadable.ROWID_ALIAS + suffix); + } + this.userProvidedAliases = userProvidedAliases; suffixedKeyColumns = determineKeyAlias( persister, suffix ); suffixedPropertyColumns = determinePropertyAliases( persister ); suffixedDiscriminatorColumn = determineDiscriminatorAlias( persister, suffix ); suffixedVersionColumn = determineVersionAlias( persister ); - rowIdAlias = (Loadable.ROWID_ALIAS + suffix).intern(); // TODO: not visible to the user! - } - - public DefaultEntityAliases(Loadable persister, String suffix) { - this( Collections.EMPTY_MAP, persister, suffix ); } private String[] determineKeyAlias(Loadable persister, String suffix) { @@ -68,9 +82,7 @@ private String[] determineKeyAlias(Loadable persister, String suffix) { else { aliases = keyColumnsCandidates; } - final String[] rtn = StringHelper.unquote( aliases, persister.getFactory().getDialect() ); - intern( rtn ); - return rtn; + return StringHelper.unquote( aliases, persister.getFactory().getDialect() ); } private String[][] determinePropertyAliases(Loadable persister) { @@ -122,17 +134,18 @@ private String getUserProvidedAlias(String propertyPath, String defaultAlias) { @Override public String[][] getSuffixedPropertyAliases(Loadable persister) { - final int size = persister.getPropertyNames().length; + final String[] propertyNames = persister.getPropertyNames(); + final int size = propertyNames.length; final String[][] suffixedPropertyAliases; - if (size > 0) { + if ( size > 0 ) { suffixedPropertyAliases = new String[size][]; + final Dialect dialect = persister.getFactory().getDialect(); for ( int j = 0; j < size; j++ ) { suffixedPropertyAliases[j] = getUserProvidedAliases( - persister.getPropertyNames()[j], + propertyNames[j], getPropertyAliases( persister, j ) ); - suffixedPropertyAliases[j] = StringHelper.unquote( suffixedPropertyAliases[j], persister.getFactory().getDialect() ); - intern( suffixedPropertyAliases[j] ); + suffixedPropertyAliases[j] = StringHelper.unquote( suffixedPropertyAliases[j], dialect ); } } else { @@ -171,9 +184,4 @@ public String getSuffix() { return suffix; } - private static void intern(String[] strings) { - for ( int i = 0; i < strings.length; i++ ) { - strings[i] = strings[i].intern(); - } - } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java index a3ebcd2069d7b0a8203388de3b4ab6734a562772..5ef4da495f7319cf7fd14eccc509972009099ea8 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java @@ -31,6 +31,7 @@ import org.hibernate.Session; import org.hibernate.StaleObjectStateException; import org.hibernate.WrongClassException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.cache.spi.FilterKey; import org.hibernate.cache.spi.QueryKey; import org.hibernate.cache.spi.QueryResultsCache; @@ -50,15 +51,22 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityUniqueKey; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.RowSelection; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SubselectFetch; import org.hibernate.engine.spi.TypedValue; +import org.hibernate.event.service.spi.EventListenerGroup; +import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventSource; +import org.hibernate.event.spi.EventType; import org.hibernate.event.spi.PostLoadEvent; +import org.hibernate.event.spi.PostLoadEventListener; import org.hibernate.event.spi.PreLoadEvent; +import org.hibernate.event.spi.PreLoadEventListener; import org.hibernate.hql.internal.HolderInstantiator; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; @@ -66,6 +74,7 @@ import org.hibernate.internal.ScrollableResultsImpl; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.loader.entity.CascadeEntityLoader; import org.hibernate.loader.spi.AfterLoadAction; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; @@ -107,12 +116,14 @@ public abstract class Loader { private volatile ColumnNameCache columnNameCache; private final boolean referenceCachingEnabled; + private final boolean enhancementAsProxyEnabled; private boolean isJdbc4 = true; public Loader(SessionFactoryImplementor factory) { this.factory = factory; this.referenceCachingEnabled = factory.getSessionFactoryOptions().isDirectReferenceCacheEntriesEnabled(); + this.enhancementAsProxyEnabled = factory.getSessionFactoryOptions().isEnhancementAsProxyEnabled(); } /** @@ -829,6 +840,7 @@ protected void extractKeysFromResultSet( keys[targetIndex], object, lockModes[targetIndex], + hydratedObjects, session ); } @@ -1147,8 +1159,19 @@ private void initializeEntitiesAndCollections( if ( hydratedObjects != null ) { int hydratedObjectsSize = hydratedObjects.size(); LOG.tracev( "Total objects hydrated: {0}", hydratedObjectsSize ); - for ( Object hydratedObject : hydratedObjects ) { - TwoPhaseLoad.initializeEntity( hydratedObject, readOnly, session, pre ); + + if ( hydratedObjectsSize != 0 ) { + final Iterable listeners = session + .getFactory() + .getServiceRegistry() + .getService( EventListenerRegistry.class ) + .getEventListenerGroup( EventType.PRE_LOAD ) + .listeners(); + + for ( Object hydratedObject : hydratedObjects ) { + TwoPhaseLoad.initializeEntity( hydratedObject, readOnly, session, pre, listeners ); + } + } } @@ -1168,9 +1191,22 @@ private void initializeEntitiesAndCollections( // split off from initializeEntity. It *must* occur after // endCollectionLoad to ensure the collection is in the // persistence context. - if ( hydratedObjects != null ) { + if ( hydratedObjects != null && hydratedObjects.size() > 0 ) { + + final Iterable postLoadEventListeners; + if ( session.isEventSource() ) { + final EventListenerGroup listenerGroup = session.getFactory() + .getServiceRegistry() + .getService( EventListenerRegistry.class ) + .getEventListenerGroup( EventType.POST_LOAD ); + postLoadEventListeners = listenerGroup.listeners(); + } + else { + postLoadEventListeners = Collections.emptyList(); + } + for ( Object hydratedObject : hydratedObjects ) { - TwoPhaseLoad.postLoad( hydratedObject, session, post ); + TwoPhaseLoad.postLoad( hydratedObject, session, post, postLoadEventListeners ); if ( afterLoadActions != null ) { for ( AfterLoadAction afterLoadAction : afterLoadActions ) { final EntityEntry entityEntry = session.getPersistenceContext().getEntry( hydratedObject ); @@ -1491,7 +1527,8 @@ private void checkVersion( Object version = session.getPersistenceContext().getEntry( entity ).getVersion(); - if ( version != null ) { //null version means the object is in the process of being loaded somewhere else in the ResultSet + if ( version != null ) { + // null version means the object is in the process of being loaded somewhere else in the ResultSet final VersionType versionType = persister.getVersionType(); final Object currentVersion = versionType.nullSafeGet( rs, @@ -1526,7 +1563,7 @@ private Object[] getRow( final List hydratedObjects, final SharedSessionContractImplementor session) throws HibernateException, SQLException { final int cols = persisters.length; - final EntityAliases[] descriptors = getEntityAliases(); + final EntityAliases[] entityAliases = getEntityAliases(); if ( LOG.isDebugEnabled() ) { LOG.debugf( "Result row: %s", StringHelper.toString( keys ) ); @@ -1546,7 +1583,6 @@ private Object[] getRow( //If the object is already loaded, return the loaded one object = session.getEntityUsingInterceptor( key ); if ( object != null ) { - //its already loaded so don't need to hydrate it instanceAlreadyLoaded( rs, i, @@ -1554,6 +1590,7 @@ private Object[] getRow( key, object, lockModes[i], + hydratedObjects, session ); } @@ -1562,7 +1599,7 @@ private Object[] getRow( rs, i, persisters[i], - descriptors[i].getRowIdAlias(), + entityAliases[i].getRowIdAlias(), key, lockModes[i], optionalObjectKey, @@ -1590,6 +1627,7 @@ private void instanceAlreadyLoaded( final EntityKey key, final Object object, final LockMode requestedLockMode, + List hydratedObjects, final SharedSessionContractImplementor session) throws HibernateException, SQLException { if ( !persister.isInstance( object ) ) { @@ -1600,7 +1638,42 @@ private void instanceAlreadyLoaded( ); } - if ( LockMode.NONE != requestedLockMode && upgradeLocks() ) { //no point doing this if NONE was requested + if ( persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() && enhancementAsProxyEnabled ) { + if ( "merge".equals( session.getLoadQueryInfluencers().getInternalFetchProfile() ) ) { + assert this instanceof CascadeEntityLoader; + // we are processing a merge and have found an existing "managed copy" in the + // session - we need to check if this copy is an enhanced-proxy and, if so, + // perform the hydration just as if it were "not yet loaded" + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) object; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + hydrateEntityState( + rs, + i, + persister, + getEntityAliases()[i].getRowIdAlias(), + key, + hydratedObjects, + session, + getInstanceClass( + rs, + i, + persister, + key.getIdentifier(), + session + ), + object, + requestedLockMode + ); + + // EARLY EXIT!!! + // - to skip the version check + return; + } + } + } + + if ( LockMode.NONE != requestedLockMode && upgradeLocks() ) { final EntityEntry entry = session.getPersistenceContext().getEntry( object ); if ( entry.getLockMode().lessThan( requestedLockMode ) ) { //we only check the version when _upgrading_ lock modes @@ -1669,6 +1742,33 @@ private Object instanceNotYetLoaded( // (but don't yet initialize the object itself) // note that we acquire LockMode.READ even if it was not requested LockMode acquiredLockMode = lockMode == LockMode.NONE ? LockMode.READ : lockMode; + hydrateEntityState( + rs, + i, + persister, + rowIdAlias, + key, + hydratedObjects, + session, + instanceClass, + object, + acquiredLockMode + ); + + return object; + } + + private void hydrateEntityState( + ResultSet rs, + int i, + Loadable persister, + String rowIdAlias, + EntityKey key, + List hydratedObjects, + SharedSessionContractImplementor session, + String instanceClass, + Object object, + LockMode acquiredLockMode) throws SQLException { loadFromResultSet( rs, i, @@ -1683,8 +1783,6 @@ private Object instanceNotYetLoaded( //materialize associations (and initialize the object) later hydratedObjects.add( object ); - - return object; } private boolean isEagerPropertyFetchEnabled(int i) { @@ -1906,7 +2004,7 @@ protected SqlStatementWrapper executeQueryStatement( final LimitHandler limitHandler = getLimitHandler( queryParameters.getRowSelection() ); - String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters.getRowSelection() ); + String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters ); // Adding locks and comments. sql = preprocessSQL( sql, queryParameters, getFactory(), afterLoadActions ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/collection/BasicCollectionLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/collection/BasicCollectionLoader.java index 9338e35d59a159ed33084ea2300131d0cb754df5..6a9c8fc471ea170b6da8814dda4b7f63c8fa2339 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/collection/BasicCollectionLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/collection/BasicCollectionLoader.java @@ -18,7 +18,7 @@ /** * Loads a collection of values or a many-to-many association. *
      - * The collection persister must implement QueryableCOllection. For + * The collection persister must implement QueryableCollection. For * other collections, create a customized subclass of Loader. * * @see OneToManyLoader diff --git a/hibernate-core/src/main/java/org/hibernate/loader/collection/OneToManyLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/collection/OneToManyLoader.java index 26d5b4519f4e1a0bd89b4700143d0a46acab02ae..c7dd9919401a18f5f2455e4ce4784f8f8e0f2f05 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/collection/OneToManyLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/collection/OneToManyLoader.java @@ -18,7 +18,7 @@ /** * Loads one-to-many associations
      *
      - * The collection persister must implement QueryableCOllection. For + * The collection persister must implement QueryableCollection. For * other collections, create a customized subclass of Loader. * * @see BasicCollectionLoader diff --git a/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaQueryTranslator.java b/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaQueryTranslator.java index 5e200386f340eeabf9fa78f18c76006db0334c98..a8a614245f6ad12a15c73930618bbc379df1e4ed 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaQueryTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaQueryTranslator.java @@ -9,12 +9,10 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -29,9 +27,7 @@ import org.hibernate.criterion.CriteriaQuery; import org.hibernate.criterion.Criterion; import org.hibernate.criterion.EnhancedProjection; -import org.hibernate.criterion.ParameterInfoCollector; import org.hibernate.criterion.Projection; -import org.hibernate.engine.query.spi.OrdinalParameterDescriptor; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.RowSelection; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -41,12 +37,14 @@ import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Loadable; import org.hibernate.persister.entity.PropertyMapping; import org.hibernate.persister.entity.Queryable; import org.hibernate.sql.JoinType; import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; +import org.hibernate.type.ManyToOneType; import org.hibernate.type.StringRepresentableType; import org.hibernate.type.Type; @@ -527,14 +525,77 @@ public TypedValue getTypedIdentifierValue(Criteria criteria, Object value) { } @Override - public String[] getColumns( - String propertyName, - Criteria subcriteria) throws HibernateException { - return getPropertyMapping( getEntityName( subcriteria, propertyName ) ) - .toColumns( - getSQLAlias( subcriteria, propertyName ), - getPropertyName( propertyName ) - ); + public Type getForeignKeyType(Criteria criteria, String associationPropertyName) { + final Type propertyType = ( (Loadable) getPropertyMapping( getEntityName( criteria ) ) ).getPropertyType( associationPropertyName ); + if ( !( propertyType instanceof ManyToOneType ) ) { + throw new QueryException( + "Argument to fk() function must be the fk owner of a to-one association, but found " + propertyType + ); + } + return ( (ManyToOneType) propertyType ).getIdentifierOrUniqueKeyType( getFactory() ); + } + + @Override + public String[] getForeignKeyColumns(Criteria criteria, String associationPropertyName) { + final PropertyMapping propertyMapping = getPropertyMapping( getEntityName( criteria ) ); + + assert propertyMapping instanceof EntityPersister; + final Type propertyType = ((EntityPersister) propertyMapping).getPropertyType( associationPropertyName ); + if ( !( propertyType instanceof ManyToOneType ) ) { + throw new QueryException( + "Argument to fk() function must be the fk owner of a to-one association, but found " + propertyType + ); + } + + return propertyMapping.toColumns( getSQLAlias( criteria, associationPropertyName ), associationPropertyName ); + } + + @Override + public TypedValue getForeignKeyTypeValue(Criteria criteria, String associationPropertyName, Object value) { + return new TypedValue( getForeignKeyType( criteria, associationPropertyName ), value ); + } + + @Override + public String[] getColumns(String propertyName, Criteria subcriteria) throws HibernateException { + try { + return getPropertyMapping( getEntityName( subcriteria, propertyName ) ) + .toColumns( getSQLAlias( subcriteria, propertyName ), getPropertyName( propertyName ) ); + } + catch (QueryException qe) { + if ( propertyName.indexOf( '.' ) > 0 ) { + final String propertyRootName = StringHelper.root( propertyName ); + final CriteriaInfoProvider pathInfo = getPathInfo( propertyRootName ); + final PropertyMapping propertyMapping = pathInfo.getPropertyMapping(); + if ( propertyMapping instanceof EntityPersister ) { + final String name = propertyName.substring( propertyRootName.length() + 1 ); + if ( ( (EntityPersister) propertyMapping ).getIdentifierPropertyName().equals( name ) ) { + final Criteria associationPathCriteria = associationPathCriteriaMap.get( propertyRootName ); + if ( associationPathCriteria == null ) { + final Criteria criteria = addInnerJoin( subcriteria, propertyRootName, pathInfo ); + return propertyMapping.toColumns( getSQLAlias( criteria, name ), name ); + } + else { + return propertyMapping.toColumns( getSQLAlias( associationPathCriteria, name ), name ); + } + } + } + throw qe; + } + else { + throw qe; + } + } + } + + private Criteria addInnerJoin(Criteria subcriteria, String root, CriteriaInfoProvider pathInfo) { + final Criteria criteria = subcriteria.createCriteria( root, root, JoinType.INNER_JOIN ); + aliasCriteriaMap.put( root, criteria ); + associationPathCriteriaMap.put( root, criteria ); + associationPathJoinTypesMap.put( root, JoinType.INNER_JOIN ); + criteriaInfoMap.put( criteria, pathInfo ); + nameCriteriaInfoMap.put( pathInfo.getName(), pathInfo ); + criteriaSQLAliasMap.put( criteria, StringHelper.generateAlias( root, criteriaSQLAliasMap.size() ) ); + return criteria; } /** @@ -597,8 +658,28 @@ public Type getTypeUsingProjection(Criteria subcriteria, String propertyName) @Override public Type getType(Criteria subcriteria, String propertyName) throws HibernateException { - return getPropertyMapping( getEntityName( subcriteria, propertyName ) ) - .toType( getPropertyName( propertyName ) ); + try { + return getPropertyMapping( getEntityName( subcriteria, propertyName ) ) + .toType( getPropertyName( propertyName ) ); + } + catch (QueryException qe) { + if ( propertyName.indexOf( '.' ) > 0 ) { + final String propertyRootName = StringHelper.root( propertyName ); + final String name = propertyName.substring( propertyRootName.length() + 1 ); + final Criteria associationPathCriteria = associationPathCriteriaMap.get( propertyRootName ); + if ( associationPathCriteria != null ) { + final PropertyMapping propertyMapping = getPropertyMapping( + getEntityName( associationPathCriteria, propertyRootName ) + ); + if ( propertyMapping instanceof EntityPersister + && ( (EntityPersister) propertyMapping ).getIdentifierPropertyName().equals( name ) ) { + return propertyMapping.toType( getPropertyName( name ) ); + } + } + throw qe; + } + throw qe; + } } /** diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/ColumnCollectionAliases.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/ColumnCollectionAliases.java index 13c10f880510fb7bfb4144560e55fdd95951901f..7787cd24305840b6da07437a75c3031bdb5c7826 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/custom/ColumnCollectionAliases.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/ColumnCollectionAliases.java @@ -12,8 +12,8 @@ import org.hibernate.persister.collection.SQLLoadableCollection; /** - * CollectionAliases that uses columnnames instead of generated aliases. - * Aliases can still be overwritten via + * CollectionAliases that uses column names instead of generated aliases. + * Aliases can still be overwritten via {@code } * * @author Max Rydahl Andersen */ diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java index de7059dc37027bc135c712d92128a9363cdff681..6e03a58079bb68f4eb4bc26c9a3906550b77b6a8 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java @@ -19,6 +19,8 @@ import org.hibernate.LockOptions; import org.hibernate.dialect.pagination.LimitHelper; import org.hibernate.engine.internal.BatchFetchQueueHelper; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.LoadQueryInfluencers; @@ -434,16 +436,22 @@ protected boolean isSingleRowLoader() { return false; } + @Override + protected boolean isSubselectLoadingEnabled() { + return persister.hasSubselectLoadableCollections(); + } + public List doEntityBatchFetch( SharedSessionContractImplementor session, QueryParameters queryParameters, Serializable[] ids) { + final JdbcServices jdbcServices = session.getJdbcServices(); final String sql = StringHelper.expandBatchIdPlaceholder( sqlTemplate, ids, alias, persister.getKeyColumnNames(), - session.getJdbcServices().getJdbcEnvironment().getDialect() + jdbcServices.getJdbcEnvironment().getDialect() ); try { @@ -478,7 +486,7 @@ public List doEntityBatchFetch( } } catch ( SQLException sqle ) { - throw session.getJdbcServices().getSqlExceptionHelper().convert( + throw jdbcServices.getSqlExceptionHelper().convert( sqle, "could not load an entity batch: " + MessageHelper.infoString( getEntityPersisters()[0], @@ -504,8 +512,9 @@ private List doTheLoad(String sql, QueryParameters queryParameters, SharedSessio return processResultSet( rs, queryParameters, session, false, null, maxRows, afterLoadActions ); } finally { - session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( st ); - session.getJdbcCoordinator().afterStatementExecution(); + final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); + jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( st ); + jdbcCoordinator.afterStatementExecution(); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java index 05a613203bd7c591f423278468eefac202015209..578d2b814a597a997ea34c026f8540b881509028 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java @@ -49,7 +49,7 @@ public abstract class AbstractLoadPlanBasedEntityLoader extends AbstractLoadPlan private final Type uniqueKeyType; private final String entityName; - private final LoadQueryDetails staticLoadQuery; + private final EntityLoadQueryDetails staticLoadQuery; public AbstractLoadPlanBasedEntityLoader( OuterJoinLoadable entityPersister, @@ -65,17 +65,20 @@ public AbstractLoadPlanBasedEntityLoader( final LoadPlanBuildingAssociationVisitationStrategy strategy; if ( buildingParameters.getQueryInfluencers().getFetchGraph() != null ) { strategy = new FetchGraphLoadPlanBuildingStrategy( - factory, buildingParameters.getQueryInfluencers(),buildingParameters.getLockMode() + factory, buildingParameters.getQueryInfluencers(), + buildingParameters.getLockOptions() != null ? buildingParameters.getLockOptions().getLockMode() : buildingParameters.getLockMode() ); } else if ( buildingParameters.getQueryInfluencers().getLoadGraph() != null ) { strategy = new LoadGraphLoadPlanBuildingStrategy( - factory, buildingParameters.getQueryInfluencers(),buildingParameters.getLockMode() + factory, buildingParameters.getQueryInfluencers(), + buildingParameters.getLockOptions() != null ? buildingParameters.getLockOptions().getLockMode() : buildingParameters.getLockMode() ); } else { strategy = new FetchStyleLoadPlanBuildingAssociationVisitationStrategy( - factory, buildingParameters.getQueryInfluencers(),buildingParameters.getLockMode() + factory, buildingParameters.getQueryInfluencers(), + buildingParameters.getLockOptions() != null ? buildingParameters.getLockOptions().getLockMode() : buildingParameters.getLockMode() ); } @@ -140,6 +143,9 @@ public final List loadEntityBatch( final QueryParameters qp = new QueryParameters(); qp.setPositionalParameterTypes( types ); qp.setPositionalParameterValues( ids ); + qp.setOptionalObject( optionalObject ); + qp.setOptionalEntityName( optionalEntityName ); + qp.setOptionalId( optionalId ); qp.setLockOptions( lockOptions ); result = executeLoad( @@ -189,7 +195,7 @@ public Object load(Serializable id, Object optionalObject, SharedSessionContract false, null ); - result = extractEntityResult( results ); + result = extractEntityResult( results, id ); } catch ( SQLException sqle ) { throw session.getJdbcServices().getSqlExceptionHelper().convert( @@ -208,14 +214,22 @@ public Object load(Serializable id, Object optionalObject, SharedSessionContract return result; } + /** + * @deprecated {@link #extractEntityResult(List, Serializable)} should be used instead. + */ + @Deprecated protected Object extractEntityResult(List results) { + return extractEntityResult( results, null ); + } + + protected Object extractEntityResult(List results, Serializable id) { if ( results.size() == 0 ) { return null; } else if ( results.size() == 1 ) { return results.get( 0 ); } - else { + else if ( staticLoadQuery.hasCollectionInitializers() ) { final Object row = results.get( 0 ); if ( row.getClass().isArray() ) { // the logical type of the result list is List. See if the contained @@ -230,7 +244,20 @@ else if ( results.size() == 1 ) { } } - throw new HibernateException( "Unable to interpret given query results in terms of a load-entity query" ); + if ( id == null ) { + throw new HibernateException( + "Unable to interpret given query results in terms of a load-entity query for " + + entityName + ); + } + else { + throw new HibernateException( + "More than one row with the given identifier was found: " + + id + + ", for class: " + + entityName + ); + } } protected int[] getNamedParameterLocs(String name) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/hql/QueryLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/hql/QueryLoader.java index 8c843e4239552604bbb8c423c73adcdffe724c1e..f103f413776b08ae521420a6639b2d2cd0c183f7 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/hql/QueryLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/hql/QueryLoader.java @@ -37,6 +37,7 @@ import org.hibernate.internal.IteratorImpl; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.loader.BasicLoader; +import org.hibernate.loader.internal.AliasConstantsHelper; import org.hibernate.loader.spi.AfterLoadAction; import org.hibernate.param.ParameterSpecification; import org.hibernate.persister.collection.CollectionPersister; @@ -159,7 +160,7 @@ private void initialize(SelectClause selectClause) { entityAliases[i] = element.getClassAlias(); sqlAliasByEntityAlias.put( entityAliases[i], sqlAliases[i] ); // TODO should we just collect these like with the collections above? - sqlAliasSuffixes[i] = ( size == 1 ) ? "" : (Integer.toString( i ) + "_").intern(); + sqlAliasSuffixes[i] = ( size == 1 ) ? "" : AliasConstantsHelper.get( i ); // sqlAliasSuffixes[i] = element.getColumnAliasSuffix(); includeInSelect[i] = !element.isFetch(); if ( includeInSelect[i] ) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/AliasConstantsHelper.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/AliasConstantsHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..7fe126ae2ada1c7ee124e0bb16ffd683b8267c15 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/AliasConstantsHelper.java @@ -0,0 +1,46 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.loader.internal; + +/** + * @author Sanne Grinovero + */ +public final class AliasConstantsHelper { + + private static final int MAX_POOL_SIZE = 40; + private static final String[] pool = initPool( MAX_POOL_SIZE ); + + /** + * Returns the same as Integer.toString( i ) + '_' + * Strings might be returned from a pool of constants, when i + * is within the range of expected most commonly requested elements. + * + * @param i + * @return + */ + public static String get(final int i) { + if ( i < MAX_POOL_SIZE && i >= 0 ) { + return pool[i]; + } + else { + return internalAlias( i ); + } + } + + private static String[] initPool(final int maxPoolSize) { + String[] pool = new String[maxPoolSize]; + for ( int i = 0; i < maxPoolSize; i++ ) { + pool[i] = internalAlias( i ); + } + return pool; + } + + private static String internalAlias(final int i) { + return Integer.toString( i ) + '_'; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractEntityGraphVisitationStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractEntityGraphVisitationStrategy.java index f5e2014dd90b822e0beef94b8bbf98761f873c2e..cc04def30b80ab85cf4d6b8ed89e93f7def2506f 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractEntityGraphVisitationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractEntityGraphVisitationStrategy.java @@ -52,7 +52,7 @@ * and we try to match the node to entity graph ( and subgraph ), if there is a match, then the attribute is fetched, * it is not, then depends on which property is used to apply this entity graph. * - * @author Strong Liu + * @author Strong Liu * @author Brett Meyer */ public abstract class AbstractEntityGraphVisitationStrategy diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractLoadPlanBuildingAssociationVisitationStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractLoadPlanBuildingAssociationVisitationStrategy.java index 848701f50e5411afad9ca0087ab134b2520baa87..367d3fff4bc063e2adbfe9f9bbffc8cc95c4d02c 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractLoadPlanBuildingAssociationVisitationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractLoadPlanBuildingAssociationVisitationStrategy.java @@ -666,23 +666,30 @@ public void foundCircularAssociation(AssociationAttributeDefinition attributeDef // go ahead and build the bidirectional fetch if ( attributeDefinition.getAssociationNature() == AssociationAttributeDefinition.AssociationNature.ENTITY ) { - final Joinable currentEntityPersister = (Joinable) currentSource().resolveEntityReference().getEntityPersister(); - final AssociationKey currentEntityReferenceAssociationKey = - new AssociationKey( currentEntityPersister.getTableName(), currentEntityPersister.getKeyColumnNames() ); - // if associationKey is equal to currentEntityReferenceAssociationKey - // that means that the current EntityPersister has a single primary key attribute - // (i.e., derived attribute) which is mapped by attributeDefinition. - // This is not a bidirectional association. - // TODO: AFAICT, to avoid an overflow, the associated entity must already be loaded into the session, or - // it must be loaded when the ID for the dependent entity is resolved. Is there some other way to - // deal with this??? final FetchSource registeredFetchSource = registeredFetchSource( associationKey ); - if ( registeredFetchSource != null && ! associationKey.equals( currentEntityReferenceAssociationKey ) ) { - currentSource().buildBidirectionalEntityReference( - attributeDefinition, - fetchStrategy, - registeredFetchSource( associationKey ).resolveEntityReference() - ); + if ( registeredFetchSource != null ) { + final ExpandingFetchSource currentSource = currentSource(); + final Joinable currentEntityPersister = (Joinable) currentSource.resolveEntityReference().getEntityPersister(); + + final AssociationKey currentEntityReferenceAssociationKey = + new AssociationKey( currentEntityPersister.getTableName(), currentEntityPersister.getKeyColumnNames() ); + // if associationKey is equal to currentEntityReferenceAssociationKey + // that means that the current EntityPersister has a single primary key attribute + // (i.e., derived attribute) which is mapped by attributeDefinition. + // This is not a bidirectional association. + // TODO: AFAICT, to avoid an overflow, the associated entity must already be loaded into the session, or + // it must be loaded when the ID for the dependent entity is resolved. Is there some other way to + // deal with this??? + if ( !associationKey.equals( currentEntityReferenceAssociationKey ) ) { + currentSource.buildBidirectionalEntityReference( + attributeDefinition, + fetchStrategy, + registeredFetchSource.resolveEntityReference() + ); + } + } + else { + // Do nothing, no bi-directionality } } else { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchGraphLoadPlanBuildingStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchGraphLoadPlanBuildingStrategy.java index 27c270923085168ba5c129b111f81b862aad593e..0de03dd47967a36d03ac54cf29e90f1bdc9560dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchGraphLoadPlanBuildingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchGraphLoadPlanBuildingStrategy.java @@ -16,7 +16,7 @@ /** * Loadplan building strategy for {@link javax.persistence.EntityGraph} is applied in {@code javax.persistence.fetchgraph} mode. * - * @author Strong Liu + * @author Strong Liu */ public class FetchGraphLoadPlanBuildingStrategy extends AbstractEntityGraphVisitationStrategy { private final GraphNodeImplementor rootEntityGraph; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/LoadGraphLoadPlanBuildingStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/LoadGraphLoadPlanBuildingStrategy.java index 63382fffc66d22b6a49aceb6d69a0b5501ea3dfb..842a295c98e77ba6a886609e5202b5844d40ba36 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/LoadGraphLoadPlanBuildingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/LoadGraphLoadPlanBuildingStrategy.java @@ -19,7 +19,7 @@ /** * Loadplan building strategy for {@link javax.persistence.EntityGraph} is applied in {@code javax.persistence.loadgraph} mode. * - * @author Strong Liu + * @author Strong Liu */ public class LoadGraphLoadPlanBuildingStrategy extends AbstractEntityGraphVisitationStrategy { private final GraphNodeImplementor rootEntityGraph; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/spi/ExpandingCollectionQuerySpace.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/spi/ExpandingCollectionQuerySpace.java index ce4938f4168fc605583388a8fb632696e9cf0822..de7e5c528f91a665575fa7978fba3c3d7602a7d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/spi/ExpandingCollectionQuerySpace.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/spi/ExpandingCollectionQuerySpace.java @@ -29,7 +29,7 @@ public interface ExpandingCollectionQuerySpace extends CollectionQuerySpace, Exp * @param join The element or index join to add. * * @throws java.lang.IllegalArgumentException if {@code join} is an instance of - * {@link org.hibernate.loader.plan.spi.JoinDefinedByMetadata} and {@code join.getJoinedPropertyName() + * {@link org.hibernate.loader.plan.spi.JoinDefinedByMetadata} and {@code join.getJoinedPropertyName()} * is neither {@code "elements"} and {@code "indices"}. * @throws java.lang.IllegalStateException if there is already an existing join with the same joined property name. */ diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java index bcc8147baa5efd14091501ebebd7d20e2ad6b648..f3fcead56b3827c1ce11cf80298ff0d306ec9c02 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java @@ -12,6 +12,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -24,6 +25,7 @@ import org.hibernate.dialect.pagination.LimitHelper; import org.hibernate.dialect.pagination.NoopLimitHandler; import org.hibernate.engine.jdbc.ColumnNameCache; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.ResultSetWrapper; import org.hibernate.engine.spi.PersistenceContext; @@ -37,6 +39,7 @@ import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext; import org.hibernate.loader.plan.exec.spi.LoadQueryDetails; import org.hibernate.loader.spi.AfterLoadAction; +import org.hibernate.resource.jdbc.ResourceRegistry; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.Type; @@ -82,24 +85,6 @@ protected List executeLoad( LoadQueryDetails loadQueryDetails, boolean returnProxies, ResultTransformer forcedResultTransformer) throws SQLException { - final List afterLoadActions = new ArrayList(); - return executeLoad( - session, - queryParameters, - loadQueryDetails, - returnProxies, - forcedResultTransformer, - afterLoadActions - ); - } - - protected List executeLoad( - SharedSessionContractImplementor session, - QueryParameters queryParameters, - LoadQueryDetails loadQueryDetails, - boolean returnProxies, - ResultTransformer forcedResultTransformer, - List afterLoadActions) throws SQLException { final PersistenceContext persistenceContext = session.getPersistenceContext(); final boolean defaultReadOnlyOrig = persistenceContext.isDefaultReadOnly(); if ( queryParameters.isReadOnlyInitialized() ) { @@ -114,11 +99,11 @@ protected List executeLoad( } persistenceContext.beforeLoad(); try { - List results = null; + final List results; final String sql = loadQueryDetails.getSqlStatement(); SqlStatementWrapper wrapper = null; try { - wrapper = executeQueryStatement( sql, queryParameters, false, afterLoadActions, session ); + wrapper = executeQueryStatement( sql, queryParameters, false, session ); results = loadQueryDetails.getResultSetProcessor().extractResults( wrapper.getResultSet(), session, @@ -132,17 +117,15 @@ public int[] getNamedParameterLocations(String name) { returnProxies, queryParameters.isReadOnly(), forcedResultTransformer, - afterLoadActions + Collections.EMPTY_LIST ); } finally { if ( wrapper != null ) { - session.getJdbcCoordinator().getResourceRegistry().release( - wrapper.getResultSet(), - wrapper.getStatement() - ); - session.getJdbcCoordinator().getResourceRegistry().release( wrapper.getStatement() ); - session.getJdbcCoordinator().afterStatementExecution(); + final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); + final ResourceRegistry resourceRegistry = jdbcCoordinator.getResourceRegistry(); + resourceRegistry.release( wrapper.getStatement() ); + jdbcCoordinator.afterStatementExecution(); } persistenceContext.afterLoad(); } @@ -158,16 +141,14 @@ public int[] getNamedParameterLocations(String name) { protected SqlStatementWrapper executeQueryStatement( final QueryParameters queryParameters, final boolean scroll, - List afterLoadActions, final SharedSessionContractImplementor session) throws SQLException { - return executeQueryStatement( getStaticLoadQuery().getSqlStatement(), queryParameters, scroll, afterLoadActions, session ); + return executeQueryStatement( getStaticLoadQuery().getSqlStatement(), queryParameters, scroll, session ); } protected SqlStatementWrapper executeQueryStatement( String sqlStatement, QueryParameters queryParameters, boolean scroll, - List afterLoadActions, SharedSessionContractImplementor session) throws SQLException { // Processing query filters. @@ -177,7 +158,7 @@ protected SqlStatementWrapper executeQueryStatement( final LimitHandler limitHandler = getLimitHandler( queryParameters.getRowSelection() ); - String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters.getRowSelection() ); + String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters ); // Adding locks and comments. sql = session.getJdbcServices().getJdbcEnvironment().getDialect() diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java index 106c2c9e315e00efd627d0444e29ab7c48429a26..ee0e6482082e5535cf8fd23e9563564435e953de 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadQueryDetails.java @@ -40,7 +40,6 @@ public abstract class AbstractLoadQueryDetails implements LoadQueryDetails { private final String[] keyColumnNames; private final Return rootReturn; private final LoadQueryJoinAndFetchProcessor queryProcessor; - private final QueryBuildingParameters buildingParameters; private String sqlStatement; private ResultSetProcessor resultSetProcessor; @@ -59,30 +58,7 @@ protected AbstractLoadQueryDetails( this.keyColumnNames = keyColumnNames; this.rootReturn = rootReturn; this.loadPlan = loadPlan; - this.queryProcessor = new LoadQueryJoinAndFetchProcessor( - aliasResolutionContext, buildingParameters.getQueryInfluencers(), factory - ); - this.buildingParameters = buildingParameters; - } - - /** - * Constructs an AbstractLoadQueryDetails object from an initial object and new building parameters, - * with the guarantee that only batch size changed between the initial parameters and the new ones. - * - * @param initialLoadQueryDetails The initial object to be copied - * @param buildingParameters The new building parameters, with only the batch size being different - * from the parameters used in the initial object. - */ - protected AbstractLoadQueryDetails( - AbstractLoadQueryDetails initialLoadQueryDetails, - QueryBuildingParameters buildingParameters) { - this.keyColumnNames = initialLoadQueryDetails.keyColumnNames; - this.rootReturn = initialLoadQueryDetails.rootReturn; - this.loadPlan = initialLoadQueryDetails.loadPlan; - this.queryProcessor = new LoadQueryJoinAndFetchProcessor( - initialLoadQueryDetails.queryProcessor, buildingParameters.getQueryInfluencers() - ); - this.buildingParameters = buildingParameters; + this.queryProcessor = new LoadQueryJoinAndFetchProcessor( aliasResolutionContext, buildingParameters, factory ); } protected QuerySpace getQuerySpace(String querySpaceUid) { @@ -108,7 +84,7 @@ protected final AliasResolutionContext getAliasResolutionContext() { } protected final QueryBuildingParameters getQueryBuildingParameters() { - return buildingParameters; + return queryProcessor.getQueryBuildingParameters(); } protected final SessionFactoryImplementor getSessionFactory() { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AliasResolutionContextImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AliasResolutionContextImpl.java index fdd434ec84132241d8dbc2ecf0c4e52f2a567d67..0e48b943d43453fa5cc4b931d9c7faffebc971b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AliasResolutionContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AliasResolutionContextImpl.java @@ -19,6 +19,7 @@ import org.hibernate.loader.DefaultEntityAliases; import org.hibernate.loader.EntityAliases; import org.hibernate.loader.GeneratedCollectionAliases; +import org.hibernate.loader.internal.AliasConstantsHelper; import org.hibernate.loader.plan.build.spi.QuerySpaceTreePrinter; import org.hibernate.loader.plan.build.spi.TreePrinterHelper; import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; @@ -34,6 +35,8 @@ import org.jboss.logging.Logger; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * Provides aliases that are used by load queries and ResultSet processors. * @@ -140,7 +143,7 @@ private EntityAliases createEntityAliases(EntityPersister entityPersister) { } private String createSuffix() { - return Integer.toString( currentAliasSuffix++ ) + '_'; + return AliasConstantsHelper.get( currentAliasSuffix++ ); } /** @@ -151,7 +154,7 @@ private String createSuffix() { * query space UID: *
        *
      • - * {@link ##resolveCollectionReferenceAliases(String)} can be used to + * {@link #resolveCollectionReferenceAliases(String)} can be used to * look up the returned collection reference aliases; *
      • *
      • @@ -237,7 +240,7 @@ private void registerSqlTableAliasMapping(String querySpaceUid, String sqlTableA if ( querySpaceUidToSqlTableAliasMap == null ) { querySpaceUidToSqlTableAliasMap = new HashMap(); } - String old = querySpaceUidToSqlTableAliasMap.put( querySpaceUid, sqlTableAlias ); + String old = querySpaceUidToSqlTableAliasMap.put( safeInterning( querySpaceUid ), safeInterning( sqlTableAlias ) ); if ( old != null ) { if ( old.equals( sqlTableAlias ) ) { // silently ignore... diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java index 6803b709978b923375628193594c2032728daba6..3b03f72d9ce2920c368a4916e47527494d6cea12 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/BatchingLoadQueryDetailsFactory.java @@ -41,7 +41,7 @@ private BatchingLoadQueryDetailsFactory() { * * @return The EntityLoadQueryDetails */ - public LoadQueryDetails makeEntityLoadQueryDetails( + public EntityLoadQueryDetails makeEntityLoadQueryDetails( LoadPlan loadPlan, String[] keyColumnNames, QueryBuildingParameters buildingParameters, @@ -76,7 +76,7 @@ public LoadQueryDetails makeEntityLoadQueryDetails( * that add additional joins here) * @return The EntityLoadQueryDetails */ - public LoadQueryDetails makeEntityLoadQueryDetails( + public EntityLoadQueryDetails makeEntityLoadQueryDetails( EntityLoadQueryDetails entityLoadQueryDetailsTemplate, QueryBuildingParameters buildingParameters) { return new EntityLoadQueryDetails( diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java index f7052d5ac8a083b93f9387ff48c0577305cf0a86..7c78004c5112002de9a58452da8ff0ede8580dc0 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityLoadQueryDetails.java @@ -14,6 +14,7 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.loader.plan.exec.process.internal.AbstractRowReader; import org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl; import org.hibernate.loader.plan.exec.process.internal.EntityReturnReader; @@ -84,24 +85,22 @@ protected EntityLoadQueryDetails( generate(); } - /** - * Constructs an EntityLoadQueryDetails object from an initial object and new building parameters, - * with the guarantee that only batch size changed between the initial parameters and the new ones. - * - * @param initialEntityLoadQueryDetails The initial object to be copied - * @param buildingParameters The new building parameters, with only the batch size being different - * from the parameters used in the initial object. - */ protected EntityLoadQueryDetails( EntityLoadQueryDetails initialEntityLoadQueryDetails, QueryBuildingParameters buildingParameters) { - super( - initialEntityLoadQueryDetails, - buildingParameters + this( + initialEntityLoadQueryDetails.getLoadPlan(), + initialEntityLoadQueryDetails.getKeyColumnNames(), + new AliasResolutionContextImpl( initialEntityLoadQueryDetails.getSessionFactory() ), + (EntityReturn) initialEntityLoadQueryDetails.getRootReturn(), + buildingParameters, + initialEntityLoadQueryDetails.getSessionFactory() ); - this.entityReferenceAliases = initialEntityLoadQueryDetails.entityReferenceAliases; - this.readerCollector = initialEntityLoadQueryDetails.readerCollector; - generate(); + } + + public boolean hasCollectionInitializers() { + return CollectionHelper.isNotEmpty( readerCollector.getArrayReferenceInitializers() ) || + CollectionHelper.isNotEmpty( readerCollector.getNonArrayCollectionReferenceInitializers() ); } private EntityReturn getRootEntityReturn() { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityReferenceAliasesImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityReferenceAliasesImpl.java index 16df015cec23abe7c74ea79372b977eab7d62da0..2d4d87c74e2723344a38db4304229f46450eb526 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityReferenceAliasesImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityReferenceAliasesImpl.java @@ -9,6 +9,8 @@ import org.hibernate.loader.EntityAliases; import org.hibernate.loader.plan.exec.spi.EntityReferenceAliases; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * @author Gail Badner * @author Steve Ebersole @@ -18,7 +20,7 @@ public class EntityReferenceAliasesImpl implements EntityReferenceAliases { private final EntityAliases columnAliases; public EntityReferenceAliasesImpl(String tableAlias, EntityAliases columnAliases) { - this.tableAlias = tableAlias; + this.tableAlias = safeInterning( tableAlias ); this.columnAliases = columnAliases; } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java index 070b8c5bb133042a2b825c33dafcf5f2df16557b..333f3384d01eb28d68c7f57f8b15cabe0716e81e 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/LoadQueryJoinAndFetchProcessor.java @@ -13,7 +13,6 @@ import org.hibernate.AssertionFailure; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; -import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.util.StringHelper; @@ -21,6 +20,7 @@ import org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl; import org.hibernate.loader.plan.exec.process.spi.ReaderCollector; import org.hibernate.loader.plan.exec.query.internal.SelectStatementBuilder; +import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters; import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; import org.hibernate.loader.plan.exec.spi.CollectionReferenceAliases; import org.hibernate.loader.plan.exec.spi.EntityReferenceAliases; @@ -66,51 +66,34 @@ public class LoadQueryJoinAndFetchProcessor { private static final Logger LOG = CoreLogging.logger( LoadQueryJoinAndFetchProcessor.class ); - private final AliasResolutionContext aliasResolutionContext; - private final AliasResolutionContextImpl mutableAliasResolutionContext; - private final LoadQueryInfluencers queryInfluencers; + private final AliasResolutionContextImpl aliasResolutionContext; + private final QueryBuildingParameters buildingParameters; private final SessionFactoryImplementor factory; /** * Instantiates a LoadQueryJoinAndFetchProcessor with the given information * - * @param mutableAliasResolutionContext - * @param queryInfluencers + * @param aliasResolutionContext + * @param buildingParameters * @param factory */ public LoadQueryJoinAndFetchProcessor( - AliasResolutionContextImpl mutableAliasResolutionContext, - LoadQueryInfluencers queryInfluencers, + AliasResolutionContextImpl aliasResolutionContext, + QueryBuildingParameters buildingParameters, SessionFactoryImplementor factory) { - this.aliasResolutionContext = mutableAliasResolutionContext; - this.mutableAliasResolutionContext = mutableAliasResolutionContext; - this.queryInfluencers = queryInfluencers; + this.aliasResolutionContext = aliasResolutionContext; + this.buildingParameters = buildingParameters; this.factory = factory; } - /** - * Instantiates a LoadQueryJoinAndFetchProcessor from an initial object and new query influencers. - * - * Aliases are considered already contributed to the initial objects alias resolution context - * and won't be added again. - * - * @param initialLoadQueryJoinAndFetchProcessor The initial object to be copied - * @param queryInfluencers The new query influencers - */ - public LoadQueryJoinAndFetchProcessor( - LoadQueryJoinAndFetchProcessor initialLoadQueryJoinAndFetchProcessor, - LoadQueryInfluencers queryInfluencers) { - this.aliasResolutionContext = initialLoadQueryJoinAndFetchProcessor.aliasResolutionContext; - // Do not change the alias resolution context, it should be pre-populated - this.mutableAliasResolutionContext = null; - this.queryInfluencers = queryInfluencers; - this.factory = initialLoadQueryJoinAndFetchProcessor.factory; - } - public AliasResolutionContext getAliasResolutionContext() { return aliasResolutionContext; } + public QueryBuildingParameters getQueryBuildingParameters() { + return buildingParameters; + } + public SessionFactoryImplementor getSessionFactory() { return factory; } @@ -182,12 +165,7 @@ private void handleCompositeJoin(Join join, JoinFragment joinFragment) { ); } - if ( mutableAliasResolutionContext != null ) { - mutableAliasResolutionContext.registerCompositeQuerySpaceUidResolution( - rightHandSideUid, - leftHandSideTableAlias - ); - } + aliasResolutionContext.registerCompositeQuerySpaceUidResolution( rightHandSideUid, leftHandSideTableAlias ); } private void renderEntityJoin(Join join, JoinFragment joinFragment) { @@ -195,8 +173,8 @@ private void renderEntityJoin(Join join, JoinFragment joinFragment) { // see if there is already aliases registered for this entity query space (collection joins) EntityReferenceAliases aliases = aliasResolutionContext.resolveEntityReferenceAliases( rightHandSide.getUid() ); - if ( aliases == null && mutableAliasResolutionContext != null ) { - mutableAliasResolutionContext.generateEntityReferenceAliases( + if ( aliases == null ) { + aliasResolutionContext.generateEntityReferenceAliases( rightHandSide.getUid(), rightHandSide.getEntityPersister() ); @@ -227,10 +205,10 @@ private String resolveAdditionalJoinCondition(String rhsTableAlias, String withC // calls to the Joinable.filterFragment() method where the Joinable is either the entity or // collection persister final String filter = associationType!=null? - associationType.getOnCondition( rhsTableAlias, factory, queryInfluencers.getEnabledFilters() ): + associationType.getOnCondition( rhsTableAlias, factory, buildingParameters.getQueryInfluencers().getEnabledFilters() ): joinable.filterFragment( rhsTableAlias, - queryInfluencers.getEnabledFilters() + buildingParameters.getQueryInfluencers().getEnabledFilters() ); if ( StringHelper.isEmpty( withClause ) && StringHelper.isEmpty( filter ) ) { @@ -281,9 +259,11 @@ else if ( !StringHelper.isEmpty( joinConditions ) ) { ); String[] joinColumns = join.resolveAliasedLeftHandSideJoinConditionColumns( lhsTableAlias ); - if ( joinColumns.length == 0 ) { + QuerySpace lhsQuerySpace = join.getLeftHandSide(); + if ( joinColumns.length == 0 && lhsQuerySpace instanceof EntityQuerySpace ) { // When no columns are available, this is a special join that involves multiple subtypes - AbstractEntityPersister persister = (AbstractEntityPersister) ( (EntityQuerySpace) join.getLeftHandSide() ).getEntityPersister(); + EntityQuerySpace entityQuerySpace = (EntityQuerySpace) lhsQuerySpace; + AbstractEntityPersister persister = (AbstractEntityPersister) entityQuerySpace.getEntityPersister(); String[][] polyJoinColumns = persister.getPolymorphicJoinColumns( lhsTableAlias, @@ -317,19 +297,7 @@ else if ( !StringHelper.isEmpty( joinConditions ) ) { private void renderCollectionJoin(Join join, JoinFragment joinFragment) { final CollectionQuerySpace rightHandSide = (CollectionQuerySpace) join.getRightHandSide(); - if ( mutableAliasResolutionContext != null ) { - registerCollectionJoinAliases( mutableAliasResolutionContext, rightHandSide ); - } - addJoins( - join, - joinFragment, - (Joinable) rightHandSide.getCollectionPersister(), - null - ); - } - private void registerCollectionJoinAliases(AliasResolutionContextImpl mutableAliasResolutionContext, - CollectionQuerySpace rightHandSide) { // The SQL join to the "collection table" needs to be rendered. // // In the case of a basic collection, that's the only join needed. @@ -385,14 +353,14 @@ private void registerCollectionJoinAliases(AliasResolutionContextImpl mutableAli ) ); } - mutableAliasResolutionContext.generateCollectionReferenceAliases( + aliasResolutionContext.generateCollectionReferenceAliases( rightHandSide.getUid(), rightHandSide.getCollectionPersister(), collectionElementJoin.getRightHandSide().getUid() ); } else { - mutableAliasResolutionContext.generateCollectionReferenceAliases( + aliasResolutionContext.generateCollectionReferenceAliases( rightHandSide.getUid(), rightHandSide.getCollectionPersister(), null @@ -412,11 +380,17 @@ private void registerCollectionJoinAliases(AliasResolutionContextImpl mutableAli ) ); } - mutableAliasResolutionContext.generateEntityReferenceAliases( + aliasResolutionContext.generateEntityReferenceAliases( collectionIndexJoin.getRightHandSide().getUid(), rightHandSide.getCollectionPersister().getIndexDefinition().toEntityDefinition().getEntityPersister() ); } + addJoins( + join, + joinFragment, + (Joinable) rightHandSide.getCollectionPersister(), + null + ); } private void renderManyToManyJoin( @@ -447,7 +421,7 @@ private void renderManyToManyJoin( final CollectionPersister persister = leftHandSide.getCollectionPersister(); manyToManyFilter = persister.getManyToManyFilterFragment( entityTableAlias, - queryInfluencers.getEnabledFilters() + buildingParameters.getQueryInfluencers().getEnabledFilters() ); } else { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/AbstractRowReader.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/AbstractRowReader.java index b1823ec453c22452b0680e0c527f397e1554b1ed..2083474b9a865fd58fc846cf2465a46ba6a86f1c 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/AbstractRowReader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/AbstractRowReader.java @@ -15,9 +15,15 @@ import java.util.Map; import org.hibernate.engine.internal.TwoPhaseLoad; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.event.service.spi.EventListenerGroup; +import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventSource; +import org.hibernate.event.spi.EventType; import org.hibernate.event.spi.PostLoadEvent; +import org.hibernate.event.spi.PostLoadEventListener; import org.hibernate.event.spi.PreLoadEvent; +import org.hibernate.event.spi.PreLoadEventListener; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.loader.plan.exec.process.spi.CollectionReferenceInitializer; @@ -230,16 +236,25 @@ private void performTwoPhaseLoad( : hydratedEntityRegistrations.size(); log.tracev( "Total objects hydrated: {0}", numberOfHydratedObjects ); - if ( hydratedEntityRegistrations == null ) { + if ( numberOfHydratedObjects == 0 ) { return; } + final SharedSessionContractImplementor session = context.getSession(); + final Iterable listeners = session + .getFactory() + .getServiceRegistry() + .getService( EventListenerRegistry.class ) + .getEventListenerGroup( EventType.PRE_LOAD ) + .listeners(); + for ( HydratedEntityRegistration registration : hydratedEntityRegistrations ) { TwoPhaseLoad.initializeEntity( registration.getInstance(), context.isReadOnly(), - context.getSession(), - preLoadEvent + session, + preLoadEvent, + listeners ); } } @@ -259,16 +274,29 @@ private void postLoad( // split off from initializeEntity. It *must* occur after // endCollectionLoad to ensure the collection is in the // persistence context. - if ( hydratedEntityRegistrations == null ) { + if ( hydratedEntityRegistrations == null || hydratedEntityRegistrations.size() == 0 ) { return; } + final SharedSessionContractImplementor session = context.getSession(); + final Iterable postLoadEventListeners; + if ( session.isEventSource() ) { + final EventListenerGroup listenerGroup = session.getFactory() + .getServiceRegistry() + .getService( EventListenerRegistry.class ) + .getEventListenerGroup( EventType.POST_LOAD ); + postLoadEventListeners = listenerGroup.listeners(); + } + else { + postLoadEventListeners = Collections.emptyList(); + } + for ( HydratedEntityRegistration registration : hydratedEntityRegistrations ) { - TwoPhaseLoad.postLoad( registration.getInstance(), context.getSession(), postLoadEvent ); + TwoPhaseLoad.postLoad( registration.getInstance(), session, postLoadEvent, postLoadEventListeners ); if ( afterLoadActionList != null ) { for ( AfterLoadAction afterLoadAction : afterLoadActionList ) { afterLoadAction.afterLoad( - context.getSession(), + session, registration.getInstance(), (Loadable) registration.getEntityReference().getEntityPersister() ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java index 29767345976825eec2bf59b6c0e82699d9698613..094000b664cda284d62459b6f6b7d42c91f21217 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceInitializerImpl.java @@ -14,6 +14,9 @@ import org.hibernate.LockMode; import org.hibernate.StaleObjectStateException; import org.hibernate.WrongClassException; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.engine.internal.TwoPhaseLoad; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.EntityKey; @@ -195,6 +198,31 @@ public void hydrateEntityState(ResultSet resultSet, ResultSetProcessingContextIm // use the existing association as the hydrated state processingState.registerEntityInstance( existing ); //context.registerHydratedEntity( entityReference, entityKey, existing ); + + // see if the entity is enhanced and is being used as a "proxy" (is fully uninitialized) + final BytecodeEnhancementMetadata enhancementMetadata = entityReference.getEntityPersister() + .getEntityMetamodel() + .getBytecodeEnhancementMetadata(); + + if ( enhancementMetadata.isEnhancedForLazyLoading() ) { + final BytecodeLazyAttributeInterceptor interceptor = enhancementMetadata.extractLazyInterceptor( existing ); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + final LockMode requestedLockMode = context.resolveLockMode( entityReference ); + final LockMode lockModeToAcquire = requestedLockMode == LockMode.NONE + ? LockMode.READ + : requestedLockMode; + + loadFromResultSet( + resultSet, + context, + existing, + getConcreteEntityTypeName( resultSet, context, entityKey ), + entityKey, + lockModeToAcquire + ); + } + } + return; } @@ -202,9 +230,9 @@ public void hydrateEntityState(ResultSet resultSet, ResultSetProcessingContextIm // determine which entity instance to use. Either the supplied one, or instantiate one Object entityInstance = null; - if ( isReturn && - context.shouldUseOptionalEntityInformation() && - context.getQueryParameters().getOptionalObject() != null ) { + // If an "optional" instance with an EntityKey equal to entityKey is available + // in the context, then use that instance. + if ( isReturn && context.getQueryParameters().getOptionalObject() != null ) { final EntityKey optionalEntityKey = ResultSetProcessorHelper.getOptionalObjectKey( context.getQueryParameters(), context.getSession() diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/SelectStatementBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/SelectStatementBuilder.java index 3d2b1d8ac30319dae3c739e4ec325a0475c2c820..0a1f4df0ec7f6b08d5a6237a8e6cc1de622a97a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/SelectStatementBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/SelectStatementBuilder.java @@ -187,7 +187,7 @@ public String toStatementString() { StringBuilder buf = new StringBuilder( guesstimatedBufferSize ); if ( StringHelper.isNotEmpty( comment ) ) { - buf.append( "/* " ).append( comment ).append( " */ " ); + buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " ); } buf.append( "select " ) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/BidirectionalEntityReference.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/BidirectionalEntityReference.java index 23f18d6bb845dd8fd495096fe22e72f765286750..ef3e4726cbf2721c517ca91842b4f888139a0c83 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/BidirectionalEntityReference.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/BidirectionalEntityReference.java @@ -35,7 +35,7 @@ public interface BidirectionalEntityReference extends EntityReference { /** * The query space UID returned using {@link #getQuerySpaceUid()} must - * be the same as returned by {@link #getTargetEntityReference()#getQuerySpaceUid()} + * be the same as returned by {@link #getTargetEntityReference()} * * @return The query space UID. */ diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/FetchSource.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/FetchSource.java index bb4599e94bdeea9bfa6b61a5946521b7e7727edd..235c6a2a06a1b857df195b25509c243c07410f1d 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/FetchSource.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/FetchSource.java @@ -55,7 +55,7 @@ public interface FetchSource { * * @return the "current" EntityReference or null if none. * . - * @see org.hibernate.loader.plan.spi.Fetch#getSource(). + * @see org.hibernate.loader.plan.spi.Fetch#getSource() */ public EntityReference resolveEntityReference(); } 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 5e5fa4e9620781f04b789bb7b17695a9430120c0..d2dcc5aab60a84b38436a441025adf5093aea2ad 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java @@ -15,8 +15,11 @@ import org.hibernate.dialect.function.SQLFunctionRegistry; import org.hibernate.engine.spi.Mapping; import org.hibernate.internal.util.StringHelper; +import org.hibernate.loader.internal.AliasConstantsHelper; import org.hibernate.sql.Template; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * A column of a relational database table * @@ -90,21 +93,25 @@ public void setName(String name) { * returns quoted name as it would be in the mapping file. */ public String getQuotedName() { - return quoted ? + return safeInterning( + quoted ? "`" + name + "`" : - name; + name + ); } public String getQuotedName(Dialect d) { - return quoted ? + return safeInterning( + quoted ? d.openQuote() + name + d.closeQuote() : - name; + name + ); } @Override public String getAlias(Dialect dialect) { final int lastLetter = StringHelper.lastIndexOfLetter( name ); - final String suffix = Integer.toString( uniqueInteger ) + '_'; + final String suffix = AliasConstantsHelper.get( uniqueInteger ); String alias = name; if ( lastLetter == -1 ) { @@ -137,7 +144,7 @@ else if ( name.length() > lastLetter + 1 ) { */ @Override public String getAlias(Dialect dialect, Table table) { - return getAlias( dialect ) + table.getUniqueInteger() + '_'; + return safeInterning( getAlias( dialect ) + AliasConstantsHelper.get( table.getUniqueInteger() ) ); } public boolean isNullable() { @@ -268,10 +275,12 @@ public boolean hasCheckConstraint() { @Override public String getTemplate(Dialect dialect, SQLFunctionRegistry functionRegistry) { - return hasCustomRead() + return safeInterning( + hasCustomRead() // see note in renderTransformerReadFragment wrt access to SessionFactory ? Template.renderTransformerReadFragment( customRead, getQuotedName( dialect ) ) - : Template.TEMPLATE + '.' + getQuotedName( dialect ); + : Template.TEMPLATE + '.' + getQuotedName( dialect ) + ); } public boolean hasCustomRead() { @@ -338,7 +347,7 @@ public String getCustomWrite() { } public void setCustomWrite(String customWrite) { - this.customWrite = customWrite; + this.customWrite = safeInterning( customWrite ); } public String getCustomRead() { @@ -346,7 +355,7 @@ public String getCustomRead() { } public void setCustomRead(String customRead) { - this.customRead = StringHelper.nullIfEmpty( customRead ); + this.customRead = safeInterning( StringHelper.nullIfEmpty( customRead ) ); } public String getCanonicalName() { 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 53a54777d50604e5c1bed51037cc26cc1195accd..f988a8ba4e7df96296f134158c8637be166ab748 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java @@ -52,6 +52,9 @@ public class Component extends SimpleValue implements MetaAttributable { private java.util.Map tuplizerImpls; + // cache the status of the type + private volatile Type type; + /** * @deprecated User {@link Component#Component(MetadataBuildingContext, PersistentClass)} instead. */ @@ -209,13 +212,28 @@ public void setDynamic(boolean dynamic) { @Override public Type getType() throws MappingException { - // TODO : temporary initial step towards HHH-1907 - final ComponentMetamodel metamodel = new ComponentMetamodel( - this, - getMetadata().getMetadataBuildingOptions() - ); - final TypeFactory factory = getMetadata().getTypeConfiguration().getTypeResolver().getTypeFactory(); - return isEmbedded() ? factory.embeddedComponent( metamodel ) : factory.component( metamodel ); + // Resolve the type of the value once and for all as this operation generates a proxy class + // for each invocation. + // Unfortunately, there's no better way of doing that as none of the classes are immutable and + // we can't know for sure the current state of the property or the value. + Type localType = type; + + if ( localType == null ) { + synchronized ( this ) { + if ( type == null ) { + // TODO : temporary initial step towards HHH-1907 + final ComponentMetamodel metamodel = new ComponentMetamodel( + this, + getMetadata().getMetadataBuildingOptions() + ); + final TypeFactory factory = getMetadata().getTypeConfiguration().getTypeResolver().getTypeFactory(); + localType = isEmbedded() ? factory.embeddedComponent( metamodel ) : factory.component( metamodel ); + type = localType; + } + } + } + + return localType; } @Override @@ -288,15 +306,15 @@ public boolean[] getColumnUpdateability() { } return result; } - + public boolean isKey() { return isKey; } - + public void setKey(boolean isKey) { this.isKey = isKey; } - + public boolean hasPojoRepresentation() { return componentClassName!=null; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java b/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java index 8e671c6cffc697b0dc7c8b2ee53af40ebb5e3624..44c5db907b32ff7be8a3c9ef3b5d4eb492b6eb60 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java @@ -33,7 +33,7 @@ public ForeignKey() { @Override public String getExportIdentifier() { // NOt sure name is always set. Might need some implicit naming - return StringHelper.qualify( getTable().getName(), "FK-" + getName() ); + return StringHelper.qualify( getTable().getExportIdentifier(), "FK-" + getName() ); } public void disableCreation() { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java b/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java index 4d16249a97d54ee51c0b8a7a77bbb51ef60d7009..b32eae83642594f36bae881bfca1b71a180f1113 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Formula.java @@ -11,8 +11,11 @@ import org.hibernate.dialect.Dialect; import org.hibernate.dialect.function.SQLFunctionRegistry; import org.hibernate.internal.util.StringHelper; +import org.hibernate.loader.internal.AliasConstantsHelper; import org.hibernate.sql.Template; +import static org.hibernate.internal.util.StringHelper.safeInterning; + /** * A formula is a derived column value * @author Gavin King @@ -35,7 +38,7 @@ public Formula(String formula) { @Override public String getTemplate(Dialect dialect, SQLFunctionRegistry functionRegistry) { String template = Template.renderWhereStringTemplate(formula, dialect, functionRegistry); - return StringHelper.replace( template, "{alias}", Template.TEMPLATE ); + return safeInterning( StringHelper.replace( template, "{alias}", Template.TEMPLATE ) ); } @Override @@ -50,7 +53,7 @@ public String getText() { @Override public String getAlias(Dialect dialect) { - return "formula" + Integer.toString(uniqueInteger) + '_'; + return "formula" + AliasConstantsHelper.get( uniqueInteger ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Index.java b/hibernate-core/src/main/java/org/hibernate/mapping/Index.java index 7781ccceb317d78e7a411f2c2b1030fc3c5b401e..4d7da83ec7cc75056c9abbc2c59a10bdab2046a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Index.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Index.java @@ -238,6 +238,6 @@ public String toString() { @Override public String getExportIdentifier() { - return StringHelper.qualify( getTable().getName(), "IDX-" + getName() ); + return StringHelper.qualify( getTable().getExportIdentifier(), "IDX-" + getName() ); } } 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 51a37b4016fea87c6db8aa2c6829be0e6d9a61e0..168e97259fedd68f4482ddc0f522c371f968a277 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java @@ -71,7 +71,8 @@ public Type getType() throws MappingException { isLazy(), isUnwrapProxy(), entityName, - propertyName + propertyName, + constrained ); } else { @@ -83,7 +84,8 @@ public Type getType() throws MappingException { isLazy(), isUnwrapProxy(), entityName, - propertyName + propertyName, + constrained ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java b/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java index bcc9cd24486baf04d2f1baf72db2502afffae287..3c5e9a03b12708fc58de3388c9257e7e00f2803f 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java @@ -89,6 +89,6 @@ public String generatedConstraintNamePrefix() { @Override public String getExportIdentifier() { - return StringHelper.qualify( getTable().getName(), "PK-" + getName() ); + return StringHelper.qualify( getTable().getExportIdentifier(), "PK-" + getName() ); } } 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 ebf86b44e977ca85dcbc5034013b3058349afaf3..f18d27046b638cb94d48cd84a13d21a57d654656 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java @@ -14,6 +14,7 @@ import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.PropertyNotFoundException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadeStyles; import org.hibernate.engine.spi.Mapping; @@ -233,29 +234,31 @@ public String toString() { public void setLazy(boolean lazy) { this.lazy=lazy; } - + + /** + * Is this property lazy in the "bytecode" sense? + * + * Lazy here means whether we should push *something* to the entity + * instance for this field in its "base fetch group". Mainly it affects + * whether we should list this property's columns in the SQL select + * for the owning entity when we load its "base fetch group". + * + * The "something" we push varies based on the nature (basic, etc) of + * the property. + * + * @apiNote This form reports whether the property is considered part of the + * base fetch group based solely on the mapping information. However, + * {@link EnhancementHelper#includeInBaseFetchGroup} is used internally to make that + * decision to account for {@link org.hibernate.cfg.AvailableSettings#ALLOW_ENHANCEMENT_AS_PROXY} + */ public boolean isLazy() { if ( value instanceof ToOne ) { - // both many-to-one and one-to-one are represented as a - // Property. EntityPersister is relying on this value to - // determine "lazy fetch groups" in terms of field-level - // interception. So we need to make sure that we return - // true here for the case of many-to-one and one-to-one - // with lazy="no-proxy" - // - // * impl note - lazy="no-proxy" currently forces both - // lazy and unwrap to be set to true. The other case we - // are extremely interested in here is that of lazy="proxy" - // where lazy is set to true, but unwrap is set to false. - // thus we use both here under the assumption that this - // return is really only ever used during persister - // construction to determine the lazy property/field fetch - // groupings. If that assertion changes then this check - // needs to change as well. Partially, this is an issue with - // the overloading of the term "lazy" here... - ToOne toOneValue = ( ToOne ) value; - return toOneValue.isLazy() && toOneValue.isUnwrapProxy(); + // For a many-to-one, this is always false. Whether the + // association is EAGER, PROXY or NO-PROXY we want the fk + // selected + return false; } + return lazy; } 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 a563522f189a72203dac337760ad3a5ae3ad4d65..0e40a4eedd2cd2dd4b6b77df20a132113ff3fe88 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -597,8 +597,8 @@ public org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry getJava ); int jdbcTypeCode = recommendedSqlType.getSqlType(); if ( isLob() ) { - if ( LobTypeMappings.INSTANCE.hasCorrespondingLobCode( jdbcTypeCode ) ) { - jdbcTypeCode = LobTypeMappings.INSTANCE.getCorrespondingLobCode( jdbcTypeCode ); + if ( LobTypeMappings.isMappedToKnownLobCode( jdbcTypeCode ) ) { + jdbcTypeCode = LobTypeMappings.getLobCodeTypeMapping( jdbcTypeCode ); } else { if ( Serializable.class.isAssignableFrom( entityAttributeJavaTypeDescriptor.getJavaType() ) ) { @@ -617,7 +617,7 @@ public org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry getJava } } if ( isNationalized() ) { - jdbcTypeCode = NationalizedTypeMappings.INSTANCE.getCorrespondingNationalizedCode( jdbcTypeCode ); + jdbcTypeCode = NationalizedTypeMappings.toNationalizedTypeCode( jdbcTypeCode ); } // find the standard SqlTypeDescriptor for that JDBC type code (allow itr to be remapped if needed!) 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 f7ade4cb7c653ce2e37fba03ad763a06422de6d7..f9a9d868faacdbc98070558557565ff5532f1aec 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java @@ -408,7 +408,7 @@ public boolean equals(Table table) { && Identifier.areEqual( schema, table.schema ) && Identifier.areEqual( catalog, table.catalog ); } - + public void validateColumns(Dialect dialect, Mapping mapping, TableMetadata tableInfo) { Iterator iter = getColumnIterator(); while ( iter.hasNext() ) { @@ -441,28 +441,16 @@ public Iterator sqlAlterStrings( Dialect dialect, Metadata metadata, TableInformation tableInfo, - String defaultCatalog, - String defaultSchema) throws HibernateException { - - final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - - Identifier quotedCatalog = catalog != null && catalog.isQuoted() ? - new Identifier( tableInfo.getName().getCatalogName().getText(), true ) : - tableInfo.getName().getCatalogName(); - - Identifier quotedSchema = schema != null && schema.isQuoted() ? - new Identifier( tableInfo.getName().getSchemaName().getText(), true ) : - tableInfo.getName().getSchemaName(); + Identifier defaultCatalog, + Identifier defaultSchema) throws HibernateException { - Identifier quotedTable = name != null && name.isQuoted() ? - new Identifier( tableInfo.getName().getObjectName().getText(), true ) : - tableInfo.getName().getObjectName(); + final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); final String tableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( new QualifiedTableName( - quotedCatalog, - quotedSchema, - quotedTable + catalog != null ? catalog : defaultCatalog, + schema != null ? schema : defaultSchema, + name ), dialect ); @@ -473,7 +461,7 @@ public Iterator sqlAlterStrings( Iterator iter = getColumnIterator(); List results = new ArrayList(); - + while ( iter.hasNext() ) { final Column column = (Column) iter.next(); final ColumnInformation columnInfo = tableInfo.getColumn( Identifier.toIdentifier( column.getName(), column.isQuoted() ) ); @@ -581,7 +569,7 @@ public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, } } - + if ( col.isUnique() ) { String keyName = Constraint.generateName( "UK_", this, col ); UniqueKey uk = getOrCreateUniqueKey( keyName ); @@ -589,7 +577,7 @@ public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, buf.append( dialect.getUniqueDelegate() .getColumnDefinitionUniquenessFragment( col ) ); } - + if ( col.hasCheckConstraint() && dialect.supportsColumnCheck() ) { buf.append( " check (" ) .append( col.getCheckConstraint() ) diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java b/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java index c85a6847f74d73846add12304a5314bd4c2348a8..b3ff9fdf5d0c1183c75829890566249ae82d760c 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java @@ -72,6 +72,6 @@ public String generatedConstraintNamePrefix() { @Override public String getExportIdentifier() { - return StringHelper.qualify( getTable().getName(), "UK-" + getName() ); + return StringHelper.qualify( getTable().getExportIdentifier(), "UK-" + getName() ); } } 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 823ddfac2e0fe6950f2df70c36cf7b836a1dcb8c..ea42504ae9324f01d52d092e620f209d5fd6e244 100755 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java @@ -41,7 +41,7 @@ *
      • {@link #buildAttribute normal attributes}
      • *
      • {@link #buildIdAttribute id attributes}
      • *
      • {@link #buildVersionAttribute version attributes}
      • - *
          + *
        * * @author Steve Ebersole * @author Emmanuel Bernard 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 edfc52477e5e4cb36895ebe10b199ae90faea75b..e97fdad69112abcab4b3b85742113942645dc182 100755 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java @@ -114,16 +114,23 @@ public Map, MappedSuperclassType> getMappedSuperclassTypeMap() { } /*package*/ void registerEntityType(PersistentClass persistentClass, EntityTypeImpl entityType) { + if ( ignoreUnsupported && entityType.getBindableJavaType() == null ) { + return; + } + if ( entityType.getBindableJavaType() != null ) { entityTypes.put( entityType.getBindableJavaType(), entityType ); } + entityTypesByEntityName.put( persistentClass.getEntityName(), entityType ); entityTypesByPersistentClass.put( persistentClass, entityType ); orderedMappings.add( persistentClass ); } /*package*/ void registerEmbeddedableType(EmbeddableTypeImpl embeddableType) { - embeddables.add( embeddableType ); + if ( !( ignoreUnsupported && embeddableType.getParent().getJavaType() == null ) ) { + embeddables.add( embeddableType ); + } } /*package*/ void registerMappedSuperclassType( diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetamodelImpl.java index b3a3dc4b17de5948620a6886ecb618d72ce890ab..fb499da06d6c1c4362cd62a70d67f533c05fd100 100755 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetamodelImpl.java @@ -570,7 +570,7 @@ public Set> getManagedTypes() { jpaEntityTypeMap.size() + jpaMappedSuperclassTypeMap.size() + jpaEmbeddableTypes.size() ); final Set> managedTypes = new HashSet>( setSize ); - managedTypes.addAll( jpaEntityTypeMap.values() ); + managedTypes.addAll( jpaEntityTypesByEntityName.values() ); managedTypes.addAll( jpaMappedSuperclassTypeMap.values() ); managedTypes.addAll( jpaEmbeddableTypes ); return managedTypes; @@ -711,7 +711,7 @@ public String[] getAllEntityNames() { @Override public String[] getAllCollectionRoles() { - return ArrayHelper.toStringArray( entityPersisterMap.keySet() ); + return ArrayHelper.toStringArray( collectionPersisterMap.keySet() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java index adbff2b77fc6cebc18e539e45ca5ef55802a5b50..f282eab74eb24748f4227a058d17be692ee18bf6 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java @@ -18,6 +18,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; +import org.hibernate.Filter; import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.QueryException; @@ -701,7 +702,7 @@ protected CollectionInitializer getAppropriateInitializer(Serializable key, Shar if ( subselectInitializer != null ) { return subselectInitializer; } - else if ( session.getLoadQueryInfluencers().getEnabledFilters().isEmpty() ) { + else if ( ! session.getLoadQueryInfluencers().hasEnabledFilters() ) { return initializer; } else { @@ -1877,8 +1878,9 @@ public CacheEntryStructure getCacheEntryStructure() { @Override public boolean isAffectedByEnabledFilters(SharedSessionContractImplementor session) { - return filterHelper.isAffectedBy( session.getLoadQueryInfluencers().getEnabledFilters() ) || - ( isManyToMany() && manyToManyFilterHelper.isAffectedBy( session.getLoadQueryInfluencers().getEnabledFilters() ) ); + final Map enabledFilters = session.getLoadQueryInfluencers().getEnabledFilters(); + return filterHelper.isAffectedBy( enabledFilters ) || + ( isManyToMany() && manyToManyFilterHelper.isAffectedBy( enabledFilters ) ); } public boolean isSubselectLoadable() { 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 1204fa1e971fa946a60b8698ed7cafefb4a06879..127a9550b717c7861c27f8a9720b045a8cabc92d 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 @@ -29,14 +29,19 @@ import org.hibernate.EntityMode; import org.hibernate.FetchMode; import org.hibernate.HibernateException; +import org.hibernate.JDBCException; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.MappingException; +import org.hibernate.PropertyValueException; import org.hibernate.QueryException; import org.hibernate.Session; import org.hibernate.StaleObjectStateException; import org.hibernate.StaleStateException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeDescriptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; @@ -71,11 +76,15 @@ import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.Mapping; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistenceContext.NaturalIdHelper; import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.ValueInclusion; +import org.hibernate.id.Assigned; +import org.hibernate.id.ForeignGenerator; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.PostInsertIdentifierGenerator; import org.hibernate.id.PostInsertIdentityPersister; @@ -146,11 +155,12 @@ */ public abstract class AbstractEntityPersister implements OuterJoinLoadable, Queryable, ClassMetadata, UniqueKeyLoadable, - SQLLoadable, LazyPropertyInitializer, PostInsertIdentityPersister, Lockable { + SQLLoadable, LazyPropertyInitializer, PostInsertIdentityPersister, Lockable { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractEntityPersister.class ); public static final String ENTITY_CLASS = "class"; + public static final String VERSION_COLUMN_ALIAS = "version_"; private final NavigableRole navigableRole; @@ -535,7 +545,7 @@ public AbstractEntityPersister( if ( creationContext.getSessionFactory().getSessionFactoryOptions().isSecondLevelCacheEnabled() ) { this.canWriteToCache = determineCanWriteToCache( persistentClass, cacheAccessStrategy ); - this.canReadFromCache = determineCanReadFromCache( persistentClass ); + this.canReadFromCache = determineCanReadFromCache( persistentClass, cacheAccessStrategy ); this.cacheAccessStrategy = cacheAccessStrategy; this.isLazyPropertiesCacheable = persistentClass.getRootClass().isLazyPropertiesCacheable(); this.naturalIdRegionAccessStrategy = naturalIdRegionAccessStrategy; @@ -681,7 +691,13 @@ public AbstractEntityPersister( propertyColumnWriters[i] = colWriters; propertyColumnAliases[i] = colAliases; - if ( lazyAvailable && prop.isLazy() ) { + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + prop, + entityMetamodel.isInstrumented(), + creationContext.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled() + ); + + if ( lazy ) { lazyNames.add( prop.getName() ); lazyNumbers.add( i ); lazyTypes.add( prop.getValue().getType() ); @@ -751,7 +767,11 @@ public AbstractEntityPersister( int[] colnos = new int[prop.getColumnSpan()]; int[] formnos = new int[prop.getColumnSpan()]; int l = 0; - Boolean lazy = Boolean.valueOf( prop.isLazy() && lazyAvailable ); + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + prop, + entityMetamodel.isInstrumented(), + creationContext.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled() + ); while ( colIter.hasNext() ) { Selectable thing = (Selectable) colIter.next(); if ( thing.isFormula() ) { @@ -916,7 +936,11 @@ private boolean determineCanWriteToCache(PersistentClass persistentClass, Entity } @SuppressWarnings("unchecked") - private boolean determineCanReadFromCache(PersistentClass persistentClass) { + private boolean determineCanReadFromCache(PersistentClass persistentClass, EntityDataAccess cacheAccessStrategy) { + if ( cacheAccessStrategy == null ) { + return false; + } + if ( persistentClass.isCached() ) { return true; } @@ -1018,7 +1042,7 @@ protected Map generateLazySelectStringsByFetchGroup() { public Object initializeLazyProperty(String fieldName, Object entity, SharedSessionContractImplementor session) { final EntityEntry entry = session.getPersistenceContext().getEntry( entity ); - final InterceptorImplementor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); assert interceptor != null : "Expecting bytecode interceptor to be non-null"; if ( hasCollections() ) { @@ -1045,10 +1069,10 @@ public Object initializeLazyProperty(String fieldName, Object entity, SharedSess session.getPersistenceContext().addUninitializedCollection( persister, collection, key ); } - // HHH-11161 Initialize, if the collection is not extra lazy - if ( !persister.isExtraLazy() ) { - session.initializeCollection( collection, false ); - } +// // HHH-11161 Initialize, if the collection is not extra lazy +// if ( !persister.isExtraLazy() ) { +// session.initializeCollection( collection, false ); +// } interceptor.attributeInitialized( fieldName ); if ( collectionType.isArrayType() ) { @@ -1139,10 +1163,10 @@ private Object initializeLazyPropertiesFromDatastore( throw new AssertionFailure( "no lazy properties" ); } - final InterceptorImplementor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); assert interceptor != null : "Expecting bytecode interceptor to be non-null"; - LOG.trace( "Initializing lazy properties from datastore" ); + LOG.tracef( "Initializing lazy properties from datastore (triggered for `%s`)", fieldName ); final String fetchGroup = getEntityMetamodel().getBytecodeEnhancementMetadata() .getLazyAttributesMetadata() @@ -1172,7 +1196,6 @@ private Object initializeLazyPropertiesFromDatastore( rs = session.getJdbcCoordinator().getResultSetReturn().extract( ps ); rs.next(); } - final Object[] snapshot = entry.getLoadedState(); for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) { final boolean previousInitialized = initializedLazyAttributeNames.contains( fetchGroupAttributeDescriptor.getName() ); @@ -1203,7 +1226,7 @@ private Object initializeLazyPropertiesFromDatastore( fieldName, entity, session, - snapshot, + entry, fetchGroupAttributeDescriptor.getLazyIndex(), selectedValue ); @@ -1252,7 +1275,6 @@ private Object initializeLazyPropertiesFromCache( Object result = null; Serializable[] disassembledValues = cacheEntry.getDisassembledState(); - final Object[] snapshot = entry.getLoadedState(); for ( int j = 0; j < lazyPropertyNames.length; j++ ) { final Serializable cachedValue = disassembledValues[lazyPropertyNumbers[j]]; final Type lazyPropertyType = lazyPropertyTypes[j]; @@ -1269,7 +1291,7 @@ private Object initializeLazyPropertiesFromCache( session, entity ); - if ( initializeLazyProperty( fieldName, entity, session, snapshot, j, propValue ) ) { + if ( initializeLazyProperty( fieldName, entity, session, entry, j, propValue ) ) { result = propValue; } } @@ -1284,13 +1306,17 @@ private boolean initializeLazyProperty( final String fieldName, final Object entity, final SharedSessionContractImplementor session, - final Object[] snapshot, + final EntityEntry entry, final int j, final Object propValue) { setPropertyValue( entity, lazyPropertyNumbers[j], propValue ); - if ( snapshot != null ) { + if ( entry.getLoadedState() != null ) { // object have been loaded with setReadOnly(true); HHH-2236 - snapshot[lazyPropertyNumbers[j]] = lazyPropertyTypes[j].deepCopy( propValue, factory ); + entry.getLoadedState()[lazyPropertyNumbers[j]] = lazyPropertyTypes[j].deepCopy( propValue, factory ); + } + // If the entity has deleted state, then update that as well + if ( entry.getDeletedState() != null ) { + entry.getDeletedState()[lazyPropertyNumbers[j]] = lazyPropertyTypes[j].deepCopy( propValue, factory ); } return fieldName.equals( lazyPropertyNames[j] ); } @@ -1630,7 +1656,7 @@ protected String generateSelectVersionString() { SimpleSelect select = new SimpleSelect( getFactory().getDialect() ) .setTableName( getVersionedTableName() ); if ( isVersioned() ) { - select.addColumn( versionColumnName ); + select.addColumn( getVersionColumnName(), VERSION_COLUMN_ALIAS ); } else { select.addColumns( rootTableKeyColumnNames ); @@ -1862,7 +1888,7 @@ public Object getCurrentVersion(Serializable id, SharedSessionContractImplemento if ( !isVersioned() ) { return this; } - return getVersionType().nullSafeGet( rs, getVersionColumnName(), session, null ); + return getVersionType().nullSafeGet( rs, VERSION_COLUMN_ALIAS, session, null ); } finally { session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( rs, st ); @@ -2285,7 +2311,7 @@ protected void initSubclassPropertyAliasesMap(PersistentClass model) throws Mapp new String[] {idColumnNames[i]} ); } -// if (hasIdentifierProperty() && !ENTITY_ID.equals( getIdentifierPropertyName() ) ) { +// if (hasIdentifierProperty() && !ENTITY_ID.equals( getIdentifierPropertyNames() ) ) { if ( hasIdentifierProperty() ) { subclassPropertyAliases.put( getIdentifierPropertyName() + "." + idPropertyNames[i], @@ -3123,8 +3149,11 @@ protected void insert( // TODO : shouldn't inserts be Expectations.NONE? final Expectation expectation = Expectations.appropriateExpectation( insertResultCheckStyles[j] ); final int jdbcBatchSizeToUse = session.getConfiguredJdbcBatchSize(); - final boolean useBatch = expectation.canBeBatched() && jdbcBatchSizeToUse > 1; - if ( useBatch && inserBatchKey == null) { + final boolean useBatch = expectation.canBeBatched() && + jdbcBatchSizeToUse > 1 && + getIdentifierGenerator().supportsJdbcBatchInserts(); + + if ( useBatch && inserBatchKey == null ) { inserBatchKey = new BasicBatchKey( getEntityName() + "#INSERT", expectation @@ -3167,9 +3196,8 @@ protected void insert( .executeUpdate( insert ), insert, -1 ); } - } - catch (SQLException e) { + catch (SQLException | JDBCException e) { if ( useBatch ) { session.getJdbcCoordinator().abortBatch(); } @@ -4265,6 +4293,62 @@ public Object load(Serializable id, Object optionalObject, LockOptions lockOptio return loader.load( id, optionalObject, session, lockOptions ); } + @Override + public Object initializeEnhancedEntityUsedAsProxy( + Object entity, + String nameOfAttributeBeingAccessed, + SharedSessionContractImplementor session) { + final BytecodeEnhancementMetadata enhancementMetadata = getEntityMetamodel().getBytecodeEnhancementMetadata(); + final BytecodeLazyAttributeInterceptor currentInterceptor = enhancementMetadata.extractLazyInterceptor( entity ); + if ( currentInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) currentInterceptor; + + final EntityKey entityKey = proxyInterceptor.getEntityKey(); + final Serializable identifier = entityKey.getIdentifier(); + final Object loaded = readLockLoader.load( + identifier, + entity, + session, + LockOptions.READ + ); + + if ( loaded == null ) { + final PersistenceContext persistenceContext = session.getPersistenceContext(); + persistenceContext.removeEntry( entity ); + persistenceContext.removeEntity( entityKey ); + session.getFactory().getEntityNotFoundDelegate().handleEntityNotFound( + entityKey.getEntityName(), + identifier + ); + } + + final LazyAttributeLoadingInterceptor interceptor = enhancementMetadata.injectInterceptor( + entity, + identifier, + session + ); + + final Object value; + if ( nameOfAttributeBeingAccessed == null ) { + return null; + } + else if ( interceptor.isAttributeLoaded( nameOfAttributeBeingAccessed ) ) { + value = getEntityTuplizer().getPropertyValue( entity, nameOfAttributeBeingAccessed ); + } + else { + value = ( (LazyPropertyInitializer) this ).initializeLazyProperty( nameOfAttributeBeingAccessed, entity, session ); + } + + return interceptor.readObject( + entity, + nameOfAttributeBeingAccessed, + value + ); + } + + throw new IllegalStateException( ); + } + @Override public List multiLoad(Serializable[] ids, SharedSessionContractImplementor session, MultiLoadOptions loadOptions) { return DynamicBatchingEntityLoaderBuilder.INSTANCE.multiLoad( @@ -4574,9 +4658,14 @@ public boolean hasLazyProperties() { public void afterReassociate(Object entity, SharedSessionContractImplementor session) { if ( getEntityMetamodel().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { - LazyAttributeLoadingInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata().extractInterceptor( entity ); + final BytecodeLazyAttributeInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata() + .extractLazyInterceptor( entity ); if ( interceptor == null ) { - getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( entity, session ); + getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( + entity, + getIdentifier( entity, session ), + session + ); } else { interceptor.setSession( session ); @@ -4640,10 +4729,26 @@ public Boolean isTransient(Object entity, SharedSessionContractImplementor sessi if ( isVersioned() ) { // let this take precedence if defined, since it works for // assigned identifiers - Boolean result = entityMetamodel.getVersionProperty() + final Boolean isUnsaved = entityMetamodel.getVersionProperty() .getUnsavedValue().isUnsaved( version ); - if ( result != null ) { - return result; + if ( isUnsaved != null ) { + if ( isUnsaved ) { + final IdentifierGenerator identifierGenerator = getIdentifierGenerator(); + if ( identifierGenerator != null && !( identifierGenerator instanceof ForeignGenerator ) + && !( identifierGenerator instanceof Assigned ) ) { + final Boolean unsaved = entityMetamodel.getIdentifierProperty() + .getUnsavedValue().isUnsaved( id ); + if ( unsaved != null && !unsaved ) { + throw new PropertyValueException( + "Detached entity with generated id '" + id + "' has an uninitialized version value '" + version + "'", + getEntityName(), + getVersionColumnName() + ); + } + } + } + + return isUnsaved; } } @@ -5445,6 +5550,11 @@ public EntityTuplizer getEntityTuplizer() { @Override public BytecodeEnhancementMetadata getInstrumentationMetadata() { + return getBytecodeEnhancementMetadata(); + } + + @Override + public BytecodeEnhancementMetadata getBytecodeEnhancementMetadata() { return entityMetamodel.getBytecodeEnhancementMetadata(); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java index 59c9f3cbf2b88b0b3062efa911587c96ea960e58..f7dbfd1696bd50f09decd5ee81e3dff949949865 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java @@ -386,7 +386,7 @@ protected void initIdentifierPropertyPaths( } } - if ( idPropName != null ) { + if ( (! etype.isNullable() ) && idPropName != null ) { String idpath2 = extendPath( path, idPropName ); addPropertyPath( idpath2, idtype, columns, columnReaders, columnReaderTemplates, null, factory ); initPropertyPaths( idpath2, idtype, columns, columnReaders, columnReaderTemplates, null, factory ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorType.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorType.java index d3467089361493e7c30b513266d14068f387d63e..2bb0367175c4ac64e8cb865baf6f2a10b77126cd 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorType.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/DiscriminatorType.java @@ -37,18 +37,22 @@ public DiscriminatorType(Type underlyingType, Loadable persister) { this.persister = persister; } + @Override public Class getReturnedClass() { return Class.class; } + @Override public String getName() { return getClass().getName(); } + @Override public boolean isMutable() { return false; } + @Override public Object nullSafeGet( ResultSet rs, String[] names, @@ -57,6 +61,7 @@ public Object nullSafeGet( return nullSafeGet( rs, names[0], session, owner ); } + @Override public Object nullSafeGet( ResultSet rs, String name, @@ -71,6 +76,7 @@ public Object nullSafeGet( return ( EntityMode.POJO == entityPersister.getEntityMode() ) ? entityPersister.getMappedClass() : entityName; } + @Override public void nullSafeSet( PreparedStatement st, Object value, @@ -80,6 +86,7 @@ public void nullSafeSet( nullSafeSet( st, value, index, session ); } + @Override public void nullSafeSet( PreparedStatement st, Object value, @@ -90,26 +97,31 @@ public void nullSafeSet( underlyingType.nullSafeSet(st, entityPersister.getDiscriminatorValue(), index, session); } + @Override public String toLoggableString(Object value, SessionFactoryImplementor factory) throws HibernateException { return value == null ? "[null]" : value.toString(); } + @Override public Object deepCopy(Object value, SessionFactoryImplementor factory) throws HibernateException { return value; } + @Override public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache) throws HibernateException { return original; } + @Override public boolean[] toColumnNullness(Object value, Mapping mapping) { return value == null ? ArrayHelper.FALSE : ArrayHelper.TRUE; } + @Override public boolean isDirty(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session) throws HibernateException { return Objects.equals( old, current ); @@ -118,6 +130,7 @@ public boolean isDirty(Object old, Object current, boolean[] checkable, SharedSe // simple delegation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + @Override public int[] sqlTypes(Mapping mapping) throws MappingException { return underlyingType.sqlTypes( mapping ); } @@ -132,6 +145,7 @@ public Size[] defaultSizes(Mapping mapping) throws MappingException { return underlyingType.defaultSizes( mapping ); } + @Override public int getColumnSpan(Mapping mapping) throws MappingException { return underlyingType.getColumnSpan( mapping ); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index eb47a19382cd80352e93fae6b1586f45f4d99e7d..dac30cd60e578f0d44eca2c2482f5c434280b794 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -15,7 +15,9 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.MappingException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; +import org.hibernate.bytecode.spi.NotInstrumentedException; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.cache.spi.entry.CacheEntry; @@ -132,6 +134,20 @@ public interface EntityPersister extends EntityDefinition { */ EntityMetamodel getEntityMetamodel(); + /** + * Called from {@link EnhancementAsProxyLazinessInterceptor} to trigger load of + * the entity's non-lazy state as well as the named attribute we are accessing + * if it is still uninitialized after fetching non-lazy state + */ + default Object initializeEnhancedEntityUsedAsProxy( + Object entity, + String nameOfAttributeBeingAccessed, + SharedSessionContractImplementor session) { + throw new UnsupportedOperationException( + "Initialization of entity enhancement used to act like a proxy is not supported by this EntityPersister : " + getClass().getName() + ); + } + /** * Determine whether the given name represents a subclass entity * (or this entity itself) of the entity mapped by this persister. @@ -798,7 +814,11 @@ Object createProxy(Serializable id, SharedSessionContractImplementor session) EntityTuplizer getEntityTuplizer(); BytecodeEnhancementMetadata getInstrumentationMetadata(); - + + default BytecodeEnhancementMetadata getBytecodeEnhancementMetadata() { + return getInstrumentationMetadata(); + } + FilterAliasGenerator getFilterAliasGenerator(final String rootAlias); /** diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java index b3f6c3eb542aa94f0934571d861f76bdcf8c0cd4..a936c776f71e6b06c5aa9fca297ea0925dcd613f 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java @@ -243,12 +243,7 @@ else if ( persistentClass.isDiscriminatorValueNotNull() ) { while ( joinItr.hasNext() ) { Join join = (Join) joinItr.next(); - isNullableTable[tableIndex++] = join.isOptional() || - creationContext.getSessionFactory() - .getSessionFactoryOptions() - .getJpaCompliance() - .isJpaCacheComplianceEnabled(); - + isNullableTable[tableIndex] = join.isOptional(); Table table = join.getTable(); final String tableName = determineTableName( table, jdbcEnvironment ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java index 1afbb6b852c527f748fdfad01390af79fd8a8634..a5c574e4530ec85a52ff4eb259910b7f2e5dd09a 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java @@ -178,11 +178,7 @@ public SingleTableEntityPersister( Join join = (Join) joinIter.next(); qualifiedTableNames[j] = determineTableName( join.getTable(), jdbcEnvironment ); isInverseTable[j] = join.isInverse(); - isNullableTable[j] = join.isOptional() - || creationContext.getSessionFactory() - .getSessionFactoryOptions() - .getJpaCompliance() - .isJpaCacheComplianceEnabled(); + isNullableTable[j] = join.isOptional(); cascadeDeleteEnabled[j] = join.getKey().isCascadeDeleteEnabled() && factory.getDialect().supportsCascadeDelete(); @@ -248,12 +244,7 @@ public SingleTableEntityPersister( isConcretes.add( persistentClass.isClassOrSuperclassJoin( join ) ); isDeferreds.add( join.isSequentialSelect() ); isInverses.add( join.isInverse() ); - isNullables.add( - join.isOptional() || creationContext.getSessionFactory() - .getSessionFactoryOptions() - .getJpaCompliance() - .isJpaCacheComplianceEnabled() - ); + isNullables.add( join.isOptional() ); isLazies.add( lazyAvailable && join.isLazy() ); if ( join.isSequentialSelect() && !persistentClass.isClassOrSuperclassJoin( join ) ) { hasDeferred = true; diff --git a/hibernate-core/src/main/java/org/hibernate/persister/spi/PersisterClassResolver.java b/hibernate-core/src/main/java/org/hibernate/persister/spi/PersisterClassResolver.java index 42dbd34d4d2c35b9124681c28fe0eb3458f31b45..f636df293b09b47acf0601bf25a7ab54d4a163d7 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/spi/PersisterClassResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/spi/PersisterClassResolver.java @@ -21,7 +21,7 @@ *
      • the default provider as chosen by Hibernate Core (best choice most of the time)
      • * * - * @author Emmanuel Bernard + * @author Emmanuel Bernard * @author Steve Ebersole */ public interface PersisterClassResolver extends Service { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java index afbe99fad86fd31b39bef9081a261a71c1d12c82..70d46f62ab054d5596df61b15dc0a88f337e42d1 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java @@ -58,7 +58,7 @@ public static FetchStyle determineFetchStyleByProfile( final String relativePropertyPath = pos >= 0 ? fullPath.substring( pos ) : rootPropertyName; - final String fetchRole = persister.getEntityName() + "." + relativePropertyPath; + final String fetchRole = persister.getEntityName() + '.' + relativePropertyPath; for ( String profileName : loadQueryInfluencers.getEnabledFetchProfileNames() ) { final FetchProfile profile = loadQueryInfluencers.getSessionFactory().getFetchProfile( profileName ); diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java index 20fb09921604b735a4b46032df85fefa171db90d..b36f656052f375813341e15744d436913e9259a4 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java @@ -25,6 +25,7 @@ public interface ParameterRegistration extends ProcedureParameter { * * @return The name; */ + @Override String getName(); /** @@ -33,14 +34,27 @@ public interface ParameterRegistration extends ProcedureParameter { * * @return The name; */ + @Override Integer getPosition(); + /** + * Return the Java type of the parameter. + * + * @return The Java type of the parameter. + * @deprecated Call {@link #getParameterType()} instead. + */ + @Deprecated + default Class getType() { + return getParameterType(); + } + /** * Retrieves the parameter "mode" which describes how the parameter is defined in the actual database procedure * definition (is it an INPUT parameter? An OUTPUT parameter? etc). * * @return The parameter mode. */ + @Override ParameterMode getMode(); /** @@ -59,6 +73,7 @@ public interface ParameterRegistration extends ProcedureParameter { * * @param enabled {@code true} indicates that the NULL should be passed; {@code false} indicates it should not. */ + @Override void enablePassingNulls(boolean enabled); /** diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java index 4d9dd0b8600f1c9fedd72befc29a7bda66fd92b4..e5536efd32153c2e0afc1a2b5cfa8bf8d1bb6a8b 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java @@ -79,8 +79,19 @@ private void internalSetValue(T value) { } if ( procedureParameter.getParameterType() != null ) { - if ( !procedureParameter.getParameterType().isInstance( value ) && !procedureParameter.getHibernateType().getReturnedClass().isInstance( value ) ) { - throw new IllegalArgumentException( "Bind value [" + value + "] was not of specified type [" + procedureParameter.getParameterType() ); + if ( value == null ) { + if ( !procedureParameter.isPassNullsEnabled() ) { + throw new IllegalArgumentException( "The parameter " + + ( procedureParameter.getName() != null + ? "named [" + procedureParameter.getName() + "]" + : "at position [" + procedureParameter.getPosition() + "]" ) + + " was null. You need to call ParameterRegistration#enablePassingNulls(true) in order to pass null parameters." ); + } + } + else if ( !procedureParameter.getParameterType().isInstance( value ) && + !procedureParameter.getHibernateType().getReturnedClass().isInstance( value ) ) { + throw new IllegalArgumentException( "Bind value [" + value + "] was not of specified type [" + procedureParameter + .getParameterType() ); } } 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 e4cc8971ae363e2e6c8c6de36b2658b8e72311c0..f80cb13dfa9faae0cbcba648c668ae137f1f1a1b 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 @@ -25,7 +25,6 @@ import javax.persistence.Parameter; import javax.persistence.ParameterMode; import javax.persistence.TemporalType; -import javax.persistence.TransactionRequiredException; import org.hibernate.HibernateException; import org.hibernate.engine.ResultSetMappingDefinition; @@ -636,9 +635,8 @@ protected ProcedureOutputs outputs() { @Override public int executeUpdate() { - if ( ! getProducer().isTransactionInProgress() ) { - throw new TransactionRequiredException( "javax.persistence.Query.executeUpdate requires active transaction" ); - } + getProducer().checkTransactionNeededForUpdateOperation( + "javax.persistence.Query.executeUpdate requires active transaction" ); // the expectation is that there is just one Output, of type UpdateCountOutput try { diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java index 805662fe1da694edc1dd55be8f2c56d96bcd7c5e..130cde2d20929c142f0972bbac0ffe48ca745f8c 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java @@ -34,6 +34,7 @@ public interface ParameterRegistrationImplementor extends ParameterRegistrati * * @return The Hibernate Type */ + @Override Type getHibernateType(); /** @@ -46,6 +47,7 @@ public interface ParameterRegistrationImplementor extends ParameterRegistrati * that the parameter will simply be ignored, with the assumption that the corresponding argument * defined a default value. */ + @Override boolean isPassNullsEnabled(); /** diff --git a/hibernate-core/src/main/java/org/hibernate/property/access/spi/EnhancedSetterImpl.java b/hibernate-core/src/main/java/org/hibernate/property/access/spi/EnhancedSetterImpl.java index 89e90c9d0347be87977092cf521fe3d99c2362d4..7cdf6581dcda34f075cfe04b7dd7df38ffc82d35 100644 --- a/hibernate-core/src/main/java/org/hibernate/property/access/spi/EnhancedSetterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/property/access/spi/EnhancedSetterImpl.java @@ -6,14 +6,16 @@ */ package org.hibernate.property.access.spi; -import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; +import java.io.Serializable; +import java.lang.reflect.Field; + +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.engine.spi.CompositeOwner; import org.hibernate.engine.spi.CompositeTracker; import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SessionFactoryImplementor; - -import java.lang.reflect.Field; +import org.hibernate.property.access.internal.AbstractFieldSerialForm; /** * A specialized Setter implementation for handling setting values into @@ -25,9 +27,9 @@ * @author Luis Barreiro */ public class EnhancedSetterImpl extends SetterFieldImpl { - private final String propertyName; + @SuppressWarnings("rawtypes") public EnhancedSetterImpl(Class containerClass, String propertyName, Field field) { super( containerClass, propertyName, field ); this.propertyName = propertyName; @@ -46,9 +48,34 @@ public void set(Object target, Object value, SessionFactoryImplementor factory) // This marks the attribute as initialized, so it doesn't get lazy loaded afterwards if ( target instanceof PersistentAttributeInterceptable ) { PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) target ).$$_hibernate_getInterceptor(); - if ( interceptor != null && interceptor instanceof LazyAttributeLoadingInterceptor ) { - interceptor.attributeInitialized( propertyName ); + if ( interceptor instanceof BytecodeLazyAttributeInterceptor ) { + ( (BytecodeLazyAttributeInterceptor) interceptor ).attributeInitialized( propertyName ); } } } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // serialization + + private Object writeReplace() { + return new SerialForm( getContainerClass(), propertyName, getField() ); + } + + @SuppressWarnings("rawtypes") + private static class SerialForm extends AbstractFieldSerialForm implements Serializable { + private final Class containerClass; + private final String propertyName; + + + private SerialForm(Class containerClass, String propertyName, Field field) { + super( field ); + this.containerClass = containerClass; + this.propertyName = propertyName; + } + + private Object readResolve() { + return new EnhancedSetterImpl( containerClass, propertyName, resolveField() ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/property/access/spi/SetterFieldImpl.java b/hibernate-core/src/main/java/org/hibernate/property/access/spi/SetterFieldImpl.java index 58c7d9e472673ba09d2390a9519cd6ce489bd865..cc9c19f0f568825388408cb723bcb81b3d22c6e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/property/access/spi/SetterFieldImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/property/access/spi/SetterFieldImpl.java @@ -6,7 +6,6 @@ */ package org.hibernate.property.access.spi; -import java.io.ObjectStreamException; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -35,6 +34,18 @@ public SetterFieldImpl(Class containerClass, String propertyName, Field field) { this.setterMethod = ReflectHelper.setterMethodOrNull( containerClass, propertyName, field.getType() ); } + public Class getContainerClass() { + return containerClass; + } + + public String getPropertyName() { + return propertyName; + } + + protected Field getField() { + return field; + } + @Override public void set(Object target, Object value, SessionFactoryImplementor factory) { try { @@ -83,7 +94,7 @@ public Method getMethod() { return setterMethod; } - private Object writeReplace() throws ObjectStreamException { + private Object writeReplace() { return new SerialForm( containerClass, propertyName, field ); } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java index 6a3db7de0a3996382f221ace38bf9910fd258ddb..213b1978c7712540ca94acc0819e5e68eafba004 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java @@ -13,7 +13,9 @@ import org.hibernate.LazyInitializationException; import org.hibernate.SessionException; import org.hibernate.TransientObjectException; +import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreLogging; @@ -44,8 +46,17 @@ public abstract class AbstractLazyInitializer implements LazyInitializer { private boolean allowLoadOutsideTransaction; /** - * For serialization from the non-pojo initializers (HHH-3309) + * @deprecated This constructor was initially intended for serialization only, and is not useful anymore. + * In any case it should not be relied on by user code. + * Subclasses should rather implement Serializable with an {@code Object writeReplace()} method returning + * a subclass of {@link AbstractSerializableProxy}, + * which in turn implements Serializable and an {@code Object readResolve()} method + * instantiating the {@link AbstractLazyInitializer} subclass + * and calling {@link AbstractSerializableProxy#afterDeserialization(AbstractLazyInitializer)} on it. + * See {@link org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor} and + * {@link org.hibernate.proxy.pojo.bytebuddy.SerializableProxy} for examples. */ + @Deprecated protected AbstractLazyInitializer() { } @@ -73,6 +84,11 @@ public final String getEntityName() { return entityName; } + @Override + public final Serializable getInternalIdentifier() { + return id; + } + @Override public final Serializable getIdentifier() { if ( isUninitialized() && isInitializeProxyWhenAccessingIdentifier() ) { @@ -82,7 +98,7 @@ public final Serializable getIdentifier() { } private boolean isInitializeProxyWhenAccessingIdentifier() { - return session != null && session.getFactory() + return getSession() != null && getSession().getFactory() .getSessionFactoryOptions() .getJpaCompliance().isJpaProxyComplianceEnabled(); } @@ -232,6 +248,33 @@ else if ( session.isOpen() && session.isConnected() ) { } } + /** + * Attempt to initialize the proxy without loading anything from the database. + * + * This will only have any effect if the proxy is still attached to a session, + * and the entity being proxied has been loaded and added to the persistence context + * of that session since the proxy was created. + */ + public final void initializeWithoutLoadIfPossible() { + if ( !initialized && session != null && session.isOpen() ) { + final EntityKey key = session.generateEntityKey( + getInternalIdentifier(), + session.getFactory().getMetamodel().entityPersister( getEntityName() ) + ); + final Object entity = session.getPersistenceContext().getEntity( key ); + if ( entity != null ) { + setImplementation( entity ); + } + } + } + + /** + * Initialize internal state based on the currently attached session, + * in order to be ready to load data even after the proxy is detached from the session. + * + * This method only has any effect if + * {@link SessionFactoryOptions#isInitializeLazyStateOutsideTransactionsEnabled()} is {@code true}. + */ protected void prepareForPossibleLoadingOutsideTransaction() { if ( session != null ) { allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled(); @@ -260,7 +303,7 @@ protected final boolean isConnectedToSession() { } private Object getProxyOrNull() { - final EntityKey entityKey = generateEntityKeyOrNull( getIdentifier(), session, getEntityName() ); + final EntityKey entityKey = generateEntityKeyOrNull( getInternalIdentifier(), session, getEntityName() ); if ( entityKey != null && session != null && session.isOpen() ) { return session.getPersistenceContext().getProxy( entityKey ); } @@ -281,8 +324,8 @@ public final void setImplementation(Object target) { @Override public final Object getImplementation(SharedSessionContractImplementor s) throws HibernateException { - final EntityKey entityKey = generateEntityKeyOrNull( getIdentifier(), s, getEntityName() ); - return (entityKey == null ? null : s.getPersistenceContext().getEntity( entityKey )); + final EntityKey entityKey = generateEntityKeyOrNull( getInternalIdentifier(), s, getEntityName() ); + return ( entityKey == null ? null : s.getPersistenceContext().getEntity( entityKey ) ); } /** @@ -331,9 +374,10 @@ public final void setReadOnly(boolean readOnly) { } this.readOnly = readOnly; if ( initialized ) { - EntityKey key = generateEntityKeyOrNull( getIdentifier(), session, getEntityName() ); - if ( key != null && session.getPersistenceContext().containsEntity( key ) ) { - session.getPersistenceContext().setReadOnly( target, readOnly ); + EntityKey key = generateEntityKeyOrNull( getInternalIdentifier(), session, getEntityName() ); + final PersistenceContext persistenceContext = session.getPersistenceContext(); + if ( key != null && persistenceContext.containsEntity( key ) ) { + persistenceContext.setReadOnly( target, readOnly ); } } } @@ -352,7 +396,7 @@ public final void setReadOnly(boolean readOnly) { * * @throws IllegalStateException if isReadOnlySettingAvailable() == true */ - protected final Boolean isReadOnlyBeforeAttachedToSession() { + public final Boolean isReadOnlyBeforeAttachedToSession() { if ( isReadOnlySettingAvailable() ) { throw new IllegalStateException( "Cannot call isReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]" @@ -362,25 +406,57 @@ protected final Boolean isReadOnlyBeforeAttachedToSession() { } /** - * Set the read-only/modifiable setting that should be put in affect when it is - * attached to a session. - *

        + * Get whether the proxy can load data even + * if it's not attached to a session with an ongoing transaction. + * + * This method should only be called during serialization, + * and only makes sense after a call to {@link #prepareForPossibleLoadingOutsideTransaction()}. + * + * @return {@code true} if out-of-transaction loads are allowed, {@code false} otherwise. + */ + protected boolean isAllowLoadOutsideTransaction() { + return allowLoadOutsideTransaction; + } + + /** + * Get the session factory UUID. + * + * This method should only be called during serialization, + * and only makes sense after a call to {@link #prepareForPossibleLoadingOutsideTransaction()}. + * + * @return the session factory UUID. + */ + protected String getSessionFactoryUuid() { + return sessionFactoryUuid; + } + + /** + * Restore settings that are not passed to the constructor, + * but are still preserved during serialization. + * * This method should only be called during deserialization, before associating * the proxy with a session. * - * @param readOnlyBeforeAttachedToSession, the read-only/modifiable setting to use when + * @param readOnlyBeforeAttachedToSession the read-only/modifiable setting to use when * associated with a session; null indicates that the default should be used. + * @param sessionFactoryUuid the session factory uuid, to be used if {@code allowLoadOutsideTransaction} is {@code true}. + * @param allowLoadOutsideTransaction whether the proxy can load data even + * if it's not attached to a session with an ongoing transaction. * * @throws IllegalStateException if isReadOnlySettingAvailable() == true */ /* package-private */ - final void setReadOnlyBeforeAttachedToSession(Boolean readOnlyBeforeAttachedToSession) { + final void afterDeserialization(Boolean readOnlyBeforeAttachedToSession, + String sessionFactoryUuid, boolean allowLoadOutsideTransaction) { if ( isReadOnlySettingAvailable() ) { throw new IllegalStateException( - "Cannot call setReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]" + "Cannot call afterDeserialization when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]" ); } this.readOnlyBeforeAttachedToSession = readOnlyBeforeAttachedToSession; + + this.sessionFactoryUuid = sessionFactoryUuid; + this.allowLoadOutsideTransaction = allowLoadOutsideTransaction; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java index be63fd87c424d3c466cd7f4542188cb95137a6c2..95f0a6dfff3a256a6f708696832f1508070d1c74 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java @@ -5,10 +5,11 @@ * See the lgpl.txt file in the root directory or . */ package org.hibernate.proxy; + import java.io.Serializable; /** - * Convenience base class for SerializableProxy. + * Convenience base class for the serialized form of {@link AbstractLazyInitializer}. * * @author Gail Badner */ @@ -16,17 +17,32 @@ public abstract class AbstractSerializableProxy implements Serializable { private String entityName; private Serializable id; private Boolean readOnly; + private String sessionFactoryUuid; + private boolean allowLoadOutsideTransaction; /** - * For serialization + * @deprecated This constructor was initially intended for serialization only, and is not useful anymore. + * In any case it should not be relied on by user code. */ + @Deprecated protected AbstractSerializableProxy() { } + /** + * @deprecated use {@link #AbstractSerializableProxy(String, Serializable, Boolean, String, boolean)} instead. + */ + @Deprecated protected AbstractSerializableProxy(String entityName, Serializable id, Boolean readOnly) { + this( entityName, id, readOnly, null, false ); + } + + protected AbstractSerializableProxy(String entityName, Serializable id, Boolean readOnly, + String sessionFactoryUuid, boolean allowLoadOutsideTransaction) { this.entityName = entityName; this.id = id; this.readOnly = readOnly; + this.sessionFactoryUuid = sessionFactoryUuid; + this.allowLoadOutsideTransaction = allowLoadOutsideTransaction; } protected String getEntityName() { @@ -46,8 +62,23 @@ protected Serializable getId() { * @param li the read-only/modifiable setting to use when * associated with a session; null indicates that the default should be used. * @throws IllegalStateException if isReadOnlySettingAvailable() == true + * + * @deprecated Use {@link #afterDeserialization(AbstractLazyInitializer)} instead. */ + @Deprecated protected void setReadOnlyBeforeAttachedToSession(AbstractLazyInitializer li) { - li.setReadOnlyBeforeAttachedToSession( readOnly ); + li.afterDeserialization( readOnly, null, false ); + } + + /** + * Initialize an {@link AbstractLazyInitializer} after deserialization. + * + * This method should only be called during deserialization, + * before associating the AbstractLazyInitializer with a session. + * + * @param li the {@link AbstractLazyInitializer} to initialize. + */ + protected void afterDeserialization(AbstractLazyInitializer li) { + li.afterDeserialization( readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/LazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/LazyInitializer.java index 023bf4592ce6dfc932e2bb1f005f23591d10c120..01a961d2abe95d7e00f49eefc1e16004f5aff049 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/LazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/LazyInitializer.java @@ -30,6 +30,17 @@ public interface LazyInitializer { * * @return The identifier value. */ + default Serializable getInternalIdentifier() { + return getIdentifier(); + } + + /** + * Retrieve the identifier value for the entity our owning proxy represents. + * + * When JPA proxy compliance is enabled the proxy is initialized. + * + * @return The identifier value. + */ Serializable getIdentifier(); /** diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java index 7561579352f47f29ad06db0345ea3149831da911..6c08dd18d0131a527afe94ec6d4fae835bdff62e 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java @@ -31,4 +31,20 @@ public Class getPersistentClass() { throw new UnsupportedOperationException("dynamic-map entity representation"); } + // Expose the following methods to MapProxy by overriding them (so that classes in this package see them) + + @Override + protected void prepareForPossibleLoadingOutsideTransaction() { + super.prepareForPossibleLoadingOutsideTransaction(); + } + + @Override + protected boolean isAllowLoadOutsideTransaction() { + return super.isAllowLoadOutsideTransaction(); + } + + @Override + protected String getSessionFactoryUuid() { + return super.getSessionFactoryUuid(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java index 7585b2b4472303689f44707eede5012dca2d5ae7..448f5c9b2ae3b7d9cd6cf2c0aa467c0879d1c0dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java @@ -22,64 +22,105 @@ public class MapProxy implements HibernateProxy, Map, Serializable { private MapLazyInitializer li; + private Object replacement; + MapProxy(MapLazyInitializer li) { this.li = li; } - public Object writeReplace() { - return this; - } - + @Override public LazyInitializer getHibernateLazyInitializer() { return li; } + @Override public int size() { return li.getMap().size(); } + @Override public void clear() { li.getMap().clear(); } + @Override public boolean isEmpty() { return li.getMap().isEmpty(); } + @Override public boolean containsKey(Object key) { return li.getMap().containsKey(key); } + @Override public boolean containsValue(Object value) { return li.getMap().containsValue(value); } + @Override public Collection values() { return li.getMap().values(); } + @Override public void putAll(Map t) { li.getMap().putAll(t); } + @Override public Set entrySet() { return li.getMap().entrySet(); } + @Override public Set keySet() { return li.getMap().keySet(); } + @Override public Object get(Object key) { return li.getMap().get(key); } + @Override public Object remove(Object key) { return li.getMap().remove(key); } + @Override public Object put(Object key, Object value) { return li.getMap().put(key, value); } + @Override + public Object writeReplace() { + /* + * If the target has already been loaded somewhere, just not set on the proxy, + * then use it to initialize the proxy so that we will serialize that instead of the proxy. + */ + li.initializeWithoutLoadIfPossible(); + + if ( li.isUninitialized() ) { + if ( replacement == null ) { + li.prepareForPossibleLoadingOutsideTransaction(); + replacement = serializableProxy(); + } + return replacement; + } + else { + return li.getImplementation(); + } + } + + private Object serializableProxy() { + return new SerializableMapProxy( + li.getEntityName(), + li.getInternalIdentifier(), + ( li.isReadOnlySettingAvailable() ? Boolean.valueOf( li.isReadOnly() ) : li.isReadOnlyBeforeAttachedToSession() ), + li.getSessionFactoryUuid(), + li.isAllowLoadOutsideTransaction() + ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java new file mode 100644 index 0000000000000000000000000000000000000000..189ae366eb325ddf0ea8005378efb85904aa5a48 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.proxy.map; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.hibernate.proxy.AbstractSerializableProxy; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyFactory; +import org.hibernate.type.CompositeType; + +public final class SerializableMapProxy extends AbstractSerializableProxy { + + public SerializableMapProxy( + String entityName, + Serializable id, + Boolean readOnly, + String sessionFactoryUuid, + boolean allowLoadOutsideTransaction) { + super( entityName, id, readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); + } + + private Object readResolve() { + MapLazyInitializer initializer = new MapLazyInitializer( getEntityName(), getId(), null ); + afterDeserialization( initializer ); + return new MapProxy( initializer ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java index 46eae5ff1cf59635132a11a88d02c21196d6fc2c..a8059fb2d7caccd6ed3dffc7d1bf5ec0ff43c8c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/BasicLazyInitializer.java @@ -9,7 +9,6 @@ import java.io.Serializable; import java.lang.reflect.Method; -import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.MarkerObject; import org.hibernate.proxy.AbstractLazyInitializer; @@ -91,20 +90,15 @@ else if ( method.equals( setIdentifierMethod ) ) { } private Object getReplacement() { - final SharedSessionContractImplementor session = getSession(); - if ( isUninitialized() && session != null && session.isOpen() ) { - final EntityKey key = session.generateEntityKey( - getIdentifier(), - session.getFactory().getMetamodel().entityPersister( getEntityName() ) - ); - final Object entity = session.getPersistenceContext().getEntity( key ); - if ( entity != null ) { - setImplementation( entity ); - } - } + /* + * If the target has already been loaded somewhere, just not set on the proxy, + * then use it to initialize the proxy so that we will serialize that instead of the proxy. + */ + initializeWithoutLoadIfPossible(); if ( isUninitialized() ) { if ( replacement == null ) { + prepareForPossibleLoadingOutsideTransaction(); replacement = serializableProxy(); } return replacement; diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/ProxyFactoryHelper.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/ProxyFactoryHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..4f64fc6bb912c397d2339c956ef64e5dd2ed54b6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/ProxyFactoryHelper.java @@ -0,0 +1,123 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.proxy.pojo; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Iterator; +import java.util.Set; + +import org.hibernate.HibernateException; +import org.hibernate.MappingException; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.Subclass; +import org.hibernate.property.access.spi.Getter; +import org.hibernate.property.access.spi.Setter; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.tuple.entity.PojoEntityTuplizer; + +/** + * Most of this code was originally an internal detail of {@link PojoEntityTuplizer}, + * then extracted to make it easier for integrators to initialize a custom + * {@link org.hibernate.proxy.ProxyFactory}. + */ +public final class ProxyFactoryHelper { + + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( ProxyFactoryHelper.class ); + + private ProxyFactoryHelper() { + //not meant to be instantiated + } + + public static Set extractProxyInterfaces(final PersistentClass persistentClass, final String entityName) { + /* + * We need to preserve the order of the interfaces they were put into the set, since javassist will choose the + * first one's class-loader to construct the proxy class with. This is also the reason why HibernateProxy.class + * should be the last one in the order (on JBossAS7 its class-loader will be org.hibernate module's class- + * loader, which will not see the classes inside deployed apps. See HHH-3078 + */ + final Set proxyInterfaces = new java.util.LinkedHashSet(); + final Class mappedClass = persistentClass.getMappedClass(); + final Class proxyInterface = persistentClass.getProxyInterface(); + + if ( proxyInterface != null && !mappedClass.equals( proxyInterface ) ) { + if ( !proxyInterface.isInterface() ) { + throw new MappingException( + "proxy must be either an interface, or the class itself: " + entityName + ); + } + proxyInterfaces.add( proxyInterface ); + } + + if ( mappedClass.isInterface() ) { + proxyInterfaces.add( mappedClass ); + } + + Iterator subclasses = persistentClass.getSubclassIterator(); + while ( subclasses.hasNext() ) { + final Subclass subclass = subclasses.next(); + final Class subclassProxy = subclass.getProxyInterface(); + final Class subclassClass = subclass.getMappedClass(); + if ( subclassProxy != null && !subclassClass.equals( subclassProxy ) ) { + if ( !subclassProxy.isInterface() ) { + throw new MappingException( + "proxy must be either an interface, or the class itself: " + subclass.getEntityName() + ); + } + proxyInterfaces.add( subclassProxy ); + } + } + + proxyInterfaces.add( HibernateProxy.class ); + return proxyInterfaces; + } + + public static void validateProxyability(final PersistentClass persistentClass) { + Iterator properties = persistentClass.getPropertyIterator(); + Class clazz = persistentClass.getMappedClass(); + while ( properties.hasNext() ) { + Property property = (Property) properties.next(); + validateGetterSetterMethodProxyability( "Getter", property.getGetter( clazz ).getMethod() ); + validateGetterSetterMethodProxyability( "Setter", property.getSetter( clazz ).getMethod() ); + } + } + + public static void validateGetterSetterMethodProxyability(String getterOrSetter, Method method ) { + if ( method != null && Modifier.isFinal( method.getModifiers() ) ) { + throw new HibernateException( + String.format( + "%s methods of lazy classes cannot be final: %s#%s", + getterOrSetter, + method.getDeclaringClass().getName(), + method.getName() + ) + ); + } + } + + public static Method extractProxySetIdentifierMethod(final Setter idSetter, final Class proxyInterface) { + Method idSetterMethod = idSetter == null ? null : idSetter.getMethod(); + + Method proxySetIdentifierMethod = idSetterMethod == null || proxyInterface == null ? + null : + ReflectHelper.getMethod( proxyInterface, idSetterMethod ); + return proxySetIdentifierMethod; + } + + public static Method extractProxyGetIdentifierMethod(final Getter idGetter, final Class proxyInterface) { + Method idGetterMethod = idGetter == null ? null : idGetter.getMethod(); + + Method proxyGetIdentifierMethod = idGetterMethod == null || proxyInterface == null ? + null : + ReflectHelper.getMethod( proxyInterface, idGetterMethod ); + return proxyGetIdentifierMethod; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java index 915ae46fff964eb9e4495fbc4817c811f99a9676..21a273327e7fa2529e5012bc6611e11ee2044da0 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java @@ -17,13 +17,6 @@ import org.hibernate.proxy.pojo.BasicLazyInitializer; import org.hibernate.type.CompositeType; -import net.bytebuddy.implementation.bind.annotation.AllArguments; -import net.bytebuddy.implementation.bind.annotation.FieldValue; -import net.bytebuddy.implementation.bind.annotation.Origin; -import net.bytebuddy.implementation.bind.annotation.RuntimeType; -import net.bytebuddy.implementation.bind.annotation.StubValue; -import net.bytebuddy.implementation.bind.annotation.This; - import static org.hibernate.internal.CoreLogging.messageLogger; public class ByteBuddyInterceptor extends BasicLazyInitializer implements ProxyConfiguration.Interceptor { @@ -92,8 +85,10 @@ protected Object serializableProxy() { getEntityName(), persistentClass, interfaces, - getIdentifier(), + getInternalIdentifier(), ( isReadOnlySettingAvailable() ? Boolean.valueOf( isReadOnly() ) : isReadOnlyBeforeAttachedToSession() ), + getSessionFactoryUuid(), + isAllowLoadOutsideTransaction(), getIdentifierMethod, setIdentifierMethod, componentIdType diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java index 0f8d0cd753ec3daad0858c3d188a8026a2663075..d67b9720c67129332ad9f99390eeaae1c4b41cb1 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java @@ -6,16 +6,13 @@ */ package org.hibernate.proxy.pojo.bytebuddy; +import static org.hibernate.internal.CoreLogging.messageLogger; + import java.io.Serializable; import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Locale; import java.util.Set; import org.hibernate.HibernateException; -import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; import org.hibernate.cfg.Environment; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreMessageLogger; @@ -26,28 +23,11 @@ import org.hibernate.proxy.ProxyFactory; import org.hibernate.type.CompositeType; -import net.bytebuddy.NamingStrategy; -import net.bytebuddy.TypeCache; -import net.bytebuddy.description.modifier.Visibility; -import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; -import net.bytebuddy.implementation.FieldAccessor; -import net.bytebuddy.implementation.MethodDelegation; -import net.bytebuddy.implementation.SuperMethodCall; -import net.bytebuddy.implementation.bytecode.assign.Assigner; - -import static net.bytebuddy.matcher.ElementMatchers.isFinalizer; -import static net.bytebuddy.matcher.ElementMatchers.isSynthetic; -import static net.bytebuddy.matcher.ElementMatchers.isVirtual; -import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; -import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.not; -import static net.bytebuddy.matcher.ElementMatchers.returns; -import static org.hibernate.internal.CoreLogging.messageLogger; - public class ByteBuddyProxyFactory implements ProxyFactory, Serializable { private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyFactory.class ); - private static final String PROXY_NAMING_SUFFIX = Environment.useLegacyProxyClassnames() ? "HibernateProxy$" : "HibernateProxy"; + + private final ByteBuddyProxyHelper byteBuddyProxyHelper; private Class persistentClass; private String entityName; @@ -59,6 +39,10 @@ public class ByteBuddyProxyFactory implements ProxyFactory, Serializable { private Class proxyClass; + public ByteBuddyProxyFactory(ByteBuddyProxyHelper byteBuddyProxyHelper) { + this.byteBuddyProxyHelper = byteBuddyProxyHelper; + } + @Override public void postInstantiate( String entityName, @@ -75,7 +59,7 @@ public void postInstantiate( this.componentIdType = componentIdType; this.overridesEquals = ReflectHelper.overridesEquals( persistentClass ); - this.proxyClass = buildProxy( persistentClass, this.interfaces ); + this.proxyClass = byteBuddyProxyHelper.buildProxy( persistentClass, this.interfaces ); } private Class[] toArray(Set interfaces) { @@ -86,35 +70,6 @@ private Class[] toArray(Set interfaces) { return interfaces.toArray( new Class[interfaces.size()] ); } - public static Class buildProxy( - final Class persistentClass, - final Class[] interfaces) { - Set> key = new HashSet>(); - if ( interfaces.length == 1 ) { - key.add( persistentClass ); - } - key.addAll( Arrays.>asList( interfaces ) ); - - final TypeCache cacheForProxies = ByteBuddyState.getCacheForProxies(); - - return cacheForProxies.findOrInsert( persistentClass.getClassLoader(), new TypeCache.SimpleKey(key), () -> - ByteBuddyState.getStaticByteBuddyInstance() - .ignore( isSynthetic().and( named( "getMetaClass" ).and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ) ) - .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getName() ) ) ) - .subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING ) - .implement( (Type[]) interfaces ) - .method( isVirtual().and( not( isFinalizer() ) ) ) - .intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) ) - .method( nameStartsWith( "$$_hibernate_" ).and( isVirtual() ) ) - .intercept( SuperMethodCall.INSTANCE ) - .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) - .implement( ProxyConfiguration.class ) - .intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) ) - .make() - .load( persistentClass.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( persistentClass ) ) - .getLoaded(), cacheForProxies ); - } - @Override public HibernateProxy getProxy( Serializable id, @@ -142,83 +97,4 @@ public HibernateProxy getProxy( throw new HibernateException( LOG.bytecodeEnhancementFailed( entityName ), t ); } } - - public static HibernateProxy deserializeProxy(SerializableProxy serializableProxy) { - final ByteBuddyInterceptor interceptor = new ByteBuddyInterceptor( - serializableProxy.getEntityName(), - serializableProxy.getPersistentClass(), - serializableProxy.getInterfaces(), - serializableProxy.getId(), - resolveIdGetterMethod( serializableProxy ), - resolveIdSetterMethod( serializableProxy ), - serializableProxy.getComponentIdType(), - null, - ReflectHelper.overridesEquals( serializableProxy.getPersistentClass() ) - ); - - // note: interface is assumed to already contain HibernateProxy.class - try { - final Class proxyClass = buildProxy( - serializableProxy.getPersistentClass(), - serializableProxy.getInterfaces() - ); - final HibernateProxy proxy = (HibernateProxy) proxyClass.newInstance(); - ( (ProxyConfiguration) proxy ).$$_hibernate_set_interceptor( interceptor ); - return proxy; - } - catch (Throwable t) { - final String message = LOG.bytecodeEnhancementFailed( serializableProxy.getEntityName() ); - LOG.error( message, t ); - throw new HibernateException( message, t ); - } - } - - @SuppressWarnings("unchecked") - private static Method resolveIdGetterMethod(SerializableProxy serializableProxy) { - if ( serializableProxy.getIdentifierGetterMethodName() == null ) { - return null; - } - - try { - return serializableProxy.getIdentifierGetterMethodClass().getDeclaredMethod( serializableProxy.getIdentifierGetterMethodName() ); - } - catch (NoSuchMethodException e) { - throw new HibernateException( - String.format( - Locale.ENGLISH, - "Unable to deserialize proxy [%s, %s]; could not locate id getter method [%s] on entity class [%s]", - serializableProxy.getEntityName(), - serializableProxy.getId(), - serializableProxy.getIdentifierGetterMethodName(), - serializableProxy.getIdentifierGetterMethodClass() - ) - ); - } - } - - @SuppressWarnings("unchecked") - private static Method resolveIdSetterMethod(SerializableProxy serializableProxy) { - if ( serializableProxy.getIdentifierSetterMethodName() == null ) { - return null; - } - - try { - return serializableProxy.getIdentifierSetterMethodClass().getDeclaredMethod( - serializableProxy.getIdentifierSetterMethodName(), - serializableProxy.getIdentifierSetterMethodParams() - ); - } - catch (NoSuchMethodException e) { - throw new HibernateException( - String.format( - Locale.ENGLISH, - "Unable to deserialize proxy [%s, %s]; could not locate id setter method [%s] on entity class [%s]", - serializableProxy.getEntityName(), - serializableProxy.getId(), - serializableProxy.getIdentifierSetterMethodName(), - serializableProxy.getIdentifierSetterMethodClass() - ) - ); - } - } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..2e3d5fdef5416e2abb6fd25b86410e8960ffc3ca --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java @@ -0,0 +1,147 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.proxy.pojo.bytebuddy; + +import static org.hibernate.internal.CoreLogging.messageLogger; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import org.hibernate.HibernateException; +import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; +import org.hibernate.cfg.Environment; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.ProxyConfiguration; + +import net.bytebuddy.NamingStrategy; +import net.bytebuddy.TypeCache; +import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; +import net.bytebuddy.implementation.SuperMethodCall; + +public class ByteBuddyProxyHelper implements Serializable { + + private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyHelper.class ); + private static final String PROXY_NAMING_SUFFIX = Environment.useLegacyProxyClassnames() ? "HibernateProxy$" : "HibernateProxy"; + + private final ByteBuddyState byteBuddyState; + + public ByteBuddyProxyHelper(ByteBuddyState byteBuddyState) { + this.byteBuddyState = byteBuddyState; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Class buildProxy( + final Class persistentClass, + final Class[] interfaces) { + Set> key = new HashSet>(); + if ( interfaces.length == 1 ) { + key.add( persistentClass ); + } + key.addAll( Arrays.>asList( interfaces ) ); + + return byteBuddyState.loadProxy( persistentClass, new TypeCache.SimpleKey(key), byteBuddy -> byteBuddy + .ignore( byteBuddyState.getProxyDefinitionHelpers().getGroovyGetMetaClassFilter() ) + .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getName() ) ) ) + .subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING ) + .implement( (Type[]) interfaces ) + .method( byteBuddyState.getProxyDefinitionHelpers().getVirtualNotFinalizerFilter() ) + .intercept( byteBuddyState.getProxyDefinitionHelpers().getDelegateToInterceptorDispatcherMethodDelegation() ) + .method( byteBuddyState.getProxyDefinitionHelpers().getHibernateGeneratedMethodFilter() ) + .intercept( SuperMethodCall.INSTANCE ) + .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) + .implement( ProxyConfiguration.class ) + .intercept( byteBuddyState.getProxyDefinitionHelpers().getInterceptorFieldAccessor() ) + ); + } + + public HibernateProxy deserializeProxy(SerializableProxy serializableProxy) { + final ByteBuddyInterceptor interceptor = new ByteBuddyInterceptor( + serializableProxy.getEntityName(), + serializableProxy.getPersistentClass(), + serializableProxy.getInterfaces(), + serializableProxy.getId(), + resolveIdGetterMethod( serializableProxy ), + resolveIdSetterMethod( serializableProxy ), + serializableProxy.getComponentIdType(), + null, + ReflectHelper.overridesEquals( serializableProxy.getPersistentClass() ) + ); + + // note: interface is assumed to already contain HibernateProxy.class + try { + final Class proxyClass = buildProxy( + serializableProxy.getPersistentClass(), + serializableProxy.getInterfaces() + ); + final HibernateProxy proxy = (HibernateProxy) proxyClass.newInstance(); + ( (ProxyConfiguration) proxy ).$$_hibernate_set_interceptor( interceptor ); + return proxy; + } + catch (Throwable t) { + final String message = LOG.bytecodeEnhancementFailed( serializableProxy.getEntityName() ); + LOG.error( message, t ); + throw new HibernateException( message, t ); + } + } + + @SuppressWarnings("unchecked") + private static Method resolveIdGetterMethod(SerializableProxy serializableProxy) { + if ( serializableProxy.getIdentifierGetterMethodName() == null ) { + return null; + } + + try { + return serializableProxy.getIdentifierGetterMethodClass().getDeclaredMethod( serializableProxy.getIdentifierGetterMethodName() ); + } + catch (NoSuchMethodException e) { + throw new HibernateException( + String.format( + Locale.ENGLISH, + "Unable to deserialize proxy [%s, %s]; could not locate id getter method [%s] on entity class [%s]", + serializableProxy.getEntityName(), + serializableProxy.getId(), + serializableProxy.getIdentifierGetterMethodName(), + serializableProxy.getIdentifierGetterMethodClass() + ) + ); + } + } + + @SuppressWarnings("unchecked") + private static Method resolveIdSetterMethod(SerializableProxy serializableProxy) { + if ( serializableProxy.getIdentifierSetterMethodName() == null ) { + return null; + } + + try { + return serializableProxy.getIdentifierSetterMethodClass().getDeclaredMethod( + serializableProxy.getIdentifierSetterMethodName(), + serializableProxy.getIdentifierSetterMethodParams() + ); + } + catch (NoSuchMethodException e) { + throw new HibernateException( + String.format( + Locale.ENGLISH, + "Unable to deserialize proxy [%s, %s]; could not locate id setter method [%s] on entity class [%s]", + serializableProxy.getEntityName(), + serializableProxy.getId(), + serializableProxy.getIdentifierSetterMethodName(), + serializableProxy.getIdentifierSetterMethodClass() + ) + ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java index 493f777b03fa09f7da4ab36fb24c378071d965f4..546247f60649e4b5506d8e1744d7c99fd1d25811 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java @@ -9,6 +9,11 @@ import java.io.Serializable; import java.lang.reflect.Method; +import org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl; +import org.hibernate.bytecode.internal.bytebuddy.ProxyFactoryFactoryImpl; +import org.hibernate.bytecode.spi.BytecodeProvider; +import org.hibernate.bytecode.spi.ProxyFactoryFactory; +import org.hibernate.cfg.Environment; import org.hibernate.proxy.AbstractSerializableProxy; import org.hibernate.proxy.HibernateProxy; import org.hibernate.type.CompositeType; @@ -26,6 +31,10 @@ public final class SerializableProxy extends AbstractSerializableProxy { private final CompositeType componentIdType; + /** + * @deprecated use {@link #SerializableProxy(String, Class, Class[], Serializable, Boolean, String, boolean, Method, Method, CompositeType)} instead. + */ + @Deprecated public SerializableProxy( String entityName, Class persistentClass, @@ -35,7 +44,24 @@ public SerializableProxy( Method getIdentifierMethod, Method setIdentifierMethod, CompositeType componentIdType) { - super( entityName, id, readOnly ); + this( + entityName, persistentClass, interfaces, id, readOnly, null, false, + getIdentifierMethod, setIdentifierMethod, componentIdType + ); + } + + public SerializableProxy( + String entityName, + Class persistentClass, + Class[] interfaces, + Serializable id, + Boolean readOnly, + String sessionFactoryUuid, + boolean allowLoadOutsideTransaction, + Method getIdentifierMethod, + Method setIdentifierMethod, + CompositeType componentIdType) { + super( entityName, id, readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); this.persistentClass = persistentClass; this.interfaces = interfaces; if ( getIdentifierMethod != null ) { @@ -104,8 +130,13 @@ protected CompositeType getComponentIdType() { } private Object readResolve() { - HibernateProxy proxy = ByteBuddyProxyFactory.deserializeProxy( this ); - setReadOnlyBeforeAttachedToSession( (ByteBuddyInterceptor) proxy.getHibernateLazyInitializer() ); + BytecodeProvider bytecodeProvider = Environment.getBytecodeProvider(); + if ( !( bytecodeProvider instanceof BytecodeProviderImpl ) ) { + throw new IllegalStateException( "The bytecode provider is not ByteBuddy, unable to deserialize a ByteBuddy proxy." ); + } + + HibernateProxy proxy = ( (BytecodeProviderImpl) bytecodeProvider ).getByteBuddyProxyHelper().deserializeProxy( this ); + afterDeserialization( (ByteBuddyInterceptor) proxy.getHibernateLazyInitializer() ); return proxy; } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistLazyInitializer.java index 5ae423c0a1891f7cd31b06f5ed9619f46bfc7652..61b15f09a3e679c2f7c265720f6061e47467f8c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistLazyInitializer.java @@ -123,8 +123,10 @@ protected Object serializableProxy() { getEntityName(), persistentClass, interfaces, - getIdentifier(), + getInternalIdentifier(), ( isReadOnlySettingAvailable() ? Boolean.valueOf( isReadOnly() ) : isReadOnlyBeforeAttachedToSession() ), + getSessionFactoryUuid(), + isAllowLoadOutsideTransaction(), getIdentifierMethod, setIdentifierMethod, componentIdType diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/SerializableProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/SerializableProxy.java index 9e9785336ebbd0da0db619ba47e986d42f98e80c..3ec55b8182e347e7717ed3435ec58d22cce66038 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/SerializableProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/SerializableProxy.java @@ -29,16 +29,37 @@ public final class SerializableProxy extends AbstractSerializableProxy { private final CompositeType componentIdType; + /** + * @deprecated use {@link #SerializableProxy(String, Class, Class[], Serializable, Boolean, String, boolean, Method, Method, CompositeType)} instead. + */ + @Deprecated + public SerializableProxy( + String entityName, + Class persistentClass, + Class[] interfaces, + Serializable id, + Boolean readOnly, + Method getIdentifierMethod, + Method setIdentifierMethod, + CompositeType componentIdType) { + this( + entityName, persistentClass, interfaces, id, readOnly, null, false, + getIdentifierMethod, setIdentifierMethod, componentIdType + ); + } + public SerializableProxy( String entityName, Class persistentClass, Class[] interfaces, Serializable id, Boolean readOnly, + String sessionFactoryUuid, + boolean allowLoadOutsideTransaction, Method getIdentifierMethod, Method setIdentifierMethod, CompositeType componentIdType) { - super( entityName, id, readOnly ); + super( entityName, id, readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); this.persistentClass = persistentClass; this.interfaces = interfaces; if ( getIdentifierMethod != null ) { @@ -114,7 +135,7 @@ protected CompositeType getComponentIdType() { */ private Object readResolve() { HibernateProxy proxy = JavassistProxyFactory.deserializeProxy( this ); - setReadOnlyBeforeAttachedToSession( ( JavassistLazyInitializer ) proxy.getHibernateLazyInitializer() ); + afterDeserialization( ( JavassistLazyInitializer ) proxy.getHibernateLazyInitializer() ); return proxy; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/Query.java b/hibernate-core/src/main/java/org/hibernate/query/Query.java index 6b43ba8b4737055f7512c9abad0a863bbb0c5ce7..e522a5b4750eaafa3ce8fd629a53866b1f10b9ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/Query.java +++ b/hibernate-core/src/main/java/org/hibernate/query/Query.java @@ -29,12 +29,10 @@ import org.hibernate.CacheMode; import org.hibernate.FlushMode; -import org.hibernate.Incubating; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; -import org.hibernate.engine.spi.RowSelection; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.BigDecimalType; import org.hibernate.type.BigIntegerType; @@ -67,7 +65,6 @@ * @author Steve Ebersole * @author Gavin King */ -@Incubating @SuppressWarnings("UnusedDeclaration") public interface Query extends TypedQuery, org.hibernate.Query, CommonQueryContract { /** diff --git a/hibernate-core/src/main/java/org/hibernate/query/QueryParameter.java b/hibernate-core/src/main/java/org/hibernate/query/QueryParameter.java index 25659e00a50a0bdfc2ab9ed2123ce74959b9c62c..a947ae7dcea64e841eb766cc552ca4f9ea5d948a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/QueryParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/QueryParameter.java @@ -21,7 +21,7 @@ public interface QueryParameter extends javax.persistence.Parameter { * * @return The Hibernate Type. */ - Type getType(); + Type getHibernateType(); int[] getSourceLocations(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java index af42b2f0091ca59e8501442ee0af97ccdfe0a365..fa66140b6478ac7a03c0a4d92afaef7dd68ef771 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java @@ -78,15 +78,15 @@ private String bindLiteral(RenderingContext renderingContext) { @SuppressWarnings({ "unchecked" }) public String renderProjection(RenderingContext renderingContext) { + if ( ValueHandlerFactory.isCharacter( literal ) ) { + // In case literal is a Character, pass literal.toString() as the argument. + return renderingContext.getDialect().inlineLiteral( literal.toString() ); + } + // some drivers/servers do not like parameters in the select clause final ValueHandlerFactory.ValueHandler handler = ValueHandlerFactory.determineAppropriateHandler( literal.getClass() ); - if ( ValueHandlerFactory.isCharacter( literal ) ) { - return '\'' + handler.render( literal ) + '\''; - } - else { - return handler.render( literal ); - } + return handler.render( literal ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java index 24f74ccacf3808f0fc19168329bd79a84d214ed4..3bac2a93ccf1aeadea9f21a7647b32f7a891a12f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java @@ -52,22 +52,27 @@ public ParameterExpressionImpl( this.position = null; } + @Override public String getName() { return name; } + @Override public Integer getPosition() { return position; } + @Override public Class getParameterType() { return getJavaType(); } + @Override public void registerParameters(ParameterRegistry registry) { registry.registerParameter( this ); } + @Override public String render(RenderingContext renderingContext) { final ExplicitParameterInfo parameterInfo = renderingContext.registerExplicitParameter( this ); return parameterInfo.render(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/ExistsPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/ExistsPredicate.java index 26c7d61cad61696a46bff4f0b3b4d1b61f8d1544..bdc70aeac266217d7ac7194a8344084f122c44ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/ExistsPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/predicate/ExistsPredicate.java @@ -15,7 +15,7 @@ import org.hibernate.query.criteria.internal.compile.RenderingContext; /** - * Models an EXISTS() predicate + * Models an {@code EXISTS()} predicate * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java index a16aca136a2230898e71a6716761d4993746d668..81f3f209088a0d828e948d6f3946efbe753338e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java @@ -25,6 +25,9 @@ import java.util.Set; import java.util.Spliterator; import java.util.Spliterators; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; import javax.persistence.CacheRetrieveMode; @@ -96,6 +99,7 @@ import static org.hibernate.jpa.QueryHints.HINT_FLUSH_MODE; import static org.hibernate.jpa.QueryHints.HINT_FOLLOW_ON_LOCKING; import static org.hibernate.jpa.QueryHints.HINT_LOADGRAPH; +import static org.hibernate.jpa.QueryHints.HINT_NATIVE_SPACES; import static org.hibernate.jpa.QueryHints.HINT_READONLY; import static org.hibernate.jpa.QueryHints.HINT_TIMEOUT; import static org.hibernate.jpa.QueryHints.SPEC_HINT_TIMEOUT; @@ -752,57 +756,104 @@ public boolean isBound(Parameter parameter) { @Override public T getParameterValue(Parameter parameter) { LOGGER.tracef( "#getParameterValue(%s)", parameter ); - getProducer().checkOpen( false ); - if ( !getParameterMetadata().containsReference( (QueryParameter) parameter ) ) { - throw new IllegalArgumentException( "Parameter reference [" + parameter + "] did not come from this query" ); - } - - final QueryParameterBinding binding = getQueryParameterBindings().getBinding( (QueryParameter) parameter ); - LOGGER.debugf( "Checking whether parameter reference [%s] is bound : %s", parameter, binding.isBound() ); - if ( !binding.isBound() ) { - throw new IllegalStateException( "Parameter value not yet bound : " + parameter.toString() ); - } - return binding.getBindValue(); + return (T) getParameterValue( + (QueryParameter) parameter, + (queryParameter) -> new IllegalStateException( "Parameter value not yet bound : " + queryParameter.toString() ), + (queryParameter, e) -> { + final String message = "Parameter reference [" + queryParameter + "] did not come from this query"; + if ( e == null ) { + return new IllegalArgumentException( message ); + } + return new IllegalArgumentException( message, e ); + }, + (queryParameter, isBound) -> LOGGER.debugf( + "Checking whether parameter reference [%s] is bound : %s", + queryParameter, + isBound + ) + ); } @Override public Object getParameterValue(String name) { getProducer().checkOpen( false ); - final QueryParameterBinding binding; - try { - binding = getQueryParameterBindings().getBinding( name ); - } - catch (QueryParameterException e) { - throw new IllegalArgumentException( "Could not resolve parameter by name - " + name, e ); - } - - LOGGER.debugf( "Checking whether named parameter [%s] is bound : %s", name, binding.isBound() ); - if ( !binding.isBound() ) { - throw new IllegalStateException( "Parameter value not yet bound : " + name ); - } - return binding.getBindValue(); + final QueryParameter queryParameter = getParameterMetadata().getQueryParameter( name ); + return getParameterValue( + queryParameter, + (parameter) -> new IllegalStateException( "Parameter value not yet bound : " + parameter.getName() ), + (parameter, e) -> { + final String message = "Could not resolve parameter by name - " + parameter.getName(); + if ( e == null ) { + return new IllegalArgumentException( message ); + } + return new IllegalArgumentException( message, e ); + }, + (parameter, isBound) -> LOGGER.debugf( + "Checking whether positional named [%s] is bound : %s", + parameter.getName(), + isBound + ) + ); } @Override public Object getParameterValue(int position) { getProducer().checkOpen( false ); - final QueryParameterBinding binding; + final QueryParameter queryParameter = getParameterMetadata().getQueryParameter( position ); + return getParameterValue( + queryParameter, + (parameter) -> new IllegalStateException( "Parameter value not yet bound : " + parameter.getPosition() ), + (parameter, e) -> { + String message = "Could not resolve parameter by position - " + parameter.getPosition(); + if ( e == null ) { + return new IllegalArgumentException( message ); + } + return new IllegalArgumentException( message, e ); + }, + (parameter, isBound) -> LOGGER.debugf( + "Checking whether positional parameter [%s] is bound : %s", + parameter.getPosition(), + isBound + ) + ); + } + + private Object getParameterValue( + QueryParameter queryParameter, + Function notBoundParamenterException, + BiFunction couldNotResolveParameterException, + BiConsumer boundCheckingLogger) { try { - binding = getQueryParameterBindings().getBinding( position ); + final QueryParameterBindings parameterBindings = getQueryParameterBindings(); + + if ( queryParameter == null ) { + throw couldNotResolveParameterException.apply( queryParameter, null ); + } + if ( parameterBindings.isMultiValuedBinding( queryParameter ) ) { + final QueryParameterListBinding queryParameterListBinding = parameterBindings + .getQueryParameterListBinding( queryParameter ); + final Collection bindValues = queryParameterListBinding.getBindValues(); + if ( bindValues == null ) { + throw notBoundParamenterException.apply( queryParameter ); + } + return bindValues; + } + + final QueryParameterBinding binding = parameterBindings.getBinding( queryParameter ); + final boolean bound = binding.isBound(); + boundCheckingLogger.accept( queryParameter, bound ); + if ( !bound ) { + throw notBoundParamenterException.apply( queryParameter ); + } + return binding.getBindValue(); } catch (QueryParameterException e) { - throw new IllegalArgumentException( "Could not resolve parameter by position - " + position, e ); - } - - LOGGER.debugf( "Checking whether positional parameter [%s] is bound : %s", (Integer) position, (Boolean) binding.isBound() ); - if ( !binding.isBound() ) { - throw new IllegalStateException( "Parameter value not yet bound : " + position ); + throw couldNotResolveParameterException.apply( queryParameter, e ); } - return binding.getBindValue(); } @Override @@ -840,7 +891,7 @@ else if ( retType.isArray() ) { protected Type determineType(String namedParam, Class retType) { Type type = getQueryParameterBindings().getBinding( namedParam ).getBindType(); if ( type == null ) { - type = getParameterMetadata().getQueryParameter( namedParam ).getType(); + type = getParameterMetadata().getQueryParameter( namedParam ).getHibernateType(); } if ( type == null ) { type = getProducer().getFactory().resolveParameterBindType( retType ); @@ -1057,6 +1108,9 @@ else if ( JPA_SHARED_CACHE_STORE_MODE.equals( hintName ) ) { final CacheStoreMode storeMode = value != null ? CacheStoreMode.valueOf( value.toString() ) : null; applied = applyJpaCacheStoreMode( storeMode ); } + else if ( HINT_NATIVE_SPACES.equals( hintName ) ) { + applied = applyQuerySpaces( value ); + } else if ( QueryHints.HINT_NATIVE_LOCKMODE.equals( hintName ) ) { applied = applyNativeQueryLockMode( value ); } @@ -1108,6 +1162,12 @@ else if ( QueryHints.HINT_PASS_DISTINCT_THROUGH.equals( hintName ) ) { return this; } + protected boolean applyQuerySpaces(Object value) { + throw new IllegalStateException( + "Illegal attempt to apply native-query spaces to a non-native query" + ); + } + protected void handleUnrecognizedHint(String hintName, Object value) { MSG_LOGGER.debugf( "Skipping unsupported query hint [%s]", hintName ); } @@ -1343,11 +1403,12 @@ protected QueryParameters makeQueryParametersForExecution(String hql) { entityGraphHintedQueryPlan = null; } else { + final SharedSessionContractImplementor producer = getProducer(); entityGraphHintedQueryPlan = new HQLQueryPlan( hql, false, - getProducer().getLoadQueryInfluencers().getEnabledFilters(), - getProducer().getFactory(), + producer.getLoadQueryInfluencers().getEnabledFilters(), + producer.getFactory(), entityGraphQueryHint ); } @@ -1511,7 +1572,7 @@ public List list() { throw new IllegalArgumentException( e ); } catch (HibernateException he) { - throw getExceptionConverter().convert( he ); + throw getExceptionConverter().convert( he, getLockOptions() ); } finally { afterQuery(); @@ -1557,12 +1618,7 @@ public R getSingleResult() { return uniqueElement( list ); } catch ( HibernateException e ) { - if ( getProducer().getFactory().getSessionFactoryOptions().isJpaBootstrap() ) { - throw getExceptionConverter().convert( e ); - } - else { - throw e; - } + throw getExceptionConverter().convert( e, getLockOptions() ); } } @@ -1582,13 +1638,8 @@ public static R uniqueElement(List list) throws NonUniqueResultException @Override public int executeUpdate() throws HibernateException { - if ( ! getProducer().isTransactionInProgress() ) { - throw getProducer().getExceptionConverter().convert( - new TransactionRequiredException( - "Executing an update/delete query" - ) - ); - } + getProducer().checkTransactionNeededForUpdateOperation( "Executing an update/delete query" ); + beforeQuery(); try { return doExecuteUpdate(); @@ -1600,12 +1651,7 @@ public int executeUpdate() throws HibernateException { throw new IllegalArgumentException( e ); } catch ( HibernateException e) { - if ( getProducer().getFactory().getSessionFactoryOptions().isJpaBootstrap() ) { - throw getExceptionConverter().convert( e ); - } - else { - throw e; - } + throw getExceptionConverter().convert( e ); } finally { afterQuery(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/NativeQueryImpl.java index e85f338094cfd22505a89ac853a1322de52d46f9..9a33069425afba1e71d186e882422f9b443314ea 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/NativeQueryImpl.java @@ -19,6 +19,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.StringTokenizer; import javax.persistence.FlushModeType; import javax.persistence.LockModeType; import javax.persistence.Parameter; @@ -32,6 +33,7 @@ import org.hibernate.MappingException; import org.hibernate.QueryException; import org.hibernate.ScrollMode; +import org.hibernate.SynchronizeableQuery; import org.hibernate.engine.ResultSetMappingDefinition; import org.hibernate.engine.query.spi.EntityGraphQueryHint; import org.hibernate.engine.query.spi.sql.NativeSQLQueryConstructorReturn; @@ -42,6 +44,7 @@ import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.query.NativeQuery; import org.hibernate.query.ParameterMetadata; import org.hibernate.query.QueryParameter; @@ -246,7 +249,7 @@ else if ( NativeSQLQueryConstructorReturn.class.isInstance( queryReturn ) ) { super.beforeQuery(); - if ( getSynchronizedQuerySpaces() != null && !getSynchronizedQuerySpaces().isEmpty() ) { + if ( CollectionHelper.isNotEmpty( getSynchronizedQuerySpaces() ) ) { // The application defined query spaces on the Hibernate native SQLQuery which means the query will already // perform a partial flush according to the defined query spaces, no need to do a full flush. return; @@ -438,12 +441,18 @@ public NativeQueryImplementor addSynchronizedQuerySpace(String querySpace) { return this; } + @Override + public SynchronizeableQuery addSynchronizedQuerySpace(String... querySpaces) { + addQuerySpaces( querySpaces ); + return this; + } + protected void addQuerySpaces(String... spaces) { if ( spaces != null ) { if ( querySpaces == null ) { querySpaces = new ArrayList<>(); } - querySpaces.addAll( Arrays.asList( (String[]) spaces ) ); + querySpaces.addAll( Arrays.asList( spaces ) ); } } @@ -468,6 +477,36 @@ public NativeQueryImplementor addSynchronizedEntityClass(Class entityClass) t return this; } + @Override + protected boolean applyQuerySpaces(Object value) { + if ( value == null ) { + return false; + } + + if ( value instanceof String[] ) { + addSynchronizedQuerySpace( (String[]) value ); + return true; + } + + if ( value instanceof Collection ) { + if ( querySpaces == null ) { + querySpaces = new ArrayList<>(); + } + querySpaces.addAll( (Collection) value ); + return true; + } + + if ( value instanceof String ) { + final StringTokenizer spaces = new StringTokenizer( (String) value, "," ); + while ( spaces.hasMoreTokens() ) { + addQuerySpaces( spaces.nextToken() ); + } + return true; + } + + return false; + } + @Override protected boolean isNativeQuery() { return true; 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 29bb9780eb80113bd7db311d06a1aecde18eac22..10132576161d845491f1969ec4b07af10e2e2e82 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 @@ -119,7 +119,7 @@ protected QueryParameterBinding makeBinding(QueryParameter queryParameter) { ); } - final QueryParameterBinding binding = makeBinding( queryParameter.getType() ); + final QueryParameterBinding binding = makeBinding( queryParameter.getHibernateType() ); parameterBindingMap.put( queryParameter, binding ); return binding; @@ -145,7 +145,7 @@ protected QueryParameterListBinding makeListBinding(QueryParameter par return parameterListBindingMap.computeIfAbsent( param, p -> new QueryParameterListBindingImpl( - param.getType(), + param.getHibernateType(), shouldValidateBindingValue() ) ); @@ -317,6 +317,14 @@ public Object[] collectPositionalBindValues() { // return values.toArray( new Object[values.size()] ); } + @Override + public boolean isMultiValuedBinding(QueryParameter parameter) { + if ( parameterListBindingMap == null ) { + return false; + } + return parameterListBindingMap.containsKey( parameter ); + } + /** * @deprecated (since 5.2) expect a different approach to org.hibernate.engine.spi.QueryParameters in 6.0 */ @@ -606,7 +614,7 @@ public String expandListValuedParameters(String queryString, SharedSessionContra syntheticParam = new NamedParameterDescriptor( syntheticName, - sourceParam.getType(), + sourceParam.getHibernateType(), sourceParam.getSourceLocations() ); } @@ -624,7 +632,7 @@ public String expandListValuedParameters(String queryString, SharedSessionContra syntheticParam = new OrdinalParameterDescriptor( syntheticPosition, syntheticPosition - jdbcStyleOrdinalCountBase, - sourceParam.getType(), + sourceParam.getHibernateType(), sourceParam.getSourceLocations() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java index d7f1144b58d8e50b7730912557a5a9c493826232..8ffa92f601453663e41e1afc431745135493dc0f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java @@ -25,7 +25,7 @@ public QueryParameterImpl(Type expectedType) { } @Override - public Type getType() { + public Type getHibernateType() { return expectedType; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java index 85a443fe6c6f8be920ca390765903dc6cc472baf..55731593e97361094c454d829ef751888aac960c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java @@ -39,6 +39,7 @@ public Integer getPosition() { return null; } + @Override public int[] getSourceLocations() { return sourceLocations; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java index 050608d0ab38f1a4586112dadc3c547844410b26..0bd9c4a2fde3562d350d6e4d6ab5bc802961d63c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java @@ -8,12 +8,16 @@ import javax.persistence.ParameterMode; +import org.hibernate.Incubating; import org.hibernate.procedure.spi.ParameterRegistrationImplementor; import org.hibernate.query.QueryParameter; /** + * NOTE: Consider this contract (and its sub-contracts) as incubating as we transition to 6.0 and SQM + * * @author Steve Ebersole */ +@Incubating public interface ProcedureParameter extends QueryParameter { /** * Retrieves the parameter "mode". Only really pertinent in regards to procedure/function calls. diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java index 009dd447bfa8df04d699250d0989f35e7d2af6c0..63a4c0528d5e7c22ab72a78951989b3e1151c968 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java @@ -118,11 +118,6 @@ public Integer getPosition() { return position; } - @Override - public Type getHibernateType() { - return getType(); - } - @Override public void setHibernateType(Type expectedType) { super.setHibernateType( expectedType ); @@ -308,6 +303,7 @@ private boolean canDoNameParameterBinding(Type hibernateType) { && ((ProcedureParameterNamedBinder) hibernateType).canDoSetting(); } + @Override public int[] getSqlTypes() { if ( mode == ParameterMode.REF_CURSOR ) { // we could use the Types#REF_CURSOR added in Java 8, but that would require requiring Java 8... diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java index f671e05a2e9ed11c741ec5fd5b07a831f08af7bb..a5558058ff8fbdfe54f3049d740e6f8bfc683b0a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java @@ -6,11 +6,15 @@ */ package org.hibernate.query.procedure.spi; +import org.hibernate.Incubating; import org.hibernate.procedure.spi.ParameterRegistrationImplementor; import org.hibernate.query.procedure.ProcedureParameter; /** + * NOTE: Consider this contract (and its sub-contracts) as incubating as we transition to 6.0 and SQM + * * @author Steve Ebersole */ +@Incubating public interface ProcedureParameterImplementor extends ProcedureParameter, ParameterRegistrationImplementor { } diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/NamedQueryRepository.java b/hibernate-core/src/main/java/org/hibernate/query/spi/NamedQueryRepository.java index cc8abd65873f347f72440c173b8602eee8bc043e..3eaa91d5d345c0001604f984748a0afee13ed991 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/NamedQueryRepository.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/NamedQueryRepository.java @@ -90,6 +90,14 @@ public ResultSetMappingDefinition getResultSetMappingDefinition(String mappingNa return namedSqlResultSetMappingMap.get( mappingName ); } + private synchronized void removeNamedQueryDefinition(String name) { + if ( this.namedQueryDefinitionMap.containsKey( name ) ) { + final Map copy = CollectionHelper.makeCopy( namedQueryDefinitionMap ); + copy.remove( name ); + this.namedQueryDefinitionMap = Collections.unmodifiableMap( copy ); + } + } + public synchronized void registerNamedQueryDefinition(String name, NamedQueryDefinition definition) { if ( NamedSQLQueryDefinition.class.isInstance( definition ) ) { throw new IllegalArgumentException( "NamedSQLQueryDefinition instance incorrectly passed to registerNamedQueryDefinition" ); @@ -109,7 +117,17 @@ public synchronized void registerNamedQueryDefinition(String name, NamedQueryDef ); } + this.namedQueryDefinitionMap = Collections.unmodifiableMap( copy ); + removeNamedSQLQueryDefinition( name ); + } + + private synchronized void removeNamedSQLQueryDefinition(String name) { + if ( this.namedSqlQueryDefinitionMap.containsKey( name ) ) { + final Map copy = CollectionHelper.makeCopy( namedSqlQueryDefinitionMap ); + copy.remove( name ); + this.namedSqlQueryDefinitionMap = Collections.unmodifiableMap( copy ); + } } public synchronized void registerNamedSQLQueryDefinition(String name, NamedSQLQueryDefinition definition) { @@ -128,6 +146,7 @@ public synchronized void registerNamedSQLQueryDefinition(String name, NamedSQLQu } this.namedSqlQueryDefinitionMap = Collections.unmodifiableMap( copy ); + removeNamedQueryDefinition( name ); } public synchronized void registerNamedProcedureCallMemento(String name, ProcedureCallMemento memento) { 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 5eb46e9b269e07048eaf1ca365c28eda962669e5..24667470fa1b35d6a3d5ecc2e5e505fe8fce762e 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 @@ -35,4 +35,12 @@ public interface QueryParameterBindings { Type[] collectPositionalBindTypes(); Object[] collectPositionalBindValues(); Map collectNamedParameterBindings(); + + /** + * @deprecated expect a different approach to org.hibernate.engine.spi.QueryParameters in 6.0 + */ + @Deprecated + default boolean isMultiValuedBinding(QueryParameter parameter) { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java index 5e50bb5316db50277969a87706366e69abce198f..0f34c14d9378c81be4e6b5f5bf9e93f9e5d0fb48 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java @@ -81,18 +81,16 @@ public void register(Statement statement, boolean cancelable) { public void release(Statement statement) { log.tracev( "Releasing statement [{0}]", statement ); - // Keep this at DEBUG level, rather than warn. Numerous connection pool implementations can return a - // proxy/wrapper around the JDBC Statement, causing excessive logging here. See HHH-8210. - if ( log.isDebugEnabled() && !xref.containsKey( statement ) ) { - log.unregisteredStatement(); + final Set resultSets = xref.remove( statement ); + if ( resultSets != null ) { + closeAll( resultSets ); } else { - final Set resultSets = xref.get( statement ); - if ( resultSets != null ) { - closeAll( resultSets ); - } - xref.remove( statement ); + // Keep this at DEBUG level, rather than warn. Numerous connection pool implementations can return a + // proxy/wrapper around the JDBC Statement, causing excessive logging here. See HHH-8210. + log.unregisteredStatement(); } + close( statement ); if ( lastQuery == statement ) { diff --git a/hibernate-core/src/main/java/org/hibernate/secure/internal/AbstractJaccSecurableEventListener.java b/hibernate-core/src/main/java/org/hibernate/secure/internal/AbstractJaccSecurableEventListener.java index e21d3a2df733008039371a508aaf604bc82bec4a..95c172d0ed0f543bb15be96b4d69a02d5148c427 100644 --- a/hibernate-core/src/main/java/org/hibernate/secure/internal/AbstractJaccSecurableEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/internal/AbstractJaccSecurableEventListener.java @@ -16,7 +16,10 @@ * Base class for JACC-securable event listeners * * @author Steve Ebersole + * + * @deprecated Support for JACC will be removed in 6.0 */ +@Deprecated public abstract class AbstractJaccSecurableEventListener implements JaccSecurityListener { private JaccService jaccService; diff --git a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreDeleteEventListener.java b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreDeleteEventListener.java index 41fea761320f1999b0fefad9c1f808e23fbaf4ee..ebec8624491985bbd21d9c4715deb3a668488549 100644 --- a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreDeleteEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreDeleteEventListener.java @@ -15,7 +15,10 @@ * * @author Kabir Khan * @author Steve Ebersole + * + * @deprecated Support for JACC will be removed in 6.0 */ +@Deprecated public class JaccPreDeleteEventListener extends AbstractJaccSecurableEventListener implements PreDeleteEventListener { public JaccPreDeleteEventListener() { } diff --git a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreInsertEventListener.java b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreInsertEventListener.java index c9ff548d5af8656dce9a13ba43a5eaa04be6f5d1..6836c0ed264294d4f0ddace3162d0a91ac69317e 100644 --- a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreInsertEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreInsertEventListener.java @@ -15,7 +15,10 @@ * * @author Kabir Khan * @author Steve Ebersole + * + * @deprecated Support for JACC will be removed in 6.0 */ +@Deprecated public class JaccPreInsertEventListener extends AbstractJaccSecurableEventListener implements PreInsertEventListener { public JaccPreInsertEventListener() { } diff --git a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreLoadEventListener.java index 7f3dc7b4ba5f1fe01a78804363be0ad5a24e6748..51d531ed0aa59c625ad62bf7ba75ee3c330059ff 100644 --- a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreLoadEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreLoadEventListener.java @@ -15,7 +15,10 @@ * * @author Kabir Khan * @author Steve Ebersole + * + * @deprecated Support for JACC will be removed in 6.0 */ +@Deprecated public class JaccPreLoadEventListener extends AbstractJaccSecurableEventListener implements PreLoadEventListener { public JaccPreLoadEventListener() { } diff --git a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreUpdateEventListener.java b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreUpdateEventListener.java index fa5a0abfbb758b9c537fe7cae1942f2867b68c83..7ced461c755317f8987cd5ddf0fa0013b9be3b63 100644 --- a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreUpdateEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccPreUpdateEventListener.java @@ -15,7 +15,10 @@ * * @author Kabir Khan * @author Steve Ebersole + * + * @deprecated Support for JACC will be removed in 6.0 */ +@Deprecated public class JaccPreUpdateEventListener extends AbstractJaccSecurableEventListener implements PreUpdateEventListener { public JaccPreUpdateEventListener() { } diff --git a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccSecurityListener.java b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccSecurityListener.java index 3828a8085879f8e4bfde752c28d43f42fa978e5b..6fc8a717690a13acc77e2bcf22e66c203dbc57ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccSecurityListener.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/internal/JaccSecurityListener.java @@ -11,6 +11,9 @@ * {@link org.hibernate.secure.spi.JaccIntegrator} for details. * * @author Kabir Khan + * + * @deprecated Support for JACC will be removed in 6.0 */ +@Deprecated public interface JaccSecurityListener { } diff --git a/hibernate-core/src/main/java/org/hibernate/secure/internal/StandardJaccServiceImpl.java b/hibernate-core/src/main/java/org/hibernate/secure/internal/StandardJaccServiceImpl.java index 959314938a6c22427d11e8b23de72edc70ab3edc..bf0c1fedbde79656989ec9239a31d7b941893213 100644 --- a/hibernate-core/src/main/java/org/hibernate/secure/internal/StandardJaccServiceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/internal/StandardJaccServiceImpl.java @@ -34,7 +34,10 @@ /** * @author Steve Ebersole + * + * @deprecated Support for JACC will be removed in 6.0 */ +@Deprecated public class StandardJaccServiceImpl implements JaccService, Configurable { private static final Logger log = Logger.getLogger( StandardJaccServiceImpl.class ); diff --git a/hibernate-core/src/main/java/org/hibernate/secure/spi/GrantedPermission.java b/hibernate-core/src/main/java/org/hibernate/secure/spi/GrantedPermission.java index 92d7fe458e834e7facc6d0156e0ebabd05a25ffc..17b71ebb2d7ce96a88ae2b9f2e882cc849b8258f 100644 --- a/hibernate-core/src/main/java/org/hibernate/secure/spi/GrantedPermission.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/spi/GrantedPermission.java @@ -10,7 +10,10 @@ * Describes a Hibernate (persistence) permission. * * @author Steve Ebersole + * + * @deprecated Support for JACC will be removed in 6.0 */ +@Deprecated public class GrantedPermission { private final String role; private final String entityName; diff --git a/hibernate-core/src/main/java/org/hibernate/secure/spi/JaccIntegrator.java b/hibernate-core/src/main/java/org/hibernate/secure/spi/JaccIntegrator.java index 36f61ce2b85af66b4620309c220870cc26f7a043..0779e7fc11ec0363a9568573f83f766d7f59dc75 100644 --- a/hibernate-core/src/main/java/org/hibernate/secure/spi/JaccIntegrator.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/spi/JaccIntegrator.java @@ -17,6 +17,7 @@ import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventType; import org.hibernate.integrator.spi.ServiceContributingIntegrator; +import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.secure.internal.DisabledJaccServiceImpl; import org.hibernate.secure.internal.JaccPreDeleteEventListener; import org.hibernate.secure.internal.JaccPreInsertEventListener; @@ -32,7 +33,10 @@ * Integrator for setting up JACC integration * * @author Steve Ebersole + * + * @deprecated Support for JACC will be removed in 6.0 */ +@Deprecated public class JaccIntegrator implements ServiceContributingIntegrator { private static final Logger log = Logger.getLogger( JaccIntegrator.class ); @@ -81,6 +85,12 @@ private void doIntegration( return; } + DeprecationLogger.DEPRECATION_LOGGER.deprecatedJaccUsage( + AvailableSettings.JACC_ENABLED, + AvailableSettings.JACC_CONTEXT_ID, + AvailableSettings.JACC_PREFIX + ); + final String contextId = (String) properties.get( AvailableSettings.JACC_CONTEXT_ID ); if ( contextId == null ) { throw new IntegrationException( "JACC context id must be specified" ); diff --git a/hibernate-core/src/main/java/org/hibernate/secure/spi/JaccPermissionDeclarations.java b/hibernate-core/src/main/java/org/hibernate/secure/spi/JaccPermissionDeclarations.java index 9fbaeff2ddcda200c21f67f09677d87dc0342b78..53441bfe8e514ec8f24fd47efda5790e778829a8 100644 --- a/hibernate-core/src/main/java/org/hibernate/secure/spi/JaccPermissionDeclarations.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/spi/JaccPermissionDeclarations.java @@ -12,7 +12,10 @@ /** * @author Steve Ebersole + * + * @deprecated Support for JACC will be removed in 6.0 */ +@Deprecated public class JaccPermissionDeclarations { private final String contextId; private List permissionDeclarations; diff --git a/hibernate-core/src/main/java/org/hibernate/secure/spi/JaccService.java b/hibernate-core/src/main/java/org/hibernate/secure/spi/JaccService.java index 2b784c6eb29b3c757df8a31296900b6df6d17cf7..50d70a810f902afad6c26d3caa63fe37b535563f 100644 --- a/hibernate-core/src/main/java/org/hibernate/secure/spi/JaccService.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/spi/JaccService.java @@ -12,7 +12,10 @@ * Service describing Hibernate integration with JACC for security service. * * @author Steve Ebersole + * + * @deprecated Support for JACC will be removed in 6.0 */ +@Deprecated public interface JaccService extends Service { /** * Obtain the JACC context-id in effect for this service. {@code null} indicates no diff --git a/hibernate-core/src/main/java/org/hibernate/secure/spi/PermissibleAction.java b/hibernate-core/src/main/java/org/hibernate/secure/spi/PermissibleAction.java index 761eaa169ea3d335c7c96ddd73cf8fb833312dfe..c5c2963d2cef02778bfa1c9506e020c9eb131977 100644 --- a/hibernate-core/src/main/java/org/hibernate/secure/spi/PermissibleAction.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/spi/PermissibleAction.java @@ -8,7 +8,10 @@ /** * @author Steve Ebersole + * + * @deprecated Support for JACC will be removed in 6.0 */ +@Deprecated public enum PermissibleAction { INSERT( "insert" ), UPDATE( "update" ), diff --git a/hibernate-core/src/main/java/org/hibernate/secure/spi/PermissionCheckEntityInformation.java b/hibernate-core/src/main/java/org/hibernate/secure/spi/PermissionCheckEntityInformation.java index 7aa7c6ad417e2982098924c6802e411b2822e26d..acc038901ce2d07da5fde2bd1770c38e9c10f69a 100644 --- a/hibernate-core/src/main/java/org/hibernate/secure/spi/PermissionCheckEntityInformation.java +++ b/hibernate-core/src/main/java/org/hibernate/secure/spi/PermissionCheckEntityInformation.java @@ -10,7 +10,10 @@ /** * @author Steve Ebersole + * + * @deprecated Support for JACC will be removed in 6.0 */ +@Deprecated public interface PermissionCheckEntityInformation { public Object getEntity(); public String getEntityName(); diff --git a/hibernate-core/src/main/java/org/hibernate/service/spi/Manageable.java b/hibernate-core/src/main/java/org/hibernate/service/spi/Manageable.java index a72eb47c59898ef92be739adde7ddcbe607905c7..1f64681cadce6d655dc34bf792c6bc7e8333f6ea 100644 --- a/hibernate-core/src/main/java/org/hibernate/service/spi/Manageable.java +++ b/hibernate-core/src/main/java/org/hibernate/service/spi/Manageable.java @@ -10,7 +10,11 @@ * Optional {@link org.hibernate.service.Service} contract for services which can be managed in JMX * * @author Steve Ebersole + * + * @deprecated Scheduled for removal in 6.0; see https://hibernate.atlassian.net/browse/HHH-14847 + * and https://hibernate.atlassian.net/browse/HHH-14846 */ +@Deprecated public interface Manageable { /** * Get the domain name to be used in registering the management bean. May be {@code null} to indicate Hibernate's diff --git a/hibernate-core/src/main/java/org/hibernate/sql/Delete.java b/hibernate-core/src/main/java/org/hibernate/sql/Delete.java index a6badc03051b0e991f0198d4253c99d5fdb04b91..876dd5f76a5f7f04bc3ff04398435c64477fbcbd 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/Delete.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/Delete.java @@ -9,6 +9,8 @@ import java.util.LinkedHashMap; import java.util.Map; +import org.hibernate.dialect.Dialect; + /** * An SQL DELETE statement * @@ -36,7 +38,7 @@ public Delete setTableName(String tableName) { public String toStatementString() { StringBuilder buf = new StringBuilder( tableName.length() + 10 ); if ( comment!=null ) { - buf.append( "/* " ).append(comment).append( " */ " ); + buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " ); } buf.append( "delete from " ).append(tableName); if ( where != null || !primaryKeyColumns.isEmpty() || versionColumnName != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/Insert.java b/hibernate-core/src/main/java/org/hibernate/sql/Insert.java index 49d828c92d9b3b69042b3c57740201bc4179499d..ed15af80f8bc65e80ffb779f0b9f183bac4995f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/Insert.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/Insert.java @@ -90,7 +90,7 @@ public Insert setTableName(String tableName) { public String toStatementString() { StringBuilder buf = new StringBuilder( columns.size()*15 + tableName.length() + 10 ); if ( comment != null ) { - buf.append( "/* " ).append( comment ).append( " */ " ); + buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " ); } buf.append("insert into ") .append(tableName); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/InsertSelect.java b/hibernate-core/src/main/java/org/hibernate/sql/InsertSelect.java index 387ee1d558c59c41405a01c48c1c77eddda5f7cb..c9110ec34c60db57f11361cbbf71e3b85310ee69 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/InsertSelect.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/InsertSelect.java @@ -65,7 +65,7 @@ public String toStatementString() { StringBuilder buf = new StringBuilder( (columnNames.size() * 15) + tableName.length() + 10 ); if ( comment!=null ) { - buf.append( "/* " ).append( comment ).append( " */ " ); + buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " ); } buf.append( "insert into " ).append( tableName ); if ( !columnNames.isEmpty() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/QuerySelect.java b/hibernate-core/src/main/java/org/hibernate/sql/QuerySelect.java index 649d973594070f14ec428b70722c14188093e83e..fc685182d8a2c59a76f6626d6e9ecfc2ab839aaa 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/QuerySelect.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/QuerySelect.java @@ -126,7 +126,7 @@ public void addOrderBy(String orderByString) { public String toQueryString() { StringBuilder buf = new StringBuilder( 50 ); if ( comment != null ) { - buf.append( "/* " ).append( comment ).append( " */ " ); + buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " ); } buf.append( "select " ); if ( distinct ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/Select.java b/hibernate-core/src/main/java/org/hibernate/sql/Select.java index e8b0aa4f3f38e8b8516b86e692ee2d35be853397..e497d7839339357f0a8037121537f0c734d5cb2d 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/Select.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/Select.java @@ -40,7 +40,7 @@ public Select(Dialect dialect) { public String toStatementString() { StringBuilder buf = new StringBuilder(guesstimatedBufferSize); if ( StringHelper.isNotEmpty(comment) ) { - buf.append("/* ").append(comment).append(" */ "); + buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " ); } buf.append("select ").append(selectClause) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/SimpleSelect.java b/hibernate-core/src/main/java/org/hibernate/sql/SimpleSelect.java index f27f407b2ceb7ccd6f93bad6cd3c9386e818163e..4c574cf5a6f27cb04b922fc58e0b4cd387a36a2a 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/SimpleSelect.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/SimpleSelect.java @@ -148,7 +148,7 @@ public String toStatementString() { ); if ( comment != null ) { - buf.append( "/* " ).append( comment ).append( " */ " ); + buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " ); } buf.append( "select " ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/Update.java b/hibernate-core/src/main/java/org/hibernate/sql/Update.java index 93611d73a4fe703486c8e49646ed484010921e9d..67ac8d8faef54cc0bf4e92657395f995f964c636 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/Update.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/Update.java @@ -166,7 +166,7 @@ public Update setWhere(String where) { public String toStatementString() { StringBuilder buf = new StringBuilder( (columns.size() * 15) + tableName.length() + 10 ); if ( comment!=null ) { - buf.append( "/* " ).append( comment ).append( " */ " ); + buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " ); } buf.append( "update " ).append( tableName ).append( " set " ); boolean assignmentsAppended = false; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByTranslation.java b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByTranslation.java index 70f4953a0866651e36854286bc01dda506ec464a..c34707903fdc4b278d1c954d9e34e5320eeb548b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByTranslation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByTranslation.java @@ -7,7 +7,7 @@ package org.hibernate.sql.ordering.antlr; /** - * Represents the result of an order-by translation by {@link @OrderByTranslator} + * Represents the result of an order-by translation by {@link OrderByFragmentTranslator} * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/TranslationContext.java b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/TranslationContext.java index 15709fe1905c873f135d23d786b496d2d3e273e5..9d14fcadf9a99c2d9dc9e9a5978ab373d782ef4c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/TranslationContext.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/TranslationContext.java @@ -31,7 +31,7 @@ public interface TranslationContext { public Dialect getDialect(); /** - * Retrieves the SQL function registry/tt> for this context. + * Retrieves the SQL function registry for this context. * * @return The SQL function registry. */ diff --git a/hibernate-core/src/main/java/org/hibernate/stat/Statistics.java b/hibernate-core/src/main/java/org/hibernate/stat/Statistics.java index 226dd590e8af5e9579426852e9e55d08f0b9d101..1b9260b199f8326d79d6a3005e36441c853d0787 100755 --- a/hibernate-core/src/main/java/org/hibernate/stat/Statistics.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/Statistics.java @@ -10,7 +10,7 @@ * Exposes statistics for a particular {@link org.hibernate.SessionFactory}. Beware of milliseconds metrics, they * are dependent of the JVM precision: you may then encounter a 10 ms approximation depending on you OS platform. * Please refer to the JVM documentation for more information. - * + * * @author Emmanuel Bernard */ public interface Statistics { @@ -37,7 +37,7 @@ public interface Statistics { /** * find entity statistics per name - * + * * @param entityName entity name * @return EntityStatistics object */ @@ -45,7 +45,7 @@ public interface Statistics { /** * Get collection statistics per role - * + * * @param role collection role * @return CollectionStatistics */ @@ -69,7 +69,7 @@ public interface Statistics { QueryStatistics getQueryStatistics(String queryString); /** - * Second level cache statistics per domain data (entity, collection, natural-id) region + * Second-level cache statistics per domain data (entity, collection, natural-id) region * * @param regionName The unqualified region name * @@ -81,7 +81,7 @@ public interface Statistics { CacheRegionStatistics getDomainDataRegionStatistics(String regionName); /** - * Second level cache statistics per query region + * Second-level cache statistics per query region * * @param regionName The unqualified region name * @@ -125,7 +125,7 @@ public interface Statistics { long getEntityLoadCount(); /** - * Get global number of entity fetchs + * Get global number of entity fetches * @return entity fetch (from DB) */ long getEntityFetchCount(); @@ -168,34 +168,34 @@ public interface Statistics { long getQueryCachePutCount(); /** - * Get the global number of naturalId queries executed against the database + * Get the global number of natural id queries executed against the database */ long getNaturalIdQueryExecutionCount(); /** - * Get the global maximum query time for naturalId queries executed against the database + * Get the global maximum query time for natural id queries executed against the database */ long getNaturalIdQueryExecutionMaxTime(); /** - * Get the region for the maximum naturalId query time + * Get the region for the maximum natural id query time */ String getNaturalIdQueryExecutionMaxTimeRegion(); String getNaturalIdQueryExecutionMaxTimeEntity(); /** - * Get the global number of cached naturalId lookups successfully retrieved from cache + * Get the global number of cached natural id lookups successfully retrieved from cache */ long getNaturalIdCacheHitCount(); /** - * Get the global number of cached naturalId lookups *not* found in cache + * Get the global number of cached natural id lookups *not* found in cache */ long getNaturalIdCacheMissCount(); /** - * Get the global number of cacheable naturalId lookups put in cache + * Get the global number of cacheable natural id lookups put in cache */ long getNaturalIdCachePutCount(); @@ -205,7 +205,7 @@ public interface Statistics { long getUpdateTimestampsCacheHitCount(); /** - * Get the global number of tables for which no update timestamps was *not* found in cache + * Get the global number of timestamp requests that were not found in the cache */ long getUpdateTimestampsCacheMissCount(); @@ -278,9 +278,9 @@ public interface Statistics { long getCollectionRecreateCount(); /** - * The milliseconds (JVM standard {@link System#currentTimeMillis()}) from - * which all statistics accessed since the initial creation of this Statistics - * instance or the last {@link #clear()} + * The milliseconds (JVM standard {@link System#currentTimeMillis()}) + * since the initial creation of this Statistics + * instance or the last time {@link #clear()} was called. * * @apiNote This time(stamp) is */ @@ -336,7 +336,7 @@ public interface Statistics { /** - * Second level cache statistics per region + * Second-level cache statistics per region * * @param regionName qualified region name * @@ -356,7 +356,7 @@ public interface Statistics { * @return NaturalIdCacheStatistics * * @deprecated (since 5.3) Use {@link #getNaturalIdStatistics} or - * {@link @getDomainDataRegionStatistics} instead depending on need + * {@link #getDomainDataRegionStatistics} instead depending on need */ @Deprecated NaturalIdCacheStatistics getNaturalIdCacheStatistics(String regionName); diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/QueryStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/QueryStatisticsImpl.java index 00ff28ba3aeee582e4cf0aca89ab6c16ce5fb64d..689a91a829c4690f22fee336f10935e24c925310 100644 --- a/hibernate-core/src/main/java/org/hibernate/stat/internal/QueryStatisticsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/QueryStatisticsImpl.java @@ -72,7 +72,7 @@ public long getCacheMissCount() { /** * Number of lines returned by all the executions of this query (from DB) * For now, {@link org.hibernate.Query#iterate()} - * and {@link org.hibernate.Query#scroll()()} do not fill this statistic + * and {@link org.hibernate.Query#scroll()} do not fill this statistic * * @return The number of rows cumulatively returned by the given query; iterate * and scroll queries do not effect this total as their number of returned rows 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 04043a2d714dd2eb7accf2d4693b8b9948eb82eb..01ff11cc8fd2f4ac453d79e151e7f24998650eb3 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 @@ -6,8 +6,6 @@ */ package org.hibernate.stat.internal; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.LongAdder; @@ -16,7 +14,6 @@ import org.hibernate.cache.spi.Region; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.service.Service; @@ -28,6 +25,7 @@ * Implementation of {@link org.hibernate.stat.Statistics} based on the {@link java.util.concurrent} package. * * @author Alex Snaps + * @author Sanne Grinovero */ @SuppressWarnings({ "unchecked" }) public class StatisticsImpl implements StatisticsImplementor, Service { @@ -85,21 +83,21 @@ public class StatisticsImpl implements StatisticsImplementor, Service { private final LongAdder optimisticFailureCount = new LongAdder(); - private final ConcurrentMap entityStatsMap = new ConcurrentHashMap(); - private final ConcurrentMap naturalIdQueryStatsMap = new ConcurrentHashMap(); - private final ConcurrentMap collectionStatsMap = new ConcurrentHashMap(); + private final StatsNamedContainer entityStatsMap = new StatsNamedContainer(); + private final StatsNamedContainer naturalIdQueryStatsMap = new StatsNamedContainer(); + private final StatsNamedContainer collectionStatsMap = new StatsNamedContainer(); /** * Keyed by query string */ - private final ConcurrentMap queryStatsMap = new ConcurrentHashMap(); + private final StatsNamedContainer queryStatsMap = new StatsNamedContainer(); /** * Keyed by region name */ - private final ConcurrentMap l2CacheStatsMap = new ConcurrentHashMap<>(); + private final StatsNamedContainer l2CacheStatsMap = new StatsNamedContainer<>(); - private final ConcurrentMap deprecatedNaturalIdStatsMap = new ConcurrentHashMap(); + private final StatsNamedContainer deprecatedNaturalIdStatsMap = new StatsNamedContainer(); @SuppressWarnings({ "UnusedDeclaration" }) @@ -197,7 +195,7 @@ public void setStatisticsEnabled(boolean b) { @Override public String[] getEntityNames() { if ( sessionFactory == null ) { - return ArrayHelper.toStringArray( entityStatsMap.keySet() ); + return entityStatsMap.keysAsArray(); } else { return sessionFactory.getMetamodel().getAllEntityNames(); @@ -210,9 +208,9 @@ public EntityStatisticsImpl getEntityStatistics(String entityName) { return null; } - return entityStatsMap.computeIfAbsent( + return entityStatsMap.getOrCompute( entityName, - s -> new EntityStatisticsImpl( sessionFactory.getMetamodel().entityPersister( entityName ) ) + s -> new EntityStatisticsImpl( sessionFactory.getMetamodel().entityPersister( s ) ) ); } @@ -310,7 +308,7 @@ public void entityCacheMiss(NavigableRole entityName, String regionName) { @Override public String[] getCollectionRoleNames() { if ( sessionFactory == null ) { - return ArrayHelper.toStringArray( collectionStatsMap.keySet() ); + return collectionStatsMap.keysAsArray(); } else { return sessionFactory.getMetamodel().getAllCollectionRoles(); @@ -323,9 +321,9 @@ public CollectionStatisticsImpl getCollectionStatistics(String role) { return null; } - return collectionStatsMap.computeIfAbsent( + return collectionStatsMap.getOrCompute( role, - s -> new CollectionStatisticsImpl( sessionFactory.getMetamodel().collectionPersister( role ) ) + s -> new CollectionStatisticsImpl( sessionFactory.getMetamodel().collectionPersister( s ) ) ); } @@ -415,12 +413,12 @@ public NaturalIdStatisticsImpl getNaturalIdStatistics(String rootEntityName) { return null; } - return naturalIdQueryStatsMap.computeIfAbsent( + return naturalIdQueryStatsMap.getOrCompute( rootEntityName, s -> { - final EntityPersister entityDescriptor = sessionFactory.getMetamodel().entityPersister( rootEntityName ); + final EntityPersister entityDescriptor = sessionFactory.getMetamodel().entityPersister( s ); if ( !entityDescriptor.hasNaturalIdentifier() ) { - throw new IllegalArgumentException( "Given entity [" + rootEntityName + "] does not define natural-id" ); + throw new IllegalArgumentException( "Given entity [" + s + "] does not define natural-id" ); } return new NaturalIdStatisticsImpl( entityDescriptor ); } @@ -429,8 +427,9 @@ public NaturalIdStatisticsImpl getNaturalIdStatistics(String rootEntityName) { @Override public DeprecatedNaturalIdCacheStatisticsImpl getNaturalIdCacheStatistics(String regionName) { - return deprecatedNaturalIdStatsMap.computeIfAbsent( - sessionFactory.getCache().unqualifyRegionName( regionName ), + final String key = sessionFactory.getCache().unqualifyRegionName( regionName ); + return deprecatedNaturalIdStatsMap.getOrCompute( + key, unqualifiedRegionName -> new DeprecatedNaturalIdCacheStatisticsImpl( unqualifiedRegionName, sessionFactory.getCache().getNaturalIdAccessesInRegion( unqualifiedRegionName ) @@ -569,18 +568,18 @@ public CacheRegionStatisticsImpl getDomainDataRegionStatistics(String regionName return null; } - return l2CacheStatsMap.computeIfAbsent( + return l2CacheStatsMap.getOrCompute( regionName, s -> { - final Region region = sessionFactory.getCache().getRegion( regionName ); + final Region region = sessionFactory.getCache().getRegion( s ); if ( region == null ) { - throw new IllegalArgumentException( "Unknown cache region : " + regionName ); + throw new IllegalArgumentException( "Unknown cache region : " + s ); } if ( region instanceof QueryResultsRegion ) { throw new IllegalArgumentException( - "Region name [" + regionName + "] referred to a query result region, not a domain data region" + "Region name [" + s + "] referred to a query result region, not a domain data region" ); } @@ -606,7 +605,7 @@ public CacheRegionStatisticsImpl getQueryRegionStatistics(String regionName) { return null; } - return l2CacheStatsMap.computeIfAbsent( + return l2CacheStatsMap.getOrCompute( regionName, s -> new CacheRegionStatisticsImpl( regionAccess.getRegion() ) ); @@ -622,15 +621,20 @@ public CacheRegionStatisticsImpl getCacheRegionStatistics(String regionName) { return null; } - return l2CacheStatsMap.computeIfAbsent( + return l2CacheStatsMap.getOrCompute( regionName, s -> { - Region region = sessionFactory.getCache().getRegion( regionName ); + Region region = sessionFactory.getCache().getRegion( s ); if ( region == null ) { + + if ( ! sessionFactory.getSessionFactoryOptions().isQueryCacheEnabled() ) { + return null; + } + // this is the pre-5.3 behavior. and since this is a pre-5.3 method it should behave consistently // NOTE that this method is deprecated - region = sessionFactory.getCache().getQueryResultsCache( regionName ).getRegion(); + region = sessionFactory.getCache().getQueryResultsCache( s ).getRegion(); } return new CacheRegionStatisticsImpl( region ); @@ -697,14 +701,14 @@ public void updateTimestampsCachePut() { @Override public String[] getQueries() { - return ArrayHelper.toStringArray( queryStatsMap.keySet() ); + return queryStatsMap.keysAsArray(); } @Override public QueryStatisticsImpl getQueryStatistics(String queryString) { - return queryStatsMap.computeIfAbsent( + return queryStatsMap.getOrCompute( queryString, - s -> new QueryStatisticsImpl( queryString ) + s -> new QueryStatisticsImpl( s ) ); } @@ -774,9 +778,9 @@ public void queryCacheHit(String hql, String regionName) { } private CacheRegionStatisticsImpl getQueryRegionStats(String regionName) { - return l2CacheStatsMap.computeIfAbsent( + return l2CacheStatsMap.getOrCompute( regionName, - s -> new CacheRegionStatisticsImpl( sessionFactory.getCache().getQueryResultsCache( regionName ).getRegion() ) + s -> new CacheRegionStatisticsImpl( sessionFactory.getCache().getQueryResultsCache( s ).getRegion() ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/StatsNamedContainer.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatsNamedContainer.java new file mode 100644 index 0000000000000000000000000000000000000000..9fe9260572825057b8b5fba946e6ba50f6b47500 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatsNamedContainer.java @@ -0,0 +1,78 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.stat.internal; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * Decorates a ConcurrentHashMap implementation to make sure the methods are being + * used correctly for the purpose of Hibernate's statistics. + * In particular, we do like the semantics of ConcurrentHashMap#computeIfAbsent + * but not how it performs. + * See also: + * - http://jsr166-concurrency.10961.n7.nabble.com/Re-ConcurrentHashMap-computeIfAbsent-td11687.html + * - https://hibernate.atlassian.net/browse/HHH-13527 + * + * @author Sanne Grinovero + */ +final class StatsNamedContainer { + + private final ConcurrentHashMap map = new ConcurrentHashMap(); + + public void clear() { + map.clear(); + } + + /** + * This method is inherently racy and expensive. Only use on non-hot paths, and + * only to get a recent snapshot. + * @return all keys in string form. + */ + public String[] keysAsArray() { + return map.keySet().toArray( new String[0] ); + } + + /** + * Similar semantics as you'd get by invoking {@link java.util.concurrent.ConcurrentMap#computeIfAbsent(Object, Function)}, + * but we check for the key existence first. + * This is technically a redundant check, but it has been shown to perform better when the key existing is very likely, + * as in our case. + * Most notably, the ConcurrentHashMap implementation might block other accesses for the sake of making + * sure the function is invoked at most once: we don't need this guarantee, and prefer to reduce risk of blocking. + */ + public V getOrCompute(final String key, final Function function) { + final V v1 = map.get( key ); + if ( v1 != null ) { + return v1; + } + else { + final V v2 = function.apply( key ); + //Occasionally a function might return null. We can't store a null in the CHM, + // so a placeholder would be required to implement that, but we prefer to just keep this + // situation as slightly sub-optimal so to not make the code more complex just to handle the exceptional case: + // null values are assumed to be rare enough for this not being worth it. + if ( v2 == null ) { + return null; + } + else { + final V v3 = map.putIfAbsent( key, v2 ); + if ( v3 == null ) { + return v2; + } + else { + return v3; + } + } + } + } + + public V get(final String key) { + return map.get( key ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java b/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java index cfddf1500bbd3b2c049a1624ac8b7cf0c75e380b..5a3bc99249be4fa3edbee655862ed08687a95019 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java @@ -36,7 +36,7 @@ * Code based on from: * https://github.com/hibernate/hibernate-orm/blob/159bc99a36d86988b61b88ba91eec82cac044e1c/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java * https://github.com/hibernate/hibernate-orm/blob/159bc99a36d86988b61b88ba91eec82cac044e1c/tooling/hibernate-enhance-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/MavenEnhancePlugin.java - * + *
        {@code
          * 
          *     
          *         
        @@ -44,6 +44,7 @@
          *     
          *     
          * 
        + * }
        * * @author Luis Barreiro * @author Taro App diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/spi/DatabaseInformation.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/spi/DatabaseInformation.java index cd72909a609fd67a1cf2aef8ac0b8f8fec651e1b..fa7d0a4f3bbc71965712ce6c8f3414de53d0a4f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/spi/DatabaseInformation.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/spi/DatabaseInformation.java @@ -54,16 +54,16 @@ public interface DatabaseInformation { /** * Obtain reference to the named TableInformation * - * @param tableName The qualfied table name + * @param tableName The qualified table name * * @return The table information. May return {@code null} if not found. */ TableInformation getTableInformation(QualifiedTableName tableName); /** - * Obtain reference to all the {@link TableInformation) for a given {@link Namespace} + * Obtain reference to all the {@link TableInformation} for a given {@link Namespace} * - * @param namespace The {@link Namespace} which contains the {@link TableInformation) + * @param namespace The {@link Namespace} which contains the {@link TableInformation} * * @return a {@link NameSpaceTablesInformation} */ diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java index a05270293098c765e5f3325df8a5fba56c27a942..84769b2ab48054c4eca6fec64b683551ba377334 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java @@ -300,8 +300,8 @@ protected void migrateTable( dialect, metadata, tableInformation, - getDefaultCatalogName( database, dialect ), - getDefaultSchemaName( database, dialect ) + database.getDefaultNamespace().getPhysicalName().getCatalog(), + database.getDefaultNamespace().getPhysicalName().getSchema() ), formatter, options, @@ -446,7 +446,7 @@ protected void applyForeignKeys( /** * Check if the ForeignKey already exists. First check based on definition and if that is not matched check if a key * with the exact same name exists. Keys with the same name are presumed to be functional equal. - * + * * @param foreignKey - ForeignKey, new key to be created * @param tableInformation - TableInformation, information of existing keys * @return boolean, true if key already exists @@ -581,14 +581,4 @@ private static void applySqlStrings( } } } - - private String getDefaultCatalogName(Database database, Dialect dialect) { - final Identifier identifier = database.getDefaultNamespace().getPhysicalName().getCatalog(); - return identifier == null ? null : identifier.render( dialect ); - } - - private String getDefaultSchemaName(Database database, Dialect dialect) { - final Identifier identifier = database.getDefaultNamespace().getPhysicalName().getSchema(); - return identifier == null ? null : identifier.render( dialect ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/GenerationTargetToDatabase.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/GenerationTargetToDatabase.java index 3a330cb0ada34f9c21fc45de301c4cc53f68c1e4..885fbeb10df351d3fdf4f17ef706a757158c511e 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/GenerationTargetToDatabase.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/exec/GenerationTargetToDatabase.java @@ -86,6 +86,15 @@ private Statement jdbcStatement() { @Override public void release() { + if ( jdbcStatement != null ) { + try { + jdbcStatement.close(); + jdbcStatement = null; + } + catch (SQLException e) { + throw ddlTransactionIsolator.getJdbcContext().getSqlExceptionHelper().convert( e, "Unable to close JDBC Statement after DDL execution" ); + } + } if ( releaseAfterUse ) { ddlTransactionIsolator.release(); } 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 4b770edf44a22c38a1d81e195b77f749f6d1fccf..a307ccfcb4717bbf6fdbd36c59d630e382bec144 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java @@ -10,6 +10,7 @@ import org.hibernate.EntityMode; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.engine.internal.UnsavedValueFactory; import org.hibernate.engine.spi.IdentifierValue; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -168,6 +169,12 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( boolean alwaysDirtyCheck = type.isAssociationType() && ( (AssociationType) type ).isAlwaysDirtyChecked(); + final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + property, + lazyAvailable, + sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled() + ); + switch ( nature ) { case BASIC: { return new EntityBasedBasicAttribute( @@ -177,7 +184,7 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( property.getName(), type, new BaselineAttributeInformation.Builder() - .setLazy( lazyAvailable && property.isLazy() ) + .setLazy( lazy ) .setInsertable( property.isInsertable() ) .setUpdateable( property.isUpdateable() ) .setValueGenerationStrategy( property.getValueGenerationStrategy() ) @@ -197,7 +204,7 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( property.getName(), (CompositeType) type, new BaselineAttributeInformation.Builder() - .setLazy( lazyAvailable && property.isLazy() ) + .setLazy( lazy ) .setInsertable( property.isInsertable() ) .setUpdateable( property.isUpdateable() ) .setValueGenerationStrategy( property.getValueGenerationStrategy() ) @@ -219,7 +226,7 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( property.getName(), (AssociationType) type, new BaselineAttributeInformation.Builder() - .setLazy( lazyAvailable && property.isLazy() ) + .setLazy( lazy ) .setInsertable( property.isInsertable() ) .setUpdateable( property.isUpdateable() ) .setValueGenerationStrategy( property.getValueGenerationStrategy() ) @@ -279,7 +286,9 @@ public static StandardProperty buildStandardProperty(Property property, boolean return new StandardProperty( property.getName(), type, - lazyAvailable && property.isLazy(), + // only called for embeddable sub-attributes which are never (yet) lazy + //lazyAvailable && property.isLazy(), + false, property.isInsertable(), property.isUpdateable(), property.getValueGenerationStrategy(), diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java b/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java index 953cc19f5d83ec3755d107556df1f7744a5091aa..f712fa4d6fc0517211265df3b5e3a9c349721209 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java @@ -92,7 +92,6 @@ public AttributeDefinition next() { // we build the association-key here because of the "goofiness" with 'currentColumnPosition' final AssociationKey associationKey; final AssociationType aType = (AssociationType) type; - final Joinable joinable = aType.getAssociatedJoinable( sessionFactory() ); if ( aType.isAnyType() ) { associationKey = new AssociationKey( @@ -111,6 +110,8 @@ public AttributeDefinition next() { ); } else if ( aType.getForeignKeyDirection() == ForeignKeyDirection.FROM_PARENT ) { + final Joinable joinable = aType.getAssociatedJoinable( sessionFactory() ); + final String lhsTableName; final String[] lhsColumnNames; @@ -133,6 +134,8 @@ else if ( aType.getForeignKeyDirection() == ForeignKeyDirection.FROM_PARENT ) { associationKey = new AssociationKey( lhsTableName, lhsColumnNames ); } else { + final Joinable joinable = aType.getAssociatedJoinable( sessionFactory() ); + associationKey = new AssociationKey( joinable.getTableName(), getRHSColumnNames( aType, sessionFactory() ) diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java index 07021458237dcb7f842d6b8ec3fb4f23c0e36f57..5ceb548e2cbeab27c4363c956e9a8e735a16a0e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java @@ -16,6 +16,7 @@ import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; @@ -23,26 +24,24 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.Assigned; -import org.hibernate.internal.CoreMessageLogger; import org.hibernate.loader.PropertyPath; import org.hibernate.mapping.Component; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; +import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.property.access.spi.Getter; import org.hibernate.property.access.spi.Setter; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.ProxyFactory; +import org.hibernate.tuple.IdentifierProperty; import org.hibernate.tuple.Instantiator; -import org.hibernate.tuple.NonIdentifierAttribute; import org.hibernate.type.AssociationType; import org.hibernate.type.ComponentType; import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; -import static org.hibernate.internal.CoreLogging.messageLogger; - /** * Support for tuplizers relating to entities. @@ -51,7 +50,6 @@ * @author Gavin King */ public abstract class AbstractEntityTuplizer implements EntityTuplizer { - private static final CoreMessageLogger LOG = messageLogger( AbstractEntityTuplizer.class ); //TODO: currently keeps Getters and Setters (instead of PropertyAccessors) because of the way getGetter() and getSetter() are implemented currently; yuck! @@ -152,7 +150,8 @@ public AbstractEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass m instantiator = buildInstantiator( entityMetamodel, mappingInfo ); - if ( entityMetamodel.isLazy() && !entityMetamodel.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { +// if ( entityMetamodel.isLazy() && !entityMetamodel.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { + if ( entityMetamodel.isLazy() ) { proxyFactory = buildProxyFactory( mappingInfo, idGetter, idSetter ); if ( proxyFactory == null ) { entityMetamodel.setLazy( false ); @@ -209,7 +208,7 @@ public Serializable getIdentifier(Object entity, SharedSessionContractImplemento id = entity; } else if ( HibernateProxy.class.isInstance( entity ) ) { - id = ( (HibernateProxy) entity ).getHibernateLazyInitializer().getIdentifier(); + id = ( (HibernateProxy) entity ).getHibernateLazyInitializer().getInternalIdentifier(); } else { if ( idGetter == null ) { @@ -384,6 +383,7 @@ public void setIdentifier(Object entity, Serializable id, EntityMode entityMode, final Object[] extractedValues = mappedIdentifierType.getPropertyValues( id, entityMode ); final Object[] injectionValues = new Object[extractedValues.length]; final PersistenceContext persistenceContext = session.getPersistenceContext(); + final MetamodelImplementor metamodel = sessionFactory.getMetamodel(); for ( int i = 0; i < virtualIdComponent.getSubtypes().length; i++ ) { final Type virtualPropertyType = virtualIdComponent.getSubtypes()[i]; final Type idClassPropertyType = mappedIdentifierType.getSubtypes()[i]; @@ -396,7 +396,7 @@ public void setIdentifier(Object entity, Serializable id, EntityMode entityMode, final String associatedEntityName = ( (EntityType) virtualPropertyType ).getAssociatedEntityName(); final EntityKey entityKey = session.generateEntityKey( (Serializable) extractedValues[i], - sessionFactory.getMetamodel().entityPersister( associatedEntityName ) + metamodel.entityPersister( associatedEntityName ) ); // it is conceivable there is a proxy, so check that first Object association = persistenceContext.getProxy( entityKey ); @@ -405,7 +405,7 @@ public void setIdentifier(Object entity, Serializable id, EntityMode entityMode, association = persistenceContext.getEntity( entityKey ); if ( association == null ) { // get the association out of the entity itself - association = sessionFactory.getMetamodel().entityPersister( entityName ).getPropertyValue( + association = metamodel.entityPersister( entityName ).getPropertyValue( entity, virtualIdComponent.getPropertyNames()[i] ); @@ -432,7 +432,7 @@ private static Serializable determineEntityId( if ( HibernateProxy.class.isInstance( entity ) ) { // entity is a proxy, so we know it is not transient; just return ID from proxy - return ( (HibernateProxy) entity ).getHibernateLazyInitializer().getIdentifier(); + return ( (HibernateProxy) entity ).getHibernateLazyInitializer().getInternalIdentifier(); } if ( session != null ) { @@ -462,13 +462,14 @@ private static EntityPersister resolveEntityPersister( if ( session != null ) { return session.getEntityPersister( - associationType.getAssociatedEntityName( session.getFactory() ), + associationType.getAssociatedEntityName( sessionFactory ), entity ); } String entityName = null; - for ( EntityNameResolver entityNameResolver : sessionFactory.getMetamodel().getEntityNameResolvers() ) { + final MetamodelImplementor metamodel = sessionFactory.getMetamodel(); + for ( EntityNameResolver entityNameResolver : metamodel.getEntityNameResolvers() ) { entityName = entityNameResolver.resolveEntityName( entity ); if ( entityName != null ) { break; @@ -479,7 +480,7 @@ private static EntityPersister resolveEntityPersister( entityName = entity.getClass().getName(); } - return sessionFactory.getMetamodel().entityPersister( entityName ); + return metamodel.entityPersister( entityName ); } @Override @@ -496,11 +497,12 @@ public void resetIdentifier( Object currentVersion, SharedSessionContractImplementor session) { //noinspection StatementWithEmptyBody - if ( entityMetamodel.getIdentifierProperty().getIdentifierGenerator() instanceof Assigned ) { + final IdentifierProperty identifierProperty = entityMetamodel.getIdentifierProperty(); + if ( identifierProperty.getIdentifierGenerator() instanceof Assigned ) { } else { //reset the id - Serializable result = entityMetamodel.getIdentifierProperty() + Serializable result = identifierProperty .getUnsavedValue() .getDefaultValue( currentId ); setIdentifier( entity, result, session ); @@ -525,28 +527,37 @@ public Object getVersion(Object entity) throws HibernateException { } protected boolean shouldGetAllProperties(Object entity) { - if ( !getEntityMetamodel().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { + final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = getEntityMetamodel().getBytecodeEnhancementMetadata(); + if ( !bytecodeEnhancementMetadata.isEnhancedForLazyLoading() ) { return true; } - return !getEntityMetamodel().getBytecodeEnhancementMetadata().hasUnFetchedAttributes( entity ); + return !bytecodeEnhancementMetadata.hasUnFetchedAttributes( entity ); } @Override public Object[] getPropertyValues(Object entity) { final BytecodeEnhancementMetadata enhancementMetadata = entityMetamodel.getBytecodeEnhancementMetadata(); + final LazyAttributesMetadata lazyAttributesMetadata = enhancementMetadata.getLazyAttributesMetadata(); + final int span = entityMetamodel.getPropertySpan(); + final String[] propertyNames = entityMetamodel.getPropertyNames(); final Object[] result = new Object[span]; for ( int j = 0; j < span; j++ ) { - NonIdentifierAttribute property = entityMetamodel.getProperties()[j]; - if ( !property.isLazy() || enhancementMetadata.isAttributeLoaded( entity, property.getName() ) ) { + final String propertyName = propertyNames[j]; + // if the attribute is not lazy (bytecode sense), we can just use the value from the instance + // if the attribute is lazy but has been initialized we can just use the value from the instance + // todo : there should be a third case here when we merge transient instances + if ( ! lazyAttributesMetadata.isLazyAttribute( propertyName ) + || enhancementMetadata.isAttributeLoaded( entity, propertyName) ) { result[j] = getters[j].get( entity ); } else { result[j] = LazyPropertyInitializer.UNFETCHED_PROPERTY; } } + return result; } @@ -644,9 +655,10 @@ private int findSubPropertyIndex(ComponentType type, String subPropertyName) { public void setPropertyValues(Object entity, Object[] values) throws HibernateException { boolean setAll = !entityMetamodel.hasLazyProperties(); + final SessionFactoryImplementor factory = getFactory(); for ( int j = 0; j < entityMetamodel.getPropertySpan(); j++ ) { if ( setAll || values[j] != LazyPropertyInitializer.UNFETCHED_PROPERTY ) { - setters[j].set( entity, values[j], getFactory() ); + setters[j].set( entity, values[j], factory ); } } } @@ -718,7 +730,8 @@ protected final Instantiator getInstantiator() { return instantiator; } - protected final ProxyFactory getProxyFactory() { + @Override + public final ProxyFactory getProxyFactory() { return proxyFactory; } @@ -734,8 +747,9 @@ public Getter getIdentifierGetter() { @Override public Getter getVersionGetter() { - if ( getEntityMetamodel().isVersioned() ) { - return getGetter( getEntityMetamodel().getVersionPropertyIndex() ); + final EntityMetamodel entityMetamodel = getEntityMetamodel(); + if ( entityMetamodel.isVersioned() ) { + return getGetter( entityMetamodel.getVersionPropertyIndex() ); } return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java index 10286c23a615f7d98e0f80638bef1562c1e909ef..4d444391051d27474df9b6a6929c94c18bb758ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataNonPojoImpl.java @@ -6,10 +6,14 @@ */ package org.hibernate.tuple.entity; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; 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.EntityKey; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; /** @@ -44,15 +48,42 @@ public LazyAttributesMetadata getLazyAttributesMetadata() { @Override public LazyAttributeLoadingInterceptor injectInterceptor( Object entity, + Object identifier, SharedSessionContractImplementor session) throws NotInstrumentedException { throw new NotInstrumentedException( errorMsg ); } + @Override + public void injectInterceptor( + Object entity, + PersistentAttributeInterceptor interceptor, + SharedSessionContractImplementor session) { + throw new NotInstrumentedException( errorMsg ); + } + + @Override + public void injectEnhancedEntityAsProxyInterceptor( + Object entity, + EntityKey entityKey, + SharedSessionContractImplementor session) { + throw new NotInstrumentedException( errorMsg ); + } + + @Override + public PersistentAttributeInterceptable createEnhancedProxy(EntityKey keyToLoad, boolean addEmptyEntry, SharedSessionContractImplementor session) { + throw new NotInstrumentedException( errorMsg ); + } + @Override public LazyAttributeLoadingInterceptor extractInterceptor(Object entity) throws NotInstrumentedException { throw new NotInstrumentedException( errorMsg ); } + @Override + public BytecodeLazyAttributeInterceptor extractLazyInterceptor(Object entity) throws NotInstrumentedException { + throw new NotInstrumentedException( errorMsg ); + } + @Override public boolean hasUnFetchedAttributes(Object entity) { return false; diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java index 48134c0ceeb464265ca422d7ff36e711bd650834..cad2b64c9fadcece0f1e565b95d0599cda26e406 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java @@ -6,29 +6,49 @@ */ package org.hibernate.tuple.entity; +import java.io.Serializable; +import java.util.Set; + +import org.hibernate.LockMode; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; 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.EntityKey; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; 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; /** * @author Steve Ebersole */ -public class BytecodeEnhancementMetadataPojoImpl implements BytecodeEnhancementMetadata { - public static BytecodeEnhancementMetadata from(PersistentClass persistentClass) { +public final class BytecodeEnhancementMetadataPojoImpl implements BytecodeEnhancementMetadata { + /** + * Static constructor + */ + public static BytecodeEnhancementMetadata from( + PersistentClass persistentClass, + Set identifierAttributeNames, + CompositeType nonAggregatedCidMapper, + boolean allowEnhancementAsProxy) { final Class mappedClass = persistentClass.getMappedClass(); final boolean enhancedForLazyLoading = PersistentAttributeInterceptable.class.isAssignableFrom( mappedClass ); final LazyAttributesMetadata lazyAttributesMetadata = enhancedForLazyLoading - ? LazyAttributesMetadata.from( persistentClass ) + ? LazyAttributesMetadata.from( persistentClass, true, allowEnhancementAsProxy ) : LazyAttributesMetadata.nonEnhanced( persistentClass.getEntityName() ); return new BytecodeEnhancementMetadataPojoImpl( persistentClass.getEntityName(), mappedClass, + identifierAttributeNames, + nonAggregatedCidMapper, enhancedForLazyLoading, lazyAttributesMetadata ); @@ -36,16 +56,26 @@ public static BytecodeEnhancementMetadata from(PersistentClass persistentClass) private final String entityName; private final Class entityClass; + private final Set identifierAttributeNames; + private final CompositeType nonAggregatedCidMapper; private final boolean enhancedForLazyLoading; private final LazyAttributesMetadata lazyAttributesMetadata; - public BytecodeEnhancementMetadataPojoImpl( + @SuppressWarnings("WeakerAccess") + protected BytecodeEnhancementMetadataPojoImpl( String entityName, Class entityClass, + Set identifierAttributeNames, + CompositeType nonAggregatedCidMapper, boolean enhancedForLazyLoading, LazyAttributesMetadata lazyAttributesMetadata) { + this.nonAggregatedCidMapper = nonAggregatedCidMapper; + assert identifierAttributeNames != null; + assert !identifierAttributeNames.isEmpty(); + this.entityName = entityName; this.entityClass = entityClass; + this.identifierAttributeNames = identifierAttributeNames; this.enhancedForLazyLoading = enhancedForLazyLoading; this.lazyAttributesMetadata = lazyAttributesMetadata; } @@ -67,18 +97,87 @@ public LazyAttributesMetadata getLazyAttributesMetadata() { @Override public boolean hasUnFetchedAttributes(Object entity) { - LazyAttributeLoadingInterceptor interceptor = enhancedForLazyLoading ? extractInterceptor( entity ) : null; - return interceptor != null && interceptor.hasAnyUninitializedAttributes(); + if ( ! enhancedForLazyLoading ) { + return false; + } + + final BytecodeLazyAttributeInterceptor interceptor = extractLazyInterceptor( entity ); + if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { + return ( (LazyAttributeLoadingInterceptor) interceptor ).hasAnyUninitializedAttributes(); + } + + //noinspection RedundantIfStatement + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + return true; + } + + return false; } @Override public boolean isAttributeLoaded(Object entity, String attributeName) { - LazyAttributeLoadingInterceptor interceptor = enhancedForLazyLoading ? extractInterceptor( entity ) : null; - return interceptor == null || interceptor.isAttributeLoaded( attributeName ); + if ( ! enhancedForLazyLoading ) { + return true; + } + + final BytecodeLazyAttributeInterceptor interceptor = extractLazyInterceptor( entity ); + if ( interceptor instanceof LazyAttributeLoadingInterceptor ) { + return ( (LazyAttributeLoadingInterceptor) interceptor ).isAttributeLoaded( attributeName ); + } + + return true; } @Override public LazyAttributeLoadingInterceptor extractInterceptor(Object entity) throws NotInstrumentedException { + return (LazyAttributeLoadingInterceptor) extractLazyInterceptor( entity ); + } + + @Override + public PersistentAttributeInterceptable createEnhancedProxy(EntityKey entityKey, boolean addEmptyEntry, SharedSessionContractImplementor session) { + final EntityPersister persister = entityKey.getPersister(); + final Serializable identifier = entityKey.getIdentifier(); + final PersistenceContext persistenceContext = session.getPersistenceContext(); + + // first, instantiate the entity instance to use as the proxy + final PersistentAttributeInterceptable entity = (PersistentAttributeInterceptable) persister.getEntityTuplizer().instantiate( identifier, session ); + + // add the entity (proxy) instance to the PC + persistenceContext.addEnhancedProxy( entityKey, entity ); + + // if requested, add the "holder entry" to the PC + if ( addEmptyEntry ) { + persistenceContext.addEntry( + entity, + Status.MANAGED, + // loaded state + null, + // row-id + null, + identifier, + // version + null, + LockMode.NONE, + // we assume it exists in db + true, + persister, + true + ); + } + + // inject the interceptor + persister.getEntityMetamodel() + .getBytecodeEnhancementMetadata() + .injectEnhancedEntityAsProxyInterceptor( entity, entityKey, session ); + + return entity; + } + + @Override + public LazyAttributeLoadingInterceptor injectInterceptor( + Object entity, + Object identifier, + SharedSessionContractImplementor session) { if ( !enhancedForLazyLoading ) { throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + "] is not enhanced for lazy loading" ); } @@ -92,17 +191,41 @@ public LazyAttributeLoadingInterceptor extractInterceptor(Object entity) throws ) ); } + final LazyAttributeLoadingInterceptor interceptor = new LazyAttributeLoadingInterceptor( + getEntityName(), + identifier, + lazyAttributesMetadata.getLazyAttributeNames(), + session + ); - final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); - if ( interceptor == null ) { - return null; - } + injectInterceptor( entity, interceptor, session ); - return (LazyAttributeLoadingInterceptor) interceptor; + return interceptor; } @Override - public LazyAttributeLoadingInterceptor injectInterceptor(Object entity, SharedSessionContractImplementor session) { + public void injectEnhancedEntityAsProxyInterceptor( + Object entity, + EntityKey entityKey, + SharedSessionContractImplementor session) { + injectInterceptor( + entity, + new EnhancementAsProxyLazinessInterceptor( + entityName, + identifierAttributeNames, + nonAggregatedCidMapper, + entityKey, + session + ), + session + ); + } + + @Override + public void injectInterceptor( + Object entity, + PersistentAttributeInterceptor interceptor, + SharedSessionContractImplementor session) { if ( !enhancedForLazyLoading ) { throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + "] is not enhanced for lazy loading" ); } @@ -117,12 +240,31 @@ public LazyAttributeLoadingInterceptor injectInterceptor(Object entity, SharedSe ); } - final LazyAttributeLoadingInterceptor interceptor = new LazyAttributeLoadingInterceptor( - getEntityName(), - lazyAttributesMetadata.getLazyAttributeNames(), - session - ); ( (PersistentAttributeInterceptable) entity ).$$_hibernate_setInterceptor( interceptor ); - return interceptor; } + + @Override + public BytecodeLazyAttributeInterceptor extractLazyInterceptor(Object entity) throws NotInstrumentedException { + if ( !enhancedForLazyLoading ) { + throw new NotInstrumentedException( "Entity class [" + entityClass.getName() + "] is not enhanced for lazy loading" ); + } + + if ( !entityClass.isInstance( entity ) ) { + throw new IllegalArgumentException( + String.format( + "Passed entity instance [%s] is not of expected type [%s]", + entity, + getEntityName() + ) + ); + } + + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + if ( interceptor == null ) { + return null; + } + + return (BytecodeLazyAttributeInterceptor) interceptor; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index 98d34aba73f349514dd3431346b8ee95efb6466b..2054723538239ac3275cfb52de6a8f5d3a0dbea8 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java @@ -8,6 +8,7 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -18,6 +19,7 @@ import org.hibernate.EntityMode; import org.hibernate.HibernateException; import org.hibernate.MappingException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.engine.OptimisticLockStyle; @@ -28,8 +30,10 @@ import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.mapping.Component; +import org.hibernate.mapping.ManyToOne; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; +import org.hibernate.mapping.Value; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.tuple.GenerationTiming; import org.hibernate.tuple.IdentifierProperty; @@ -139,7 +143,30 @@ public EntityMetamodel( versioned = persistentClass.isVersioned(); if ( persistentClass.hasPojoRepresentation() ) { - bytecodeEnhancementMetadata = BytecodeEnhancementMetadataPojoImpl.from( persistentClass ); + final Component identifierMapperComponent = persistentClass.getIdentifierMapper(); + final CompositeType nonAggregatedCidMapper; + final Set idAttributeNames; + + if ( identifierMapperComponent != null ) { + nonAggregatedCidMapper = (CompositeType) identifierMapperComponent.getType(); + idAttributeNames = new HashSet<>( ); + //noinspection unchecked + final Iterator propertyItr = identifierMapperComponent.getPropertyIterator(); + while ( propertyItr.hasNext() ) { + idAttributeNames.add( propertyItr.next().getName() ); + } + } + else { + nonAggregatedCidMapper = null; + idAttributeNames = Collections.singleton( identifierAttribute.getName() ); + } + + bytecodeEnhancementMetadata = BytecodeEnhancementMetadataPojoImpl.from( + persistentClass, + idAttributeNames, + nonAggregatedCidMapper, + sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled() + ); } else { bytecodeEnhancementMetadata = new BytecodeEnhancementMetadataNonPojoImpl( persistentClass.getEntityName() ); @@ -206,6 +233,7 @@ public EntityMetamodel( } if ( prop.isNaturalIdentifier() ) { + verifyNaturalIdProperty( prop ); naturalIdNumbers.add( i ); if ( prop.isUpdateable() ) { foundUpdateableNaturalIdProperty = true; @@ -217,10 +245,16 @@ public EntityMetamodel( } // temporary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - boolean lazy = prop.isLazy() && bytecodeEnhancementMetadata.isEnhancedForLazyLoading(); + boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( + prop, + bytecodeEnhancementMetadata.isEnhancedForLazyLoading(), + sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled() + ); + if ( lazy ) { hasLazy = true; } + propertyLaziness[i] = lazy; propertyNames[i] = properties[i].getName(); @@ -627,6 +661,31 @@ else if ( hadInDatabaseGeneration ) { } } + private void verifyNaturalIdProperty(Property property) { + final Value value = property.getValue(); + if ( value instanceof ManyToOne ) { + final ManyToOne toOne = (ManyToOne) value; + if ( toOne.isIgnoreNotFound() ) { + throw new MappingException( + "Attribute marked as natural-id can not also be a not-found association - " + + propertyName( property ) + ); + } + } + else if ( value instanceof Component ) { + final Component component = (Component) value; + //noinspection unchecked + final Iterator properties = component.getPropertyIterator(); + while ( properties.hasNext() ) { + verifyNaturalIdProperty( properties.next() ); + } + } + } + + private String propertyName(Property property) { + return getName() + "." + property.getName(); + } + private static class NoInMemoryValueGenerationStrategy implements InMemoryValueGenerationStrategy { /** * Singleton access diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java index ecb6970cbcd5c25196392a43508321a587e1b8ad..f9a79e2e8c1d4699efb46e918b9a57730e83711f 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityTuplizer.java @@ -15,6 +15,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.property.access.spi.Getter; +import org.hibernate.proxy.ProxyFactory; import org.hibernate.tuple.Tuplizer; /** @@ -273,4 +274,8 @@ Object[] getPropertyValuesToInsert(Object entity, Map mergeMap, SharedSessionCon * @return The getter for the version property. */ Getter getVersionGetter(); + + default ProxyFactory getProxyFactory() { + return null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityInstantiator.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityInstantiator.java index 47bd979d661298f4bfe77fba04d611a68efeb287..0910b6d9aea31a2712d6106f26304a5ad4cbb0b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityInstantiator.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityInstantiator.java @@ -46,6 +46,7 @@ protected Object applyInterception(Object entity) { PersistentAttributeInterceptor interceptor = new LazyAttributeLoadingInterceptor( entityMetamodel.getName(), + null, entityMetamodel.getBytecodeEnhancementMetadata() .getLazyAttributesMetadata() .getLazyAttributeNames(), diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java index f186e8a384c2eeca090547c63d56386551d4dde6..a8885c95cd603517c3336140c425b9747e40bc43 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/PojoEntityTuplizer.java @@ -7,16 +7,14 @@ package org.hibernate.tuple.entity; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Iterator; import java.util.Map; import java.util.Set; import org.hibernate.EntityMode; import org.hibernate.EntityNameResolver; import org.hibernate.HibernateException; -import org.hibernate.MappingException; -import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.spi.ReflectionOptimizer; import org.hibernate.cfg.Environment; import org.hibernate.classic.Lifecycle; @@ -26,14 +24,12 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.ReflectHelper; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; -import org.hibernate.mapping.Subclass; import org.hibernate.property.access.spi.Getter; import org.hibernate.property.access.spi.Setter; -import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.ProxyFactory; +import org.hibernate.proxy.pojo.ProxyFactoryHelper; import org.hibernate.tuple.Instantiator; import org.hibernate.type.CompositeType; @@ -51,15 +47,11 @@ public class PojoEntityTuplizer extends AbstractEntityTuplizer { private final boolean lifecycleImplementor; private final ReflectionOptimizer optimizer; - private final boolean isBytecodeEnhanced; - - public PojoEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) { super( entityMetamodel, mappedEntity ); this.mappedClass = mappedEntity.getMappedClass(); this.proxyInterface = mappedEntity.getProxyInterface(); this.lifecycleImplementor = Lifecycle.class.isAssignableFrom( mappedClass ); - this.isBytecodeEnhanced = entityMetamodel.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); String[] getterNames = new String[propertySpan]; String[] setterNames = new String[propertySpan]; @@ -91,76 +83,25 @@ public PojoEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappe protected ProxyFactory buildProxyFactory(PersistentClass persistentClass, Getter idGetter, Setter idSetter) { // determine the id getter and setter methods from the proxy interface (if any) // determine all interfaces needed by the resulting proxy - - /* - * We need to preserve the order of the interfaces they were put into the set, since javassist will choose the - * first one's class-loader to construct the proxy class with. This is also the reason why HibernateProxy.class - * should be the last one in the order (on JBossAS7 its class-loader will be org.hibernate module's class- - * loader, which will not see the classes inside deployed apps. See HHH-3078 - */ - Set proxyInterfaces = new java.util.LinkedHashSet(); - - Class mappedClass = persistentClass.getMappedClass(); - Class proxyInterface = persistentClass.getProxyInterface(); - - if ( proxyInterface != null && !mappedClass.equals( proxyInterface ) ) { - if ( !proxyInterface.isInterface() ) { - throw new MappingException( - "proxy must be either an interface, or the class itself: " + getEntityName() - ); - } - proxyInterfaces.add( proxyInterface ); - } + final String entityName = getEntityName(); + final Class mappedClass = persistentClass.getMappedClass(); + final Class proxyInterface = persistentClass.getProxyInterface(); - if ( mappedClass.isInterface() ) { - proxyInterfaces.add( mappedClass ); - } - - Iterator subclasses = persistentClass.getSubclassIterator(); - while ( subclasses.hasNext() ) { - final Subclass subclass = subclasses.next(); - final Class subclassProxy = subclass.getProxyInterface(); - final Class subclassClass = subclass.getMappedClass(); - if ( subclassProxy != null && !subclassClass.equals( subclassProxy ) ) { - if ( !subclassProxy.isInterface() ) { - throw new MappingException( - "proxy must be either an interface, or the class itself: " + subclass.getEntityName() - ); - } - proxyInterfaces.add( subclassProxy ); - } - } + final Set proxyInterfaces = ProxyFactoryHelper.extractProxyInterfaces( persistentClass, entityName ); - proxyInterfaces.add( HibernateProxy.class ); + Method proxyGetIdentifierMethod = ProxyFactoryHelper.extractProxyGetIdentifierMethod( idGetter, proxyInterface ); + Method proxySetIdentifierMethod = ProxyFactoryHelper.extractProxySetIdentifierMethod( idSetter, proxyInterface ); - Iterator properties = persistentClass.getPropertyIterator(); - Class clazz = persistentClass.getMappedClass(); - while ( properties.hasNext() ) { - Property property = (Property) properties.next(); - Method method = property.getGetter( clazz ).getMethod(); - if ( method != null && Modifier.isFinal( method.getModifiers() ) ) { - LOG.gettersOfLazyClassesCannotBeFinal( persistentClass.getEntityName(), property.getName() ); - } - method = property.getSetter( clazz ).getMethod(); - if ( method != null && Modifier.isFinal( method.getModifiers() ) ) { - LOG.settersOfLazyClassesCannotBeFinal( persistentClass.getEntityName(), property.getName() ); - } - } + ProxyFactory pf = buildProxyFactoryInternal( persistentClass, idGetter, idSetter ); + try { - Method idGetterMethod = idGetter == null ? null : idGetter.getMethod(); - Method idSetterMethod = idSetter == null ? null : idSetter.getMethod(); + ProxyFactoryHelper.validateGetterSetterMethodProxyability( "Getter", proxyGetIdentifierMethod ); + ProxyFactoryHelper.validateGetterSetterMethodProxyability( "Setter", proxySetIdentifierMethod ); - Method proxyGetIdentifierMethod = idGetterMethod == null || proxyInterface == null ? - null : - ReflectHelper.getMethod( proxyInterface, idGetterMethod ); - Method proxySetIdentifierMethod = idSetterMethod == null || proxyInterface == null ? - null : - ReflectHelper.getMethod( proxyInterface, idSetterMethod ); + ProxyFactoryHelper.validateProxyability( persistentClass ); - ProxyFactory pf = buildProxyFactoryInternal( persistentClass, idGetter, idSetter ); - try { pf.postInstantiate( - getEntityName(), + entityName, mappedClass, proxyInterfaces, proxyGetIdentifierMethod, @@ -171,7 +112,7 @@ protected ProxyFactory buildProxyFactory(PersistentClass persistentClass, Getter ); } catch (HibernateException he) { - LOG.unableToCreateProxyFactory( getEntityName(), he ); + LOG.unableToCreateProxyFactory( entityName, he ); pf = null; } return pf; @@ -268,22 +209,14 @@ public Class getConcreteProxyClass() { @Override public void afterInitialize(Object entity, SharedSessionContractImplementor session) { - - // moving to multiple fetch groups, the idea of `lazyPropertiesAreUnfetched` really - // needs to become either: - // 1) the names of all un-fetched fetch groups - // 2) the names of all fetched fetch groups - // probably (2) is best - // - // ultimately this comes from EntityEntry, although usage-search seems to show it is never updated there. - // - // also org.hibernate.persister.entity.AbstractEntityPersister.initializeLazyPropertiesFromDatastore() - // needs to be re-worked - if ( entity instanceof PersistentAttributeInterceptable ) { - final LazyAttributeLoadingInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata().extractInterceptor( entity ); - if ( interceptor == null ) { - getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( entity, session ); + final BytecodeLazyAttributeInterceptor interceptor = getEntityMetamodel().getBytecodeEnhancementMetadata().extractLazyInterceptor( entity ); + if ( interceptor == null || interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + getEntityMetamodel().getBytecodeEnhancementMetadata().injectInterceptor( + entity, + getIdentifier( entity, session ), + session + ); } else { if ( interceptor.getLinkedSession() == null ) { 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 eb91251e358f6dcc81755a66c3c1038b1889b764..962545ef860c4600059a0573c91f9d6c1099b2f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java @@ -71,11 +71,7 @@ protected MutabilityPlan getMutabilityPlan() { } protected T getReplacement(T original, T target, SharedSessionContractImplementor session) { - if ( original == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { - return target; - } - else if ( !isMutable() || - ( target != LazyPropertyInitializer.UNFETCHED_PROPERTY && isEqual( original, target ) ) ) { + if ( !isMutable() || ( target != null && isEqual( original, target ) ) ) { return original; } else { @@ -106,13 +102,13 @@ protected static Size getDefaultSize() { protected Size getDictatedSize() { return dictatedSize; } - + // final implementations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public final JavaTypeDescriptor getJavaTypeDescriptor() { return javaTypeDescriptor; } - + public final void setJavaTypeDescriptor( JavaTypeDescriptor javaTypeDescriptor ) { this.javaTypeDescriptor = javaTypeDescriptor; } @@ -351,6 +347,10 @@ public final Type getSemiResolvedType(SessionFactoryImplementor factory) { @Override @SuppressWarnings({ "unchecked" }) public final Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache) { + if ( original == null && target == null ) { + return null; + } + return getReplacement( (T) original, (T) target, session ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/AbstractType.java b/hibernate-core/src/main/java/org/hibernate/type/AbstractType.java index 14adc63b95bac81f71faa3f0e926fc6736b4052d..c61dd34f418be294e1043158e62466cd48feefd7 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractType.java @@ -19,33 +19,39 @@ /** * Abstract superclass of the built in Type hierarchy. - * + * * @author Gavin King */ public abstract class AbstractType implements Type { protected static final Size LEGACY_DICTATED_SIZE = new Size(); protected static final Size LEGACY_DEFAULT_SIZE = new Size( 19, 2, 255, Size.LobMultiplier.NONE ); // to match legacy behavior + @Override public boolean isAssociationType() { return false; } + @Override public boolean isCollectionType() { return false; } + @Override public boolean isComponentType() { return false; } + @Override public boolean isEntityType() { return false; } - + + @Override public int compare(Object x, Object y) { return ( (Comparable) x ).compareTo(y); } + @Override public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { @@ -57,6 +63,7 @@ public Serializable disassemble(Object value, SharedSessionContractImplementor s } } + @Override public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException { if ( cached==null ) { @@ -67,10 +74,12 @@ public Object assemble(Serializable cached, SharedSessionContractImplementor ses } } + @Override public boolean isDirty(Object old, Object current, SharedSessionContractImplementor session) throws HibernateException { return !isSame( old, current ); } + @Override public Object hydrate( ResultSet rs, String[] names, @@ -82,56 +91,67 @@ public Object hydrate( return nullSafeGet(rs, names, session, owner); } + @Override public Object resolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { return value; } - public Object semiResolve(Object value, SharedSessionContractImplementor session, Object owner) + @Override + public Object semiResolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { return value; } - + + @Override public boolean isAnyType() { return false; } + @Override public boolean isModified(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session) throws HibernateException { return isDirty(old, current, session); } - + + @Override public boolean isSame(Object x, Object y) throws HibernateException { return isEqual(x, y ); } + @Override public boolean isEqual(Object x, Object y) { return Objects.equals( x, y ); } - + + @Override public int getHashCode(Object x) { return x.hashCode(); } + @Override public boolean isEqual(Object x, Object y, SessionFactoryImplementor factory) { return isEqual(x, y ); } - + + @Override public int getHashCode(Object x, SessionFactoryImplementor factory) { return getHashCode(x ); } - + + @Override public Type getSemiResolvedType(SessionFactoryImplementor factory) { return this; } + @Override public Object replace( - Object original, - Object target, - SharedSessionContractImplementor session, - Object owner, - Map copyCache, - ForeignKeyDirection foreignKeyDirection) + Object original, + Object target, + SharedSessionContractImplementor session, + Object owner, + Map copyCache, + ForeignKeyDirection foreignKeyDirection) throws HibernateException { boolean include; if ( isAssociationType() ) { @@ -144,6 +164,7 @@ public Object replace( return include ? replace(original, target, session, owner, copyCache) : target; } + @Override public void beforeAssemble(Serializable cached, SharedSessionContractImplementor session) {} /*public Object copy(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache) diff --git a/hibernate-core/src/main/java/org/hibernate/type/ClobType.java b/hibernate-core/src/main/java/org/hibernate/type/ClobType.java index c6caf5934921ef84c597198ef83af3454b2a8f1a..e731c47b8c5a1dbd090ea5e384053c6fd8a59f36 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ClobType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ClobType.java @@ -36,7 +36,6 @@ protected boolean registerUnderJavaType() { @Override protected Clob getReplacement(Clob original, Clob target, SharedSessionContractImplementor session) { - return session.getJdbcServices().getJdbcEnvironment().getDialect().getLobMergeStrategy().mergeClob( original, target, session ); + return session.getJdbcServices().getJdbcEnvironment().getDialect().getLobMergeStrategy().mergeClob( (Clob) original, (Clob) target, session ); } - } diff --git a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java index 428c7bbb0894d3448097deb6f5e330fc19f1453b..2828314c4feaec5a37d4088616702f1e11ce7385 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java @@ -20,7 +20,6 @@ import java.util.SortedMap; import java.util.TreeMap; -import org.hibernate.EntityMode; import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.MappingException; @@ -46,7 +45,6 @@ import org.hibernate.pretty.MessageHelper; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; -import org.hibernate.type.spi.TypeConfiguration; import org.jboss.logging.Logger; @@ -108,8 +106,12 @@ public boolean isCollectionType() { @Override public final boolean isEqual(Object x, Object y) { return x == y - || ( x instanceof PersistentCollection && ( (PersistentCollection) x ).wasInitialized() && ( (PersistentCollection) x ).isWrapper( y ) ) - || ( y instanceof PersistentCollection && ( (PersistentCollection) y ).wasInitialized() && ( (PersistentCollection) y ).isWrapper( x ) ); + || ( x instanceof PersistentCollection && isEqual( (PersistentCollection) x, y ) ) + || ( y instanceof PersistentCollection && isEqual( (PersistentCollection) y, x ) ); + } + + private boolean isEqual(PersistentCollection x, Object y) { + return x.wasInitialized() && ( x.isWrapper( y ) || x.isDirectlyProvidedCollection( y ) ); } @Override @@ -454,7 +456,7 @@ public Object hydrate(ResultSet rs, String[] name, SharedSessionContractImplemen public Object resolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { - return resolve(value, session, owner, null); + return resolve( value, session, owner, null ); } @Override @@ -681,8 +683,23 @@ public Object replace( } if ( !Hibernate.isInitialized( original ) ) { if ( ( (PersistentCollection) original ).hasQueuedOperations() ) { - final AbstractPersistentCollection pc = (AbstractPersistentCollection) original; - pc.replaceQueuedOperationValues( getPersister( session ), copyCache ); + if ( original == target ) { + // A managed entity with an uninitialized collection is being merged, + // We need to replace any detached entities in the queued operations + // with managed copies. + final AbstractPersistentCollection pc = (AbstractPersistentCollection) original; + pc.replaceQueuedOperationValues( getPersister( session ), copyCache ); + } + else { + // original is a detached copy of the collection; + // it contains queued operations, which will be ignored + LOG.ignoreQueuedOperationsOnMerge( + MessageHelper.collectionInfoString( + getRole(), + ( (PersistentCollection) original ).getKey() + ) + ); + } } return target; } @@ -754,21 +771,22 @@ public String getOnCondition( */ public Object getCollection(Serializable key, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) { - CollectionPersister persister = getPersister( session ); + final CollectionPersister persister = getPersister( session ); final PersistenceContext persistenceContext = session.getPersistenceContext(); - final EntityMode entityMode = persister.getOwnerEntityPersister().getEntityMode(); + final CollectionKey collectionKey = new CollectionKey( persister, key ); // check if collection is currently being loaded - PersistentCollection collection = persistenceContext.getLoadContexts().locateLoadingCollection( persister, key ); + PersistentCollection collection = persistenceContext.getLoadContexts() + .locateLoadingCollection( persister, collectionKey ); if ( collection == null ) { // check if it is already completely loaded, but unowned - collection = persistenceContext.useUnownedCollection( new CollectionKey(persister, key, entityMode) ); + collection = persistenceContext.useUnownedCollection( collectionKey ); if ( collection == null ) { - collection = persistenceContext.getCollection( new CollectionKey(persister, key, entityMode) ); + collection = persistenceContext.getCollection( collectionKey ); if ( collection == null ) { // create a new collection wrapper, to be initialized later @@ -788,21 +806,21 @@ else if ( eager ) { } if ( hasHolder() ) { - session.getPersistenceContext().addCollectionHolder( collection ); + persistenceContext.addCollectionHolder( collection ); } - } + if ( LOG.isTraceEnabled() ) { + LOG.tracef( "Created collection wrapper: %s", + MessageHelper.collectionInfoString( persister, collection, + key, session ) ); + } + // we have already set the owner so we can just return the value + return collection.getValue(); + } } - - if ( LOG.isTraceEnabled() ) { - LOG.tracef( "Created collection wrapper: %s", - MessageHelper.collectionInfoString( persister, collection, - key, session ) ); - } - } - collection.setOwner(owner); + collection.setOwner( owner ); return collection.getValue(); } 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 bb078f511fc68d6ba444b5a2623a141e3fd0c784..81c9cf038eb539ac56a3ba3206693a4e7f789edf 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java @@ -369,7 +369,7 @@ public int getHashCode(Object x, SessionFactoryImplementor factory) { final Serializable id; if ( x instanceof HibernateProxy ) { - id = ( (HibernateProxy) x ).getHibernateLazyInitializer().getIdentifier(); + id = ( (HibernateProxy) x ).getHibernateLazyInitializer().getInternalIdentifier(); } else { final Class mappedClass = persister.getMappedClass(); @@ -399,7 +399,7 @@ public boolean isEqual(Object x, Object y, SessionFactoryImplementor factory) { Serializable xid; if ( x instanceof HibernateProxy ) { xid = ( (HibernateProxy) x ).getHibernateLazyInitializer() - .getIdentifier(); + .getInternalIdentifier(); } else { if ( mappedClass.isAssignableFrom( x.getClass() ) ) { @@ -414,7 +414,7 @@ public boolean isEqual(Object x, Object y, SessionFactoryImplementor factory) { Serializable yid; if ( y instanceof HibernateProxy ) { yid = ( (HibernateProxy) y ).getHibernateLazyInitializer() - .getIdentifier(); + .getInternalIdentifier(); } else { if ( mappedClass.isAssignableFrom( y.getClass() ) ) { @@ -546,7 +546,7 @@ public String toLoggableString(Object value, SessionFactoryImplementor factory) final Serializable id; if ( value instanceof HibernateProxy ) { HibernateProxy proxy = (HibernateProxy) value; - id = proxy.getHibernateLazyInitializer().getIdentifier(); + id = proxy.getHibernateLazyInitializer().getInternalIdentifier(); } else { id = persister.getIdentifier( value ); @@ -659,7 +659,7 @@ public final String getIdentifierOrUniqueKeyPropertyName(Mapping factory) } } - protected abstract boolean isNullable(); + public abstract boolean isNullable(); /** * Resolve an identifier via a load. diff --git a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java index d0b162f0d5d9e3a05464a03fd37bf6095d6536ef..473e4dc4077ce62ac5a4c1bab8012ffaf5a363c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java @@ -109,7 +109,7 @@ public ManyToOneType(ManyToOneType original, String superTypeEntityName) { } @Override - protected boolean isNullable() { + public boolean isNullable() { return ignoreNotFound; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/NClobType.java b/hibernate-core/src/main/java/org/hibernate/type/NClobType.java index 71f5921f9761c3b8d7db070d98e77ba681e8b666..bf736ce373760b13203e6af93e1382e2cc1ba8e4 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/NClobType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/NClobType.java @@ -12,7 +12,7 @@ import org.hibernate.type.descriptor.java.NClobTypeDescriptor; /** - * A type that maps between {@link java.sql.Types#CLOB CLOB} and {@link java.sql.Clob} + * A type that maps between {@link java.sql.Types#NCLOB NCLOB} and {@link java.sql.NClob} * * @author Gavin King * @author Steve Ebersole @@ -38,5 +38,4 @@ protected boolean registerUnderJavaType() { protected NClob getReplacement(NClob original, NClob target, SharedSessionContractImplementor session) { return session.getJdbcServices().getJdbcEnvironment().getDialect().getLobMergeStrategy().mergeNClob( original, target, session ); } - } diff --git a/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java index 511416994d3ab5d45842f08e0b8ab9a0107706b0..53c058e8d084db266b25e780fa73878793c2a179 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java @@ -29,9 +29,10 @@ public class OneToOneType extends EntityType { private final ForeignKeyDirection foreignKeyType; private final String propertyName; private final String entityName; + private final boolean constrained; /** - * @deprecated Use {@link #OneToOneType(TypeFactory.TypeScope, String, ForeignKeyDirection, boolean, String, boolean, boolean, String, String)} + * @deprecated Use {@link #OneToOneType(TypeFactory.TypeScope, String, ForeignKeyDirection, boolean, String, boolean, boolean, String, String, boolean)} * instead. */ @Deprecated @@ -47,6 +48,11 @@ public OneToOneType( this( scope, referencedEntityName, foreignKeyType, uniqueKeyPropertyName == null, uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName ); } + /** + * @deprecated Use {@link #OneToOneType(TypeFactory.TypeScope, String, ForeignKeyDirection, boolean, String, boolean, boolean, String, String, boolean)} + * instead. + */ + @Deprecated public OneToOneType( TypeFactory.TypeScope scope, String referencedEntityName, @@ -57,10 +63,25 @@ public OneToOneType( boolean unwrapProxy, String entityName, String propertyName) { + this( scope, referencedEntityName, foreignKeyType, referenceToPrimaryKey, uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName, foreignKeyType != ForeignKeyDirection.TO_PARENT ); + } + + public OneToOneType( + TypeFactory.TypeScope scope, + String referencedEntityName, + ForeignKeyDirection foreignKeyType, + boolean referenceToPrimaryKey, + String uniqueKeyPropertyName, + boolean lazy, + boolean unwrapProxy, + String entityName, + String propertyName, + boolean constrained) { super( scope, referencedEntityName, referenceToPrimaryKey, uniqueKeyPropertyName, !lazy, unwrapProxy ); this.foreignKeyType = foreignKeyType; this.propertyName = propertyName; this.entityName = entityName; + this.constrained = constrained; } public OneToOneType(OneToOneType original, String superTypeEntityName) { @@ -68,6 +89,7 @@ public OneToOneType(OneToOneType original, String superTypeEntityName) { this.foreignKeyType = original.foreignKeyType; this.propertyName = original.propertyName; this.entityName = original.entityName; + this.constrained = original.constrained; } @Override @@ -155,8 +177,8 @@ public Object hydrate( } @Override - protected boolean isNullable() { - return foreignKeyType==ForeignKeyDirection.TO_PARENT; + public boolean isNullable() { + return !constrained; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java index bd66bfa280d9cd21be2d3c63fb12a50e3c294e2a..c45aed75916553e6876f9a6c85c75f4a7f65613a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java @@ -25,9 +25,10 @@ * @author Gavin King */ public class SpecialOneToOneType extends OneToOneType { - + /** - * @deprecated Use {@link #SpecialOneToOneType(org.hibernate.type.TypeFactory.TypeScope, String, ForeignKeyDirection, boolean, String, boolean, boolean, String, String)} instead. + * @deprecated Use {@link SpecialOneToOneType#SpecialOneToOneType(TypeFactory.TypeScope, String, ForeignKeyDirection, boolean, String, boolean, boolean, String, String, boolean)} + * instead. */ @Deprecated public SpecialOneToOneType( @@ -41,17 +42,47 @@ public SpecialOneToOneType( String propertyName) { this( scope, referencedEntityName, foreignKeyType, uniqueKeyPropertyName == null, uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName ); } - + + /** + * @deprecated Use {@link SpecialOneToOneType#SpecialOneToOneType(TypeFactory.TypeScope, String, ForeignKeyDirection, boolean, String, boolean, boolean, String, String, boolean)} + * instead. + */ + @Deprecated public SpecialOneToOneType( TypeFactory.TypeScope scope, String referencedEntityName, ForeignKeyDirection foreignKeyType, - boolean referenceToPrimaryKey, + boolean referenceToPrimaryKey, String uniqueKeyPropertyName, boolean lazy, boolean unwrapProxy, String entityName, String propertyName) { + this ( + scope, + referencedEntityName, + foreignKeyType, + referenceToPrimaryKey, + uniqueKeyPropertyName, + lazy, + unwrapProxy, + entityName, + propertyName, + foreignKeyType != ForeignKeyDirection.TO_PARENT + ); + } + + public SpecialOneToOneType( + TypeFactory.TypeScope scope, + String referencedEntityName, + ForeignKeyDirection foreignKeyType, + boolean referenceToPrimaryKey, + String uniqueKeyPropertyName, + boolean lazy, + boolean unwrapProxy, + String entityName, + String propertyName, + boolean constrained) { super( scope, referencedEntityName, @@ -61,7 +92,8 @@ public SpecialOneToOneType( lazy, unwrapProxy, entityName, - propertyName + propertyName, + constrained ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/Type.java b/hibernate-core/src/main/java/org/hibernate/type/Type.java index 07d90ccf8cc3d2668c8b379006dd0249393b773e..0008c0cc7e6ac09ae1953eecf04a20fc4c37172a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/Type.java +++ b/hibernate-core/src/main/java/org/hibernate/type/Type.java @@ -222,7 +222,7 @@ public interface Type extends Serializable { * @throws HibernateException A problem occurred calculating the hash code */ int getHashCode(Object x, SessionFactoryImplementor factory) throws HibernateException; - + /** * Perform a {@link java.util.Comparator} style comparison between values * @@ -235,7 +235,7 @@ public interface Type extends Serializable { /** * Should the parent be considered dirty, given both the old and current value? - * + * * @param old the old value * @param current the current value * @param session The session from which the request originated. @@ -427,7 +427,7 @@ Object deepCopy(Object value, SessionFactoryImplementor factory) * @throws HibernateException An error from Hibernate */ Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException; - + /** * Called before assembling a query result set from the query cache, to allow batch fetching * of entities missing from the second-level cache. @@ -444,7 +444,7 @@ Object deepCopy(Object value, SessionFactoryImplementor factory) *
      • in the case of an entity or collection type, the key
      • *
      • otherwise, the value itself
      • * - * + * * @param rs The JDBC result set * @param names the column names making up this type value (use to read from result set) * @param session The originating session @@ -501,7 +501,7 @@ default Object resolve(Object value, SharedSessionContractImplementor session, O */ Object semiResolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException; - + /** * As part of 2-phase loading, when we perform resolving what is the resolved type for this type? Generally * speaking the type and its semi-resolved type will be the same. The main deviation from this is in the @@ -536,7 +536,7 @@ Object replace( SharedSessionContractImplementor session, Object owner, Map copyCache) throws HibernateException; - + /** * During merge, replace the existing (target) value in the entity we are merging to * with a new (original) value from the detached entity we are merging. For immutable @@ -562,16 +562,16 @@ Object replace( Object owner, Map copyCache, ForeignKeyDirection foreignKeyDirection) throws HibernateException; - + /** * Given an instance of the type, return an array of boolean, indicating * which mapped columns would be null. - * + * * @param value an instance of the type * @param mapping The mapping abstraction * * @return array indicating column nullness for a value instance */ boolean[] toColumnNullness(Object value, Mapping mapping); - + } diff --git a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java index d00817a858da2df6fe7b77ab1549017cdc33781e..f18866cb69741e884f4ea2d8712104ad88fbab1f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java @@ -200,6 +200,11 @@ public static SerializableType serializable(Class // one-to-one type builders ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + /** + * @deprecated Use {@link TypeFactory#oneToOne(String, ForeignKeyDirection, boolean, String, boolean, boolean, String, String, boolean)} + * instead. + */ + @Deprecated public EntityType oneToOne( String persistentClass, ForeignKeyDirection foreignKeyType, @@ -209,9 +214,39 @@ public EntityType oneToOne( boolean unwrapProxy, String entityName, String propertyName) { + return oneToOne( persistentClass, foreignKeyType, referenceToPrimaryKey, uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName, foreignKeyType != ForeignKeyDirection.TO_PARENT ); + } + + /** + * @deprecated Use {@link TypeFactory#specialOneToOne(String, ForeignKeyDirection, boolean, String, boolean, boolean, String, String, boolean)} + * instead. + */ + @Deprecated + public EntityType specialOneToOne( + String persistentClass, + ForeignKeyDirection foreignKeyType, + boolean referenceToPrimaryKey, + String uniqueKeyPropertyName, + boolean lazy, + boolean unwrapProxy, + String entityName, + String propertyName) { + return specialOneToOne( persistentClass, foreignKeyType, referenceToPrimaryKey, uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName, foreignKeyType != ForeignKeyDirection.TO_PARENT ); + } + + public EntityType oneToOne( + String persistentClass, + ForeignKeyDirection foreignKeyType, + boolean referenceToPrimaryKey, + String uniqueKeyPropertyName, + boolean lazy, + boolean unwrapProxy, + String entityName, + String propertyName, + boolean constrained) { return new OneToOneType( typeScope, persistentClass, foreignKeyType, referenceToPrimaryKey, - uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName + uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName, constrained ); } @@ -223,10 +258,12 @@ public EntityType specialOneToOne( boolean lazy, boolean unwrapProxy, String entityName, - String propertyName) { + String propertyName, + boolean constrained) { return new SpecialOneToOneType( typeScope, persistentClass, foreignKeyType, referenceToPrimaryKey, - uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName + uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName, + constrained ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/TypeHelper.java b/hibernate-core/src/main/java/org/hibernate/type/TypeHelper.java index a151592e5050d8e147ee712a030f78e3b1d9be1e..295cfba36d2605f3aeb288501e116fffa523f5a8 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeHelper.java @@ -159,6 +159,9 @@ public static Object[] replace( if ( original[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY || original[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN ) { copied[i] = target[i]; } + else if ( target[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { + copied[i] = types[i].replace( original[i], null, session, owner, copyCache ); + } else { copied[i] = types[i].replace( original[i], target[i], session, owner, copyCache ); } @@ -193,6 +196,9 @@ public static Object[] replace( || original[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN ) { copied[i] = target[i]; } + else if ( target[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { + copied[i] = types[i].replace( original[i], null, session, owner, copyCache, foreignKeyDirection ); + } else { copied[i] = types[i].replace( original[i], target[i], session, owner, copyCache, foreignKeyDirection ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaDescriptor.java index bdd0659b4a39540843857d02050557401d4480c7..9bc9a52e0a03373440134e7c462da6d50cbf3c81 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/InstantJavaDescriptor.java @@ -61,7 +61,23 @@ public X unwrap(Instant instant, Class type, WrapperOptions options) { } if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) { - return (X) Timestamp.from( instant ); + /* + * This works around two bugs: + * - HHH-13266 (JDK-8061577): around and before 1900, + * the number of milliseconds since the epoch does not mean the same thing + * for java.util and java.time, so conversion must be done using the year, month, day, hour, etc. + * - HHH-13379 (JDK-4312621): after 1908 (approximately), + * Daylight Saving Time introduces ambiguity in the year/month/day/hour/etc representation once a year + * (on DST end), so conversion must be done using the number of milliseconds since the epoch. + * - around 1905, both methods are equally valid, so we don't really care which one is used. + */ + ZonedDateTime zonedDateTime = instant.atZone( ZoneId.systemDefault() ); + if ( zonedDateTime.getYear() < 1905 ) { + return (X) Timestamp.valueOf( zonedDateTime.toLocalDateTime() ); + } + else { + return (X) Timestamp.from( instant ); + } } if ( java.sql.Date.class.isAssignableFrom( type ) ) { @@ -95,7 +111,22 @@ public Instant wrap(X value, WrapperOptions options) { if ( Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return ts.toInstant(); + /* + * This works around two bugs: + * - HHH-13266 (JDK-8061577): around and before 1900, + * the number of milliseconds since the epoch does not mean the same thing + * for java.util and java.time, so conversion must be done using the year, month, day, hour, etc. + * - HHH-13379 (JDK-4312621): after 1908 (approximately), + * Daylight Saving Time introduces ambiguity in the year/month/day/hour/etc representation once a year + * (on DST end), so conversion must be done using the number of milliseconds since the epoch. + * - around 1905, both methods are equally valid, so we don't really care which one is used. + */ + if ( ts.getYear() < 5 ) { // Timestamp year 0 is 1900 + return ts.toLocalDateTime().atZone( ZoneId.systemDefault() ).toInstant(); + } + else { + return ts.toInstant(); + } } if ( Long.class.isInstance( value ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateJavaDescriptor.java index 61f9c3458dc1c1435e5aa93408630a67aa248d97..44018d2a92f0899a9f13b740bba86f7897824bdf 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateJavaDescriptor.java @@ -63,6 +63,13 @@ public X unwrap(LocalDate value, Class type, WrapperOptions options) { final LocalDateTime localDateTime = value.atStartOfDay(); if ( Timestamp.class.isAssignableFrom( type ) ) { + /* + * Workaround for HHH-13266 (JDK-8061577). + * We could have done Timestamp.from( localDateTime.atZone( ZoneId.systemDefault() ).toInstant() ), + * but on top of being more complex than the line below, it won't always work. + * Timestamp.from() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ return (X) Timestamp.valueOf( localDateTime ); } @@ -97,7 +104,14 @@ public LocalDate wrap(X value, WrapperOptions options) { if ( Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ).toLocalDate(); + /* + * Workaround for HHH-13266 (JDK-8061577). + * We used to do LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ).toLocalDate(), + * but on top of being more complex than the line below, it won't always work. + * ts.toInstant() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return ts.toLocalDateTime().toLocalDate(); } if ( Long.class.isInstance( value ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateTimeJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateTimeJavaDescriptor.java index 1760393c253213b29109e346083d405ff541f8f5..aa3c760fef290df91c90253626161211ee63deec 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateTimeJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalDateTimeJavaDescriptor.java @@ -55,8 +55,14 @@ public X unwrap(LocalDateTime value, Class type, WrapperOptions options) } if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) { - Instant instant = value.atZone( ZoneId.systemDefault() ).toInstant(); - return (X) java.sql.Timestamp.from( instant ); + /* + * Workaround for HHH-13266 (JDK-8061577). + * We used to do Timestamp.from( value.atZone( ZoneId.systemDefault() ).toInstant() ), + * but on top of being more complex than the line below, it won't always work. + * Timestamp.from() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return (X) Timestamp.valueOf( value ); } if ( java.sql.Date.class.isAssignableFrom( type ) ) { @@ -98,7 +104,14 @@ public LocalDateTime wrap(X value, WrapperOptions options) { if ( Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ); + /* + * Workaround for HHH-13266 (JDK-8061577). + * We used to do LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ), + * but on top of being more complex than the line below, it won't always work. + * ts.toInstant() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return ts.toLocalDateTime(); } if ( Long.class.isInstance( value ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetDateTimeJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetDateTimeJavaDescriptor.java index df36984295635cf5e68442d116cc20f39d20cc30..c9f624f1050e88775946e951160c6225af909aad 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetDateTimeJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetDateTimeJavaDescriptor.java @@ -59,7 +59,24 @@ public X unwrap(OffsetDateTime offsetDateTime, Class type, WrapperOptions } if ( Timestamp.class.isAssignableFrom( type ) ) { - return (X) Timestamp.from( offsetDateTime.toInstant() ); + /* + * This works around two bugs: + * - HHH-13266 (JDK-8061577): around and before 1900, + * the number of milliseconds since the epoch does not mean the same thing + * for java.util and java.time, so conversion must be done using the year, month, day, hour, etc. + * - HHH-13379 (JDK-4312621): after 1908 (approximately), + * Daylight Saving Time introduces ambiguity in the year/month/day/hour/etc representation once a year + * (on DST end), so conversion must be done using the number of milliseconds since the epoch. + * - around 1905, both methods are equally valid, so we don't really care which one is used. + */ + if ( offsetDateTime.getYear() < 1905 ) { + return (X) Timestamp.valueOf( + offsetDateTime.atZoneSameInstant( ZoneId.systemDefault() ).toLocalDateTime() + ); + } + else { + return (X) Timestamp.from( offsetDateTime.toInstant() ); + } } if ( java.sql.Date.class.isAssignableFrom( type ) ) { @@ -93,7 +110,22 @@ public OffsetDateTime wrap(X value, WrapperOptions options) { if ( Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return OffsetDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ); + /* + * This works around two bugs: + * - HHH-13266 (JDK-8061577): around and before 1900, + * the number of milliseconds since the epoch does not mean the same thing + * for java.util and java.time, so conversion must be done using the year, month, day, hour, etc. + * - HHH-13379 (JDK-4312621): after 1908 (approximately), + * Daylight Saving Time introduces ambiguity in the year/month/day/hour/etc representation once a year + * (on DST end), so conversion must be done using the number of milliseconds since the epoch. + * - around 1905, both methods are equally valid, so we don't really care which one is used. + */ + if ( ts.getYear() < 5 ) { // Timestamp year 0 is 1900 + return ts.toLocalDateTime().atZone( ZoneId.systemDefault() ).toOffsetDateTime(); + } + else { + return OffsetDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ); + } } if ( Date.class.isInstance( value ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetTimeJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetTimeJavaDescriptor.java index 2e3947f57e8af7948697d2311af82ef4c41d31e9..65f0099a0c4f6aab43c2eaf34d0849fca9834641 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetTimeJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetTimeJavaDescriptor.java @@ -12,7 +12,7 @@ import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.OffsetTime; -import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Calendar; import java.util.Date; @@ -65,6 +65,12 @@ public X unwrap(OffsetTime offsetTime, Class type, WrapperOptions options final ZonedDateTime zonedDateTime = offsetTime.atDate( LocalDate.of( 1970, 1, 1 ) ).toZonedDateTime(); if ( Timestamp.class.isAssignableFrom( type ) ) { + /* + * Workaround for HHH-13266 (JDK-8061577). + * Ideally we'd want to use Timestamp.from( offsetDateTime.toInstant() ), but this won't always work. + * Timestamp.from() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ return (X) Timestamp.valueOf( zonedDateTime.toLocalDateTime() ); } @@ -95,22 +101,44 @@ public OffsetTime wrap(X value, WrapperOptions options) { return (OffsetTime) value; } + /* + * Also, in order to fix HHH-13357, and to be consistent with the conversion to Time (see above), + * we set the offset to the current offset of the JVM (OffsetDateTime.now().getOffset()). + * This is different from setting the *zone* to the current *zone* of the JVM (ZoneId.systemDefault()), + * since a zone has a varying offset over time, + * thus the zone might have a different offset for the given timezone than it has for the current date/time. + * For example, if the timestamp represents 1970-01-01TXX:YY, + * and the JVM is set to use Europe/Paris as a timezone, and the current time is 2019-04-16-08:53, + * then applying the JVM timezone to the timestamp would result in the offset +01:00, + * but applying the JVM offset would result in the offset +02:00, since DST is in effect at 2019-04-16-08:53. + * + * Of course none of this would be a problem if we just stored the offset in the database, + * but I guess there are historical reasons that explain why we don't. + */ + ZoneOffset offset = OffsetDateTime.now().getOffset(); + if ( Time.class.isInstance( value ) ) { - return ( (Time) value ).toLocalTime().atOffset( OffsetDateTime.now().getOffset() ); + return ( (Time) value ).toLocalTime().atOffset( offset ); } if ( Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return OffsetTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ); + /* + * Workaround for HHH-13266 (JDK-8061577). + * Ideally we'd want to use OffsetDateTime.ofInstant( ts.toInstant(), ... ), but this won't always work. + * ts.toInstant() assumes the number of milliseconds since the epoch + * means the same thing in Timestamp and Instant, but it doesn't, in particular before 1900. + */ + return ts.toLocalDateTime().toLocalTime().atOffset( offset ); } if ( Date.class.isInstance( value ) ) { final Date date = (Date) value; - return OffsetTime.ofInstant( date.toInstant(), ZoneId.systemDefault() ); + return OffsetTime.ofInstant( date.toInstant(), offset ); } if ( Long.class.isInstance( value ) ) { - return OffsetTime.ofInstant( Instant.ofEpochMilli( (Long) value ), ZoneId.systemDefault() ); + return OffsetTime.ofInstant( Instant.ofEpochMilli( (Long) value ), offset ); } if ( Calendar.class.isInstance( value ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ZonedDateTimeJavaDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ZonedDateTimeJavaDescriptor.java index 26cdd1e410b127c88141346fd26c0fa79244db54..a10bb47a8ecb35ce0771147b0e6a603f2fbfc36b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ZonedDateTimeJavaDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ZonedDateTimeJavaDescriptor.java @@ -59,7 +59,24 @@ public X unwrap(ZonedDateTime zonedDateTime, Class type, WrapperOptions o } if ( Timestamp.class.isAssignableFrom( type ) ) { - return (X) Timestamp.from( zonedDateTime.toInstant() ); + /* + * This works around two bugs: + * - HHH-13266 (JDK-8061577): around and before 1900, + * the number of milliseconds since the epoch does not mean the same thing + * for java.util and java.time, so conversion must be done using the year, month, day, hour, etc. + * - HHH-13379 (JDK-4312621): after 1908 (approximately), + * Daylight Saving Time introduces ambiguity in the year/month/day/hour/etc representation once a year + * (on DST end), so conversion must be done using the number of milliseconds since the epoch. + * - around 1905, both methods are equally valid, so we don't really care which one is used. + */ + if ( zonedDateTime.getYear() < 1905 ) { + return (X) Timestamp.valueOf( + zonedDateTime.withZoneSameInstant( ZoneId.systemDefault() ).toLocalDateTime() + ); + } + else { + return (X) Timestamp.from( zonedDateTime.toInstant() ); + } } if ( java.sql.Date.class.isAssignableFrom( type ) ) { @@ -93,7 +110,22 @@ public ZonedDateTime wrap(X value, WrapperOptions options) { if ( java.sql.Timestamp.class.isInstance( value ) ) { final Timestamp ts = (Timestamp) value; - return ZonedDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ); + /* + * This works around two bugs: + * - HHH-13266 (JDK-8061577): around and before 1900, + * the number of milliseconds since the epoch does not mean the same thing + * for java.util and java.time, so conversion must be done using the year, month, day, hour, etc. + * - HHH-13379 (JDK-4312621): after 1908 (approximately), + * Daylight Saving Time introduces ambiguity in the year/month/day/hour/etc representation once a year + * (on DST end), so conversion must be done using the number of milliseconds since the epoch. + * - around 1905, both methods are equally valid, so we don't really care which one is used. + */ + if ( ts.getYear() < 5 ) { // Timestamp year 0 is 1900 + return ts.toLocalDateTime().atZone( ZoneId.systemDefault() ); + } + else { + return ts.toInstant().atZone( ZoneId.systemDefault() ); + } } if ( java.util.Date.class.isInstance( value ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LobTypeMappings.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LobTypeMappings.java index ce5694cf3961afc438c0ced6e4e6a6935cebd560..c246654c9fa7c6cccea27d466ffe5e647aa1fe92 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LobTypeMappings.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LobTypeMappings.java @@ -10,62 +10,98 @@ import java.util.Locale; import java.util.Map; -import org.hibernate.internal.util.collections.BoundedConcurrentHashMap; import org.hibernate.type.descriptor.JdbcTypeNameMapper; -import org.jboss.logging.Logger; - /** * @author Steve Ebersole + * @author Sanne Grinovero */ -public class LobTypeMappings { - private static final Logger log = Logger.getLogger( LobTypeMappings.class ); +public final class LobTypeMappings { /** * Singleton access + * @deprecated use the static method helpers instead. */ + @Deprecated public static final LobTypeMappings INSTANCE = new LobTypeMappings(); - private final Map lobCodeByNonLobCode; - private LobTypeMappings() { - this.lobCodeByNonLobCode = new BoundedConcurrentHashMap(); - - // BLOB mappings - this.lobCodeByNonLobCode.put( Types.BLOB, Types.BLOB ); - this.lobCodeByNonLobCode.put( Types.BINARY, Types.BLOB ); - this.lobCodeByNonLobCode.put( Types.VARBINARY, Types.BLOB ); - this.lobCodeByNonLobCode.put( Types.LONGVARBINARY, Types.BLOB ); - - // CLOB mappings - this.lobCodeByNonLobCode.put( Types.CLOB, Types.CLOB ); - this.lobCodeByNonLobCode.put( Types.CHAR, Types.CLOB ); - this.lobCodeByNonLobCode.put( Types.VARCHAR, Types.CLOB ); - this.lobCodeByNonLobCode.put( Types.LONGVARCHAR, Types.CLOB ); - - // NCLOB mappings - this.lobCodeByNonLobCode.put( Types.NCLOB, Types.NCLOB ); - this.lobCodeByNonLobCode.put( Types.NCHAR, Types.NCLOB ); - this.lobCodeByNonLobCode.put( Types.NVARCHAR, Types.NCLOB ); - this.lobCodeByNonLobCode.put( Types.LONGNVARCHAR, Types.NCLOB ); } - public boolean hasCorrespondingLobCode(int jdbcTypeCode) { - return lobCodeByNonLobCode.containsKey( jdbcTypeCode ); + /** + * + * @param jdbcTypeCode + * @return true if corresponding Lob code exists; false otherwise. + * @deprecated use {@link #isMappedToKnownLobCode(int)} + */ + @Deprecated + public boolean hasCorrespondingLobCode(final int jdbcTypeCode) { + return isMappedToKnownLobCode( jdbcTypeCode ); } - public int getCorrespondingLobCode(int jdbcTypeCode) { - Integer lobTypeCode = lobCodeByNonLobCode.get( jdbcTypeCode ); - if ( lobTypeCode == null ) { - throw new IllegalArgumentException( - String.format( - Locale.ROOT, - "JDBC type-code [%s (%s)] not known to have a corresponding LOB equivalent", - jdbcTypeCode, - JdbcTypeNameMapper.getTypeName( jdbcTypeCode ) - ) - ); + /** + * + * @param jdbcTypeCode + * @return corresponding Lob code + * @deprecated use {@link #getLobCodeTypeMapping(int)} + */ + @Deprecated + public int getCorrespondingLobCode(final int jdbcTypeCode) { + return getLobCodeTypeMapping( jdbcTypeCode ); + } + + public static boolean isMappedToKnownLobCode(final int jdbcTypeCode) { + return + // BLOB mappings + jdbcTypeCode == Types.BLOB || + jdbcTypeCode == Types.BINARY || + jdbcTypeCode == Types.VARBINARY || + jdbcTypeCode == Types.LONGVARBINARY || + + // CLOB mappings + jdbcTypeCode == Types.CLOB || + jdbcTypeCode == Types.CHAR || + jdbcTypeCode == Types.VARCHAR || + jdbcTypeCode == Types.LONGVARCHAR || + + // NCLOB mappings + jdbcTypeCode == Types.NCLOB || + jdbcTypeCode == Types.NCHAR || + jdbcTypeCode == Types.NVARCHAR || + jdbcTypeCode == Types.LONGNVARCHAR; + } + + public static int getLobCodeTypeMapping(final int jdbcTypeCode) { + switch ( jdbcTypeCode ) { + + // BLOB mappings + case Types.BLOB : + case Types.BINARY : + case Types.VARBINARY : + case Types.LONGVARBINARY : return Types.BLOB; + + // CLOB mappings + case Types.CLOB : + case Types.CHAR : + case Types.VARCHAR : + case Types.LONGVARCHAR : return Types.CLOB; + + // NCLOB mappings + case Types.NCLOB : + case Types.NCHAR : + case Types.NVARCHAR : + case Types.LONGNVARCHAR : return Types.NCLOB; + + // Anything else: + default: + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "JDBC type-code [%s (%s)] not known to have a corresponding LOB equivalent", + jdbcTypeCode, + JdbcTypeNameMapper.getTypeName( jdbcTypeCode ) + ) ); } - return lobTypeCode; } + } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NationalizedTypeMappings.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NationalizedTypeMappings.java index eedea2e25763371bbfe51c1aedabf9bc9998e227..9dd7c21ffa3358fadf4227cf8882a1fb02ba7928 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NationalizedTypeMappings.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NationalizedTypeMappings.java @@ -7,9 +7,6 @@ package org.hibernate.type.descriptor.sql; import java.sql.Types; -import java.util.Map; - -import org.hibernate.internal.util.collections.BoundedConcurrentHashMap; import org.jboss.logging.Logger; @@ -20,35 +17,44 @@ * corresponding nationalized equivalent, so that's all we implement for now * * @author Steve Ebersole + * @author Sanne Grinovero */ -public class NationalizedTypeMappings { +public final class NationalizedTypeMappings { + private static final Logger log = Logger.getLogger( NationalizedTypeMappings.class ); /** * Singleton access + * @deprecated use the static methods instead */ + @Deprecated public static final NationalizedTypeMappings INSTANCE = new NationalizedTypeMappings(); - private final Map nationalizedCodeByNonNationalized; - - public NationalizedTypeMappings() { - this.nationalizedCodeByNonNationalized = new BoundedConcurrentHashMap(); - map( Types.CHAR, Types.NCHAR ); - map( Types.CLOB, Types.NCLOB ); - map( Types.LONGVARCHAR, Types.LONGNVARCHAR ); - map( Types.VARCHAR, Types.NVARCHAR ); + private NationalizedTypeMappings() { } - private void map(int nonNationalizedCode, int nationalizedCode) { - nationalizedCodeByNonNationalized.put( nonNationalizedCode, nationalizedCode ); + public static int toNationalizedTypeCode(final int jdbcCode) { + switch ( jdbcCode ) { + case Types.CHAR: return Types.NCHAR; + case Types.CLOB: return Types.NCLOB; + case Types.LONGVARCHAR: return Types.LONGNVARCHAR; + case Types.VARCHAR: return Types.NVARCHAR; + default: + if ( log.isDebugEnabled() ) { + log.debug( "Unable to locate nationalized jdbc-code equivalent for given jdbc code : " + jdbcCode ); + } + return jdbcCode; + } } + /** + * @deprecated use {@link #toNationalizedTypeCode(int)} + * @param jdbcCode + * @return + */ + @Deprecated public int getCorrespondingNationalizedCode(int jdbcCode) { - Integer nationalizedCode = nationalizedCodeByNonNationalized.get( jdbcCode ); - if ( nationalizedCode == null ) { - log.debug( "Unable to locate nationalized jdbc-code equivalent for given jdbc code : " + jdbcCode ); - return jdbcCode; - } - return nationalizedCode; + return toNationalizedTypeCode( jdbcCode ); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java b/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java index 2b24ff4a653ca1b508502b9061b1478e5c8271c3..85130a0bc02532c31eb445acf8d114387be85102 100644 --- a/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/CompositeUserType.java @@ -87,7 +87,7 @@ public interface CompositeUserType { * @throws HibernateException */ boolean equals(Object x, Object y) throws HibernateException; - + /** * Get a hashcode for the instance, consistent with persistence "equality" */ diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java b/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java index 4947f0cebf287754d752830aac8e1a05278507c6..21cac8b61ed895c1b7d61c59cf6158e6a53097d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/UserType.java @@ -12,9 +12,7 @@ import java.sql.SQLException; import org.hibernate.HibernateException; -import org.hibernate.boot.model.JavaTypeDescriptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.type.spi.TypeConfiguration; /** * This interface should be implemented by user-defined "types". diff --git a/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java b/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java index 7ae9d9c73d04d545aee66436580e7752d194e26b..abd55a59efd8f989cf2e5ae4e313ebdbc854edee 100755 --- a/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java +++ b/hibernate-core/src/main/java/org/hibernate/usertype/UserVersionType.java @@ -12,7 +12,7 @@ /** * A user type that may be used for a version property - * + * * @author Gavin King */ public interface UserVersionType extends UserType, Comparator { @@ -34,5 +34,4 @@ public interface UserVersionType extends UserType, Comparator { * @return an instance of the type */ Object next(Object current, SharedSessionContractImplementor session); - } diff --git a/hibernate-core/src/main/resources/org/hibernate/jpa/orm_3_0.xsd b/hibernate-core/src/main/resources/org/hibernate/jpa/orm_3_0.xsd new file mode 100644 index 0000000000000000000000000000000000000000..233d98f6eaa951195a644ceb0990c01a81cad57d --- /dev/null +++ b/hibernate-core/src/main/resources/org/hibernate/jpa/orm_3_0.xsd @@ -0,0 +1,2324 @@ + + + + + + + + + ... + + + + ]]> + + + + + + + + + + + + + + + + + + The entity-mappings element is the root element of a mapping + file. It contains the following four types of elements: + + 1. The persistence-unit-metadata element contains metadata + for the entire persistence unit. It is undefined if this element + occurs in multiple mapping files within the same persistence unit. + + 2. The package, schema, catalog and access elements apply to all of + the entity, mapped-superclass and embeddable elements defined in + the same file in which they occur. + + 3. The sequence-generator, table-generator, converter, named-query, + named-native-query, named-stored-procedure-query, and + sql-result-set-mapping elements are global to the persistence + unit. It is undefined to have more than one sequence-generator + or table-generator of the same name in the same or different + mapping files in a persistence unit. It is undefined to have + more than one named-query, named-native-query, sql-result-set-mapping, + or named-stored-procedure-query of the same name in the same + or different mapping files in a persistence unit. It is also + undefined to have more than one converter for the same target + type in the same or different mapping files in a persistence unit. + + 4. The entity, mapped-superclass and embeddable elements each define + the mapping information for a managed persistent class. The mapping + information contained in these elements may be complete or it may + be partial. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Metadata that applies to the persistence unit and not just to + the mapping file in which it is contained. + + If the xml-mapping-metadata-complete element is specified, + the complete set of mapping metadata for the persistence unit + is contained in the XML mapping files for the persistence unit. + + + + + + + + + + + + + + + + + These defaults are applied to the persistence unit as a whole + unless they are overridden by local annotation or XML + element settings. + + schema - Used as the schema for all tables, secondary tables, join + tables, collection tables, sequence generators, and table + generators that apply to the persistence unit + catalog - Used as the catalog for all tables, secondary tables, join + tables, collection tables, sequence generators, and table + generators that apply to the persistence unit + delimited-identifiers - Used to treat database identifiers as + delimited identifiers. + access - Used as the access type for all managed classes in + the persistence unit + cascade-persist - Adds cascade-persist to the set of cascade options + in all entity relationships of the persistence unit + entity-listeners - List of default entity listeners to be invoked + on each entity in the persistence unit. + + + + + + + + + + + + + + + + + + + + Defines the settings and mappings for an entity. Is allowed to be + sparsely populated and used in conjunction with the annotations. + Alternatively, the metadata-complete attribute can be used to + indicate that no annotations on the entity class (and its fields + or properties) are to be processed. If this is the case then + the defaulting rules for the entity and its subelements will + be recursively applied. + + @Target(TYPE) @Retention(RUNTIME) + public @interface Entity { + String name() default ""; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This element determines how the persistence provider accesses the + state of an entity or embedded object. + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface AssociationOverride { + String name(); + JoinColumn[] joinColumns() default{}; + JoinTable joinTable() default @JoinTable; + } + + + + + + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface AttributeOverride { + String name(); + Column column(); + } + + + + + + + + + + + + + + + + + This element contains the entity field or property mappings. + It may be sparsely populated to include only a subset of the + fields or properties. If metadata-complete for the entity is true + then the remainder of the attributes will be defaulted according + to the default rules. + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Basic { + FetchType fetch() default EAGER; + boolean optional() default true; + } + + + + + + + + + + + + + + + + + + + + + + + + + public enum CascadeType { ALL, PERSIST, MERGE, REMOVE, REFRESH, DETACH}; + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface CollectionTable { + String name() default ""; + String catalog() default ""; + String schema() default ""; + JoinColumn[] joinColumns() default {}; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Column { + String name() default ""; + boolean unique() default false; + boolean nullable() default true; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + int length() default 255; + int precision() default 0; // decimal precision + int scale() default 0; // decimal scale + } + + + + + + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface ColumnResult { + String name(); + Class type() default void.class; + } + + + + + + + + + + + + + + public enum ConstraintMode {CONSTRAINT, NO_CONSTRAINT, PROVIDER_DEFAULT}; + + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface ConstructorResult { + Class targetClass(); + ColumnResult[] columns(); + } + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface Convert { + Class converter() default void.class; + String attributeName() default ""; + boolean disableConversion() default false; + } + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface Converter { + boolean autoApply() default false; + } + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface DiscriminatorColumn { + String name() default "DTYPE"; + DiscriminatorType discriminatorType() default STRING; + String columnDefinition() default ""; + int length() default 31; + } + + + + + + + + + + + + + + + + public enum DiscriminatorType { STRING, CHAR, INTEGER }; + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface DiscriminatorValue { + String value(); + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface ElementCollection { + Class targetClass() default void.class; + FetchType fetch() default LAZY; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines the settings and mappings for embeddable objects. Is + allowed to be sparsely populated and used in conjunction with + the annotations. Alternatively, the metadata-complete attribute + can be used to indicate that no annotations are to be processed + in the class. If this is the case then the defaulting rules will + be recursively applied. + + @Target({TYPE}) @Retention(RUNTIME) + public @interface Embeddable {} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Embedded {} + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface EmbeddedId {} + + + + + + + + + + + + + + + + + Defines an entity listener to be invoked at lifecycle events + for the entities that list this listener. + + + + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface EntityListeners { + Class[] value(); + } + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface EntityResult { + Class entityClass(); + FieldResult[] fields() default {}; + String discriminatorColumn() default ""; + } + + + + + + + + + + + + + + + + + public enum EnumType { + ORDINAL, + STRING + } + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Enumerated { + EnumType value() default ORDINAL; + } + + + + + + + + + + + + + public enum FetchType { LAZY, EAGER }; + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface FieldResult { + String name(); + String column(); + } + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface ForeignKey { + String name() default ""; + ConstraintMode value() default CONSTRAINT; + String foreign-key-definition() default ""; + + Note that the elements that embed the use of the annotation + default this use as @ForeignKey(PROVIDER_DEFAULT). + + } + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface GeneratedValue { + GenerationType strategy() default AUTO; + String generator() default ""; + } + + + + + + + + + + + + + + public enum GenerationType { TABLE, SEQUENCE, IDENTITY, AUTO }; + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Id {} + + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface IdClass { + Class value(); + } + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface Index { + String name() default ""; + String columnList(); + boolean unique() default false; + } + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface Inheritance { + InheritanceType strategy() default SINGLE_TABLE; + } + + + + + + + + + + + + + public enum InheritanceType + { SINGLE_TABLE, JOINED, TABLE_PER_CLASS}; + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface JoinColumn { + String name() default ""; + String referencedColumnName() default ""; + boolean unique() default false; + boolean nullable() default true; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + ForeignKey foreignKey() default @ForeignKey(); + } + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface JoinTable { + String name() default ""; + String catalog() default ""; + String schema() default ""; + JoinColumn[] joinColumns() default {}; + JoinColumn[] inverseJoinColumns() default {}; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Lob {} + + + + + + + + + + + + public enum LockModeType { READ, WRITE, OPTIMISTIC, OPTIMISTIC_FORCE_INCREMENT, PESSIMISTIC_READ, PESSIMISTIC_WRITE, PESSIMISTIC_FORCE_INCREMENT, NONE}; + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface ManyToMany { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default LAZY; + String mappedBy() default ""; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface ManyToOne { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default EAGER; + boolean optional() default true; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKey { + String name() default ""; + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKeyClass { + Class value(); + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKeyColumn { + String name() default ""; + boolean unique() default false; + boolean nullable() default false; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + int length() default 255; + int precision() default 0; // decimal precision + int scale() default 0; // decimal scale + } + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface MapKeyJoinColumn { + String name() default ""; + String referencedColumnName() default ""; + boolean unique() default false; + boolean nullable() default false; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + String table() default ""; + } + + + + + + + + + + + + + + + + + + + + + Defines the settings and mappings for a mapped superclass. Is + allowed to be sparsely populated and used in conjunction with + the annotations. Alternatively, the metadata-complete attribute + can be used to indicate that no annotations are to be processed + If this is the case then the defaulting rules will be recursively + applied. + + @Target(TYPE) @Retention(RUNTIME) + public @interface MappedSuperclass{} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface NamedAttributeNode { + String value(); + String subgraph() default ""; + String keySubgraph() default ""; + } + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedEntityGraph { + String name() default ""; + NamedAttributeNode[] attributeNodes() default {}; + boolean includeAllAttributes() default false; + NamedSubgraph[] subgraphs() default {}; + NamedSubGraph[] subclassSubgraphs() default {}; + } + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedNativeQuery { + String name(); + String query(); + QueryHint[] hints() default {}; + Class resultClass() default void.class; + String resultSetMapping() default ""; //named SqlResultSetMapping + } + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedQuery { + String name(); + String query(); + LockModeType lockMode() default NONE; + QueryHint[] hints() default {}; + } + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface NamedStoredProcedureQuery { + String name(); + String procedureName(); + StoredProcedureParameter[] parameters() default {}; + Class[] resultClasses() default {}; + String[] resultSetMappings() default{}; + QueryHint[] hints() default {}; + } + + + + + + + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface NamedSubgraph { + String name(); + Class type() default void.class; + NamedAttributeNode[] attributeNodes(); + } + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OneToMany { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default LAZY; + String mappedBy() default ""; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OneToOne { + Class targetEntity() default void.class; + CascadeType[] cascade() default {}; + FetchType fetch() default EAGER; + boolean optional() default true; + String mappedBy() default ""; + boolean orphanRemoval() default false; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OrderBy { + String value() default ""; + } + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface OrderColumn { + String name() default ""; + boolean nullable() default true; + boolean insertable() default true; + boolean updatable() default true; + String columnDefinition() default ""; + } + + + + + + + + + + + + + + + + + public enum ParameterMode { IN, INOUT, OUT, REF_CURSOR}; + + + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostLoad {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostPersist {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostRemove {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PostUpdate {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PrePersist {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PreRemove {} + + + + + + + + + + + + + + + + @Target({METHOD}) @Retention(RUNTIME) + public @interface PreUpdate {} + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface PrimaryKeyJoinColumn { + String name() default ""; + String referencedColumnName() default ""; + String columnDefinition() default ""; + } + + + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface QueryHint { + String name(); + String value(); + } + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface SecondaryTable { + String name(); + String catalog() default ""; + String schema() default ""; + PrimaryKeyJoinColumn[] pkJoinColumns() default {}; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface SequenceGenerator { + String name(); + String sequenceName() default ""; + String catalog() default ""; + String schema() default ""; + int initialValue() default 1; + int allocationSize() default 50; + } + + + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface SqlResultSetMapping { + String name(); + EntityResult[] entities() default {}; + ConstructorResult[] classes() default{}; + ColumnResult[] columns() default {}; + } + + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface StoredProcedureParameter { + String name() default ""; + ParameterMode mode() default ParameterMode.IN; + Class type(); + } + + + + + + + + + + + + + + + + + + @Target({TYPE}) @Retention(RUNTIME) + public @interface Table { + String name() default ""; + String catalog() default ""; + String schema() default ""; + UniqueConstraint[] uniqueConstraints() default {}; + Index[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) + public @interface TableGenerator { + String name(); + String table() default ""; + String catalog() default ""; + String schema() default ""; + String pkColumnName() default ""; + String valueColumnName() default ""; + String pkColumnValue() default ""; + int initialValue() default 0; + int allocationSize() default 50; + UniqueConstraint[] uniqueConstraints() default {}; + Indexes[] indexes() default {}; + } + + + + + + + + + + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Temporal { + TemporalType value(); + } + + + + + + + + + + + + + public enum TemporalType { + DATE, // java.sql.Date + TIME, // java.sql.Time + TIMESTAMP // java.sql.Timestamp + } + + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Transient {} + + + + + + + + + + + + + @Target({}) @Retention(RUNTIME) + public @interface UniqueConstraint { + String name() default ""; + String[] columnNames(); + } + + + + + + + + + + + + + + + + @Target({METHOD, FIELD}) @Retention(RUNTIME) + public @interface Version {} + + + + + + + + + + + + diff --git a/hibernate-core/src/main/resources/org/hibernate/jpa/persistence_3_0.xsd b/hibernate-core/src/main/resources/org/hibernate/jpa/persistence_3_0.xsd new file mode 100644 index 0000000000000000000000000000000000000000..e2a294393704710ac56773aea4186c00ad6a144a --- /dev/null +++ b/hibernate-core/src/main/resources/org/hibernate/jpa/persistence_3_0.xsd @@ -0,0 +1,342 @@ + + + + + + + + + ... + + + ]]> + + + + + + + + + + + + + + + + + + + + + + Configuration of a persistence unit. + + + + + + + + + + + + Description of this persistence unit. + + + + + + + + + + + + Provider class that supplies EntityManagers for this + persistence unit. + + + + + + + + + + + + The container-specific name of the JTA datasource to use. + + + + + + + + + + + + The container-specific name of a non-JTA datasource to use. + + + + + + + + + + + + File containing mapping information. Loaded as a resource + by the persistence provider. + + + + + + + + + + + + Jar file that is to be scanned for managed classes. + + + + + + + + + + + + Managed class to be included in the persistence unit and + to scan for annotations. It should be annotated + with either @Entity, @Embeddable or @MappedSuperclass. + + + + + + + + + + + + When set to true then only listed classes and jars will + be scanned for persistent classes, otherwise the + enclosing jar or directory will also be scanned. + Not applicable to Java SE persistence units. + + + + + + + + + + + + Defines whether caching is enabled for the + persistence unit if caching is supported by the + persistence provider. When set to ALL, all entities + will be cached. When set to NONE, no entities will + be cached. When set to ENABLE_SELECTIVE, only entities + specified as cacheable will be cached. When set to + DISABLE_SELECTIVE, entities specified as not cacheable + will not be cached. When not specified or when set to + UNSPECIFIED, provider defaults may apply. + + + + + + + + + + + + The validation mode to be used for the persistence unit. + + + + + + + + + + + + + A list of standard and vendor-specific properties + and hints. + + + + + + + + + A name-value pair. + + + + + + + + + + + + + + + + + + + + Name used in code to reference this persistence unit. + + + + + + + + + + + + Type of transactions used by EntityManagers from this + persistence unit. + + + + + + + + + + + + + + + + + + + public enum PersistenceUnitTransactionType {JTA, RESOURCE_LOCAL}; + + + + + + + + + + + + + + + + public enum SharedCacheMode { ALL, NONE, ENABLE_SELECTIVE, DISABLE_SELECTIVE, UNSPECIFIED}; + + + + + + + + + + + + + + + + + + + public enum ValidationMode { AUTO, CALLBACK, NONE}; + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/AnnotationMappedNoPackageEntity.java b/hibernate-core/src/test/java/AnnotationMappedNoPackageEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..819356a0a1b49ef49b910eaca3d8468e093cf304 --- /dev/null +++ b/hibernate-core/src/test/java/AnnotationMappedNoPackageEntity.java @@ -0,0 +1,35 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity(name = "annotationnopackage") +public class AnnotationMappedNoPackageEntity { + @Id + private Integer id; + + @Basic + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-core/src/test/java/NoPackageTest.java b/hibernate-core/src/test/java/NoPackageTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5c8e20aef34c6bef49712343c9121ce76467258f --- /dev/null +++ b/hibernate-core/src/test/java/NoPackageTest.java @@ -0,0 +1,48 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +import org.hibernate.query.Query; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Test using an entity which is in no package. + * We had problems with ByteBuddy in the past. + */ +@TestForIssue(jiraKey = "HHH-13112") +public class NoPackageTest extends BaseCoreFunctionalTestCase { + + @Test + public void testNoException() { + inTransaction( session -> { + AnnotationMappedNoPackageEntity box = new AnnotationMappedNoPackageEntity(); + box.setId( 42 ); + box.setName( "This feels dirty" ); + session.persist( box ); + } ); + + inTransaction( session -> { + Query query = session.createQuery( + "select e from " + AnnotationMappedNoPackageEntity.class.getSimpleName() + " e", + AnnotationMappedNoPackageEntity.class + ); + AnnotationMappedNoPackageEntity box = query.getSingleResult(); + assertEquals( (Integer) 42, box.getId() ); + } ); + } + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + AnnotationMappedNoPackageEntity.class + }; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest.java b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest.java index dab29ade7b13806d219e67830a942797efa9a6e3..c8366cc168861d74e6ad3906b8a1b26dbc5abc09 100644 --- a/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest.java +++ b/hibernate-core/src/test/java/org/hibernate/boot/spi/metadatabuildercontributor/SqlFunctionMetadataBuilderContributorIllegalClassArgumentTest.java @@ -35,7 +35,14 @@ public void buildEntityManagerFactory() { fail("Should throw exception!"); } catch (ClassCastException e) { - assertTrue( e.getMessage().contains( "cannot be cast to org.hibernate.boot.spi.MetadataBuilderContributor" ) ); + final String javaVendor = System.getProperty("java.vendor"); + + if (javaVendor != null && (javaVendor.startsWith("IBM") || javaVendor.startsWith("AdoptOpenJDK") || javaVendor.startsWith("Eclipse OpenJ9"))) { + assertTrue( e.getMessage().contains( "incompatible with" ) ); + } else { + assertTrue( e.getMessage().contains( "cannot be cast to" ) ); + } + assertTrue( e.getMessage().contains( "org.hibernate.boot.spi.MetadataBuilderContributor" ) ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyBasicProxyFactoryTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyBasicProxyFactoryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..afc655116b2c6b3bf8a6f495b2ec347b03247376 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyBasicProxyFactoryTest.java @@ -0,0 +1,92 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.internal.bytebuddy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.hibernate.bytecode.internal.bytebuddy.BasicProxyFactoryImpl; +import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +@TestForIssue(jiraKey = "HHH-12786") +public class ByteBuddyBasicProxyFactoryTest { + + private static final BasicProxyFactoryImpl BASIC_PROXY_FACTORY = new BasicProxyFactoryImpl( Entity.class, new Class[0], new ByteBuddyState() ); + + @Test + public void testEqualsHashCode() { + Object entityProxy = BASIC_PROXY_FACTORY.getProxy(); + + assertTrue( entityProxy.equals( entityProxy ) ); + assertNotNull( entityProxy.hashCode() ); + + Object otherEntityProxy = BASIC_PROXY_FACTORY.getProxy(); + assertFalse( entityProxy.equals( otherEntityProxy ) ); + } + + @Test + public void testToString() { + Object entityProxy = BASIC_PROXY_FACTORY.getProxy(); + + assertTrue( entityProxy.toString().contains( "HibernateBasicProxy" ) ); + } + + @Test + public void testGetterSetter() { + Entity entityProxy = (Entity) BASIC_PROXY_FACTORY.getProxy(); + + entityProxy.setBool( true ); + assertTrue( entityProxy.isBool() ); + entityProxy.setBool( false ); + assertFalse( entityProxy.isBool() ); + + entityProxy.setString( "John Irving" ); + assertEquals( "John Irving", entityProxy.getString() ); + } + + @Test + public void testNonGetterSetterMethod() { + Entity entityProxy = (Entity) BASIC_PROXY_FACTORY.getProxy(); + + assertNull( entityProxy.otherMethod() ); + } + + public static class Entity { + + private String string; + + private boolean bool; + + public Entity() { + } + + public boolean isBool() { + return bool; + } + + public void setBool(boolean bool) { + this.bool = bool; + } + + public String getString() { + return string; + } + + public void setString(String string) { + this.string = string; + } + + public String otherMethod() { + return "a string"; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhanceByteCodeNotInProvidedClassLoaderTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhanceByteCodeNotInProvidedClassLoaderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8189554a17d1162aad66e78888635539614b2fc5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhanceByteCodeNotInProvidedClassLoaderTest.java @@ -0,0 +1,68 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.internal.bytebuddy; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.Enhancer; + +import org.hibernate.testing.TestForIssue; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests that bytecode can be enhanced when the original class cannot be loaded from + * the ClassLoader provided to ByteBuddy. + */ +public class EnhanceByteCodeNotInProvidedClassLoaderTest { + + @Test + @TestForIssue( jiraKey = "HHH-13343" ) + public void test() { + Enhancer enhancer = createByteBuddyEnhancer(); + byte[] buffer = readResource( SimpleEntity.class ); + // Now use a fake class name so it won't be found in the ClassLoader + // provided by DefaultEnhancementContext + byte[] enhanced = enhancer.enhance( SimpleEntity.class.getName() + "Fake", buffer ); + Assert.assertNotNull( "This is null when there have been swallowed exceptions during enhancement. Check Logs!", enhanced ); + // Make sure enhanced bytecode is different from original bytecode. + Assert.assertFalse( Arrays.equals( buffer, enhanced ) ); + } + + private byte[] readResource(Class clazz) { + String internalName = clazz.getName().replace( '.', '/' ); + String resourceName = internalName + ".class"; + + final int BUF_SIZE = 256; + byte[] buffer = new byte[BUF_SIZE]; + ByteArrayOutputStream os = new ByteArrayOutputStream(); + int readSize = 0; + try ( InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream( resourceName ) ) { + while ( ( readSize = inputStream.read( buffer ) ) != -1 ) { + os.write( buffer, 0, readSize ); + } + os.flush(); + os.close(); + } + catch (IOException ex) { + Assert.fail( "Should not have an IOException here" ); + } + return os.toByteArray(); + } + + private Enhancer createByteBuddyEnhancer() { + ByteBuddyState bytebuddy = new ByteBuddyState(); + DefaultEnhancementContext enhancementContext = new DefaultEnhancementContext(); + EnhancerImpl impl = new EnhancerImpl( enhancementContext, bytebuddy ); + return impl; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/bytebuddy/EnhancerWildFlyNamesTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhancerWildFlyNamesTest.java similarity index 94% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/bytebuddy/EnhancerWildFlyNamesTest.java rename to hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhancerWildFlyNamesTest.java index a049093956572b05099b09d114bdb32d2d2c540c..42de28c88cf3e0832bfe4caa0b8046e92fe7b663 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/bytebuddy/EnhancerWildFlyNamesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/EnhancerWildFlyNamesTest.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.bytecode.enhancement.bytebuddy; +package org.hibernate.bytecode.internal.bytebuddy; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -31,7 +31,7 @@ public class EnhancerWildFlyNamesTest { @TestForIssue( jiraKey = "HHH-12545" ) public void test() { Enhancer enhancer = createByteBuddyEnhancer(); - String internalName = Bean.class.getName().replace( '.', '/' ); + String internalName = SimpleEntity.class.getName().replace( '.', '/' ); String resourceName = internalName + ".class"; byte[] buffer = new byte[0]; try { diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/GenerateProxiesTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/GenerateProxiesTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3c016a2e949eb02d4dd8167f76104026ecce2917 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/GenerateProxiesTest.java @@ -0,0 +1,53 @@ +package org.hibernate.bytecode.internal.bytebuddy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; + +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.EnhancementException; +import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.hibernate.bytecode.spi.ByteCodeHelper; +import org.hibernate.bytecode.spi.ReflectionOptimizer; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper; +import org.junit.Test; + +public class GenerateProxiesTest { + + @Test + public void generateBasicProxy() { + BasicProxyFactoryImpl basicProxyFactory = new BasicProxyFactoryImpl( SimpleEntity.class, new Class[0], + new ByteBuddyState() ); + assertNotNull( basicProxyFactory.getProxy() ); + } + + @Test + public void generateProxy() throws InstantiationException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException, NoSuchMethodException, SecurityException { + ByteBuddyProxyHelper byteBuddyProxyHelper = new ByteBuddyProxyHelper( new ByteBuddyState() ); + Class proxyClass = byteBuddyProxyHelper.buildProxy( SimpleEntity.class, new Class[0] ); + assertNotNull( proxyClass ); + assertNotNull( proxyClass.getConstructor().newInstance() ); + } + + @Test + public void generateFastClassAndReflectionOptimizer() { + BytecodeProviderImpl bytecodeProvider = new BytecodeProviderImpl(); + ReflectionOptimizer reflectionOptimizer = bytecodeProvider.getReflectionOptimizer( SimpleEntity.class, + new String[]{ "getId", "getName" }, new String[]{ "setId", "setName" }, + new Class[]{ Long.class, String.class } ); + assertEquals( 2, reflectionOptimizer.getAccessOptimizer().getPropertyNames().length ); + assertNotNull( reflectionOptimizer.getInstantiationOptimizer().newInstance() ); + } + + @Test + public void generateEnhancedClass() throws EnhancementException, IOException { + Enhancer enhancer = new EnhancerImpl( new DefaultEnhancementContext(), new ByteBuddyState() ); + enhancer.enhance( SimpleEntity.class.getName(), + ByteCodeHelper.readByteCode( SimpleEntity.class.getClassLoader() + .getResourceAsStream( SimpleEntity.class.getName().replace( '.', '/' ) + ".class" ) ) ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcherTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcherTest.java new file mode 100644 index 0000000000000000000000000000000000000000..184eca1a6bd6018c60df6c4c2cf4c72f464cd975 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcherTest.java @@ -0,0 +1,58 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.internal.bytebuddy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.lang.reflect.Method; + +import org.junit.Test; + +public class HibernateMethodLookupDispatcherTest { + + @Test + public void testAuthorizedClass() { + HibernateMethodLookupDispatcher.registerAuthorizedClass( AuthorizedClass.class.getName() ); + + AuthorizedClass authorizedClass = new AuthorizedClass(); + assertNotNull( authorizedClass.declaredMethod ); + assertEquals( "myMethod", authorizedClass.declaredMethod.getName() ); + } + + @Test( expected = SecurityException.class ) + public void testUnauthorizedClass() { + new UnauthorizedClass(); + } + + public static class AuthorizedClass { + + private Method declaredMethod; + + public AuthorizedClass() { + declaredMethod = HibernateMethodLookupDispatcher.getDeclaredMethod( AuthorizedClass.class, "myMethod", + new Class[]{ String.class } ); + } + + public void myMethod(String myParameter) { + } + } + + public static class UnauthorizedClass { + + @SuppressWarnings("unused") + private Method declaredMethod; + + public UnauthorizedClass() { + declaredMethod = HibernateMethodLookupDispatcher.getDeclaredMethod( AuthorizedClass.class, "myMethod", + new Class[]{ String.class } ); + } + + public void myMethod(String myParameter) { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..e402cf1e000c29ec7cd341ba64a777dcac6caf1e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java @@ -0,0 +1,44 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.internal.bytebuddy; + +import java.util.regex.Pattern; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@Entity(name = "SimpleEntity") +public class SimpleEntity { + + private static final Pattern PATTERN = Pattern.compile( "whatever" ); + + @Id + @GeneratedValue + private Long id; + + @Basic(fetch = FetchType.LAZY) + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/cfg/annotations/CollectionBinderTest.java b/hibernate-core/src/test/java/org/hibernate/cfg/annotations/CollectionBinderTest.java index 57204005881aedee2f2a3fdf6b0038d52d3a7b68..edc30e455e9f3c013519e49b0e521d5edfbacec1 100644 --- a/hibernate-core/src/test/java/org/hibernate/cfg/annotations/CollectionBinderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/cfg/annotations/CollectionBinderTest.java @@ -6,8 +6,12 @@ */ package org.hibernate.cfg.annotations; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import java.sql.SQLException; -import java.util.Map; +import java.util.HashMap; import org.hibernate.MappingException; import org.hibernate.annotations.common.reflection.XClass; @@ -17,17 +21,11 @@ import org.hibernate.mapping.Collection; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Table; - import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Test; - import org.mockito.Mockito; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - /** * Test for HHH-10106 * @@ -39,20 +37,18 @@ public class CollectionBinderTest extends BaseUnitTestCase { @TestForIssue(jiraKey = "HHH-10106") public void testAssociatedClassException() throws SQLException { final Collection collection = mock(Collection.class); - final Map persistentClasses = mock(Map.class); final XClass collectionType = mock(XClass.class); final MetadataBuildingContext buildingContext = mock(MetadataBuildingContext.class); final InFlightMetadataCollector inFly = mock(InFlightMetadataCollector.class); final PersistentClass persistentClass = mock(PersistentClass.class); final Table table = mock(Table.class); - + when(buildingContext.getMetadataCollector()).thenReturn(inFly); - when(persistentClasses.get(null)).thenReturn(null); when(collection.getOwner()).thenReturn(persistentClass); when(collectionType.getName()).thenReturn("List"); when(persistentClass.getTable()).thenReturn(table); when(table.getName()).thenReturn("Hibernate"); - + CollectionBinder collectionBinder = new CollectionBinder(false) { @Override protected Collection createCollection(PersistentClass persistentClass) { @@ -69,7 +65,7 @@ protected Collection createCollection(PersistentClass persistentClass) { String expectMessage = "Association [abc] for entity [CollectionBinderTest] references unmapped class [List]"; try { - collectionBinder.bindOneToManySecondPass(collection, persistentClasses, null, collectionType, false, false, buildingContext, null); + collectionBinder.bindOneToManySecondPass(collection, new HashMap(), null, collectionType, false, false, buildingContext, null); } catch (MappingException e) { assertEquals(expectMessage, e.getMessage()); } diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/Oracle12LimitHandlerTest.java b/hibernate-core/src/test/java/org/hibernate/dialect/Oracle12LimitHandlerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..96e35caebd74247c53de7ab10c494c482f5b715b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/dialect/Oracle12LimitHandlerTest.java @@ -0,0 +1,67 @@ +package org.hibernate.dialect; + +import org.hibernate.dialect.pagination.Oracle12LimitHandler; +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.RowSelection; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +@TestForIssue( jiraKey = "HHH-14649") +public class Oracle12LimitHandlerTest { + + @Test + public void testSqlWithSpace() { + final String sql = "select p.name from Person p where p.id = 1 for update"; + final String expected = "select * from ( select p.name from Person p where p.id = 1 ) where rownum <= ? for update"; + + final QueryParameters queryParameters = getQueryParameters( 0, 5 ); + final String processedSql = Oracle12LimitHandler.INSTANCE.processSql( sql, queryParameters ); + + assertEquals( expected, processedSql ); + } + + @Test + public void testSqlWithSpaceInsideQuotedString() { + final String sql = "select p.name from Person p where p.name = ' this is a string with spaces ' for update"; + final String expected = "select * from ( select p.name from Person p where p.name = ' this is a string with spaces ' ) where rownum <= ? for update"; + + final QueryParameters queryParameters = getQueryParameters( 0, 5 ); + final String processedSql = Oracle12LimitHandler.INSTANCE.processSql( sql, queryParameters ); + + assertEquals( expected, processedSql ); + } + + @Test + public void testSqlWithForUpdateInsideQuotedString() { + final String sql = "select a.prop from A a where a.name = 'this is for update '"; + final String expected = "select a.prop from A a where a.name = 'this is for update ' fetch first ? rows only"; + + final QueryParameters queryParameters = getQueryParameters( 0, 5 ); + final String processedSql = Oracle12LimitHandler.INSTANCE.processSql( sql, queryParameters ); + + assertEquals( expected, processedSql ); + } + + @Test + public void testSqlWithForUpdateInsideAndOutsideQuotedStringA() { + final String sql = "select a.prop from A a where a.name = 'this is for update ' for update"; + final String expected = "select * from ( select a.prop from A a where a.name = 'this is for update ' ) where rownum <= ? for update"; + + final QueryParameters queryParameters = getQueryParameters( 0, 5 ); + final String processedSql = Oracle12LimitHandler.INSTANCE.processSql( sql, queryParameters ); + + assertEquals( expected, processedSql ); + } + + private QueryParameters getQueryParameters(int firstRow, int maxRow) { + final QueryParameters queryParameters = new QueryParameters(); + RowSelection rowSelection = new RowSelection(); + rowSelection.setFirstRow( firstRow ); + rowSelection.setMaxRows( maxRow ); + queryParameters.setRowSelection( rowSelection ); + return queryParameters; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/SybaseASE157LimitHandlerTest.java b/hibernate-core/src/test/java/org/hibernate/dialect/SybaseASE157LimitHandlerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ceb764d59bf292eb9c9f72adf1ea2cf990cad944 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/dialect/SybaseASE157LimitHandlerTest.java @@ -0,0 +1,54 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.dialect; + +import static org.junit.Assert.assertEquals; + +import org.hibernate.dialect.pagination.SybaseASE157LimitHandler; +import org.hibernate.engine.spi.RowSelection; +import org.junit.Test; + +public class SybaseASE157LimitHandlerTest { + + @Test + public void testLimitHandler() { + assertEquals( "select * from entity", processSql( "select * from entity", null, null ) ); + assertEquals( "select * from entity", processSql( "select * from entity", 15, null ) ); + assertEquals( "select top 15 * from entity", processSql( "select * from entity", null, 15 ) ); + assertEquals( "select top 18 * from entity", processSql( "select * from entity", 3, 15 ) ); + assertEquals( "SELECT top 18 * FROM entity", processSql( "SELECT * FROM entity", 3, 15 ) ); + assertEquals( " select top 18 * from entity", processSql( " select * from entity", 3, 15 ) ); + assertEquals( "selectand", processSql( "selectand", 3, 15 ) ); + assertEquals( "select distinct top 15 id from entity", + processSql( "select distinct id from entity", null, 15 ) ); + assertEquals( "select distinct top 18 id from entity", processSql( "select distinct id from entity", 3, 15 ) ); + assertEquals( " select distinct top 18 id from entity", + processSql( " select distinct id from entity", 3, 15 ) ); + assertEquals( + "WITH employee AS (SELECT * FROM Employees) SELECT * FROM employee WHERE ID < 20 UNION ALL SELECT * FROM employee WHERE Sex = 'M'", + processSql( + "WITH employee AS (SELECT * FROM Employees) SELECT * FROM employee WHERE ID < 20 UNION ALL SELECT * FROM employee WHERE Sex = 'M'", + 3, 15 ) ); + + assertEquals( "select top 5 * from entity", processSql( "select top 5 * from entity", 3, 15 ) ); + assertEquals( "select distinct top 7 * from entity", processSql( "select distinct top 7 * from entity", 3, 15 ) ); + assertEquals( "select distinct top 18 top_column from entity", processSql( "select distinct top_column from entity", 3, 15 ) ); + } + + private String processSql(String sql, Integer offset, Integer limit) { + RowSelection rowSelection = new RowSelection(); + if ( offset != null ) { + rowSelection.setFirstRow( offset ); + } + if (limit != null) { + rowSelection.setMaxRows( limit ); + } + + SybaseASE157LimitHandler limitHandler = new SybaseASE157LimitHandler(); + return limitHandler.processSql( sql, rowSelection ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/resolver/DialectFactoryTest.java b/hibernate-core/src/test/java/org/hibernate/dialect/resolver/DialectFactoryTest.java index faff777c0f35cbaa34760a718cdac12138695704..a0cb13fccc97e71f44beed1a8982c79a08acdf85 100644 --- a/hibernate-core/src/test/java/org/hibernate/dialect/resolver/DialectFactoryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/dialect/resolver/DialectFactoryTest.java @@ -113,7 +113,7 @@ public void testPreregisteredDialects() { testDetermination( "MySQL", 5, 5, MySQL55Dialect.class, resolver ); testDetermination( "MySQL", 5, 6, MySQL55Dialect.class, resolver ); testDetermination( "MySQL", 5, 7, MySQL57Dialect.class, resolver ); - testDetermination( "MySQL", 8, 0, MySQL57Dialect.class, resolver ); + testDetermination( "MySQL", 8, 0, MySQL8Dialect.class, resolver ); testDetermination( "MariaDB", "MariaDB connector/J", 10, 3, MariaDB103Dialect.class, resolver ); testDetermination( "MariaDB", "MariaDB connector/J", 10, 2, MariaDB102Dialect.class, resolver ); testDetermination( "MariaDB", "MariaDB connector/J", 10, 1, MariaDB10Dialect.class, resolver ); diff --git a/hibernate-core/src/test/java/org/hibernate/engine/jdbc/dialect/internal/StandardDialectResolverTest.java b/hibernate-core/src/test/java/org/hibernate/engine/jdbc/dialect/internal/StandardDialectResolverTest.java index f88414996dc8773011dc6c09a9fd5106049c296a..5858a52e18704b31fd94f53086950f66317fbc22 100644 --- a/hibernate-core/src/test/java/org/hibernate/engine/jdbc/dialect/internal/StandardDialectResolverTest.java +++ b/hibernate-core/src/test/java/org/hibernate/engine/jdbc/dialect/internal/StandardDialectResolverTest.java @@ -13,8 +13,8 @@ import java.sql.SQLException; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; /** * Unit test of the {@link StandardDialectResolver} class. @@ -44,7 +44,7 @@ public void testResolveDialectInternalForSQLServer2008() @Test public void testResolveDialectInternalForSQLServer2012() throws SQLException { - runSQLServerDialectTest( 11, SQLServer2008Dialect.class ); + runSQLServerDialectTest( 11, SQLServer2012Dialect.class ); } @Test @@ -93,7 +93,7 @@ public void testResolveDialectInternalForPostgres91() throws SQLException { @Test public void testResolveDialectInternalForPostgres92() throws SQLException { - runPostgresDialectTest( 9, 2, PostgreSQL9Dialect.class ); + runPostgresDialectTest( 9, 2, PostgreSQL92Dialect.class ); } @Test @@ -126,12 +126,38 @@ public void testResolveDialectInternalForMariaDB52() throws SQLException { runMariaDBDialectTest( 5, 2, MariaDBDialect.class ); } + @Test + public void testResolveDialectInternalForMySQL57() throws SQLException { + runMySQLDialectTest( 5, 7, MySQL57Dialect.class ); + } + + @Test + public void testResolveDialectInternalForMySQL6() throws SQLException { + runMySQLDialectTest( 6, 0, MySQL57Dialect.class ); + } + + @Test + public void testResolveDialectInternalForMySQL7() throws SQLException { + runMySQLDialectTest( 7, 0, MySQL57Dialect.class ); + } + + + @Test + public void testResolveDialectInternalForMySQL8() throws SQLException { + runMySQLDialectTest( 8, 0, MySQL8Dialect.class ); + } + private static void runMariaDBDialectTest( int majorVersion, int minorVersion, Class expectedDialect) throws SQLException { runDialectTest( "MariaDB", "MariaDB connector/J", majorVersion, minorVersion, expectedDialect ); } + private static void runMySQLDialectTest( + int majorVersion, int minorVersion, Class expectedDialect) + throws SQLException { + runDialectTest( "MySQL", "MySQL connector/J", majorVersion, minorVersion, expectedDialect ); + } private static void runSQLServerDialectTest( int version, Class expectedDialect) @@ -173,8 +199,11 @@ private static void runDialectTest( String dbms = builder.toString(); assertNotNull( "Dialect for " + dbms + " should not be null", dialect ); - assertTrue( "Dialect for " + dbms + " should be " - + expectedDialect.getSimpleName(), - expectedDialect.isInstance( dialect ) ); + // Make sure to test that the actual dialect class is as expected + // (not just an instance of the expected dialect. + assertEquals( "Dialect for " + dbms + " should be " + expectedDialect.getSimpleName(), + expectedDialect, + dialect.getClass() + ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/engine/spi/delegation/SessionDelegatorBaseImplTest.java b/hibernate-core/src/test/java/org/hibernate/engine/spi/delegation/SessionDelegatorBaseImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..800762a93271bb0065ce985fd2dbf2497d99b285 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/engine/spi/delegation/SessionDelegatorBaseImplTest.java @@ -0,0 +1,76 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.engine.spi.delegation; + +import java.sql.SQLException; +import java.sql.Statement; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.engine.spi.SessionDelegatorBaseImpl; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Andrea Boriero + */ +@RequiresDialect(H2Dialect.class) +public class SessionDelegatorBaseImplTest extends BaseCoreFunctionalTestCase { + + @Before + public void init() { + inTransaction( session -> { + session.doWork( connection -> { + try (Statement statement = connection.createStatement()) { + statement.executeUpdate( "DROP ALIAS findOneUser IF EXISTS" ); + statement.executeUpdate( + "CREATE ALIAS findOneUser AS $$\n" + + "import org.h2.tools.SimpleResultSet;\n" + + "import java.sql.*;\n" + + "@CODE\n" + + "ResultSet findOneUser() {\n" + + " SimpleResultSet rs = new SimpleResultSet();\n" + + " rs.addColumn(\"ID\", Types.INTEGER, 10, 0);\n" + + " rs.addColumn(\"NAME\", Types.VARCHAR, 255, 0);\n" + + " rs.addRow(1, \"Steve\");\n" + + " return rs;\n" + + "}\n" + + "$$" + ); + } + } ); + } ); + } + + @After + public void tearDown() { + inTransaction( session -> { + session.doWork( connection -> { + try (Statement statement = connection.createStatement()) { + statement.executeUpdate( "DROP ALIAS findOneUser IF EXISTS" ); + } + catch (SQLException e) { + //Do not ignore as failure to cleanup might lead to other tests to fail: + throw new RuntimeException( e ); + } + } ); + } ); + } + + @Test + public void testcreateStoredProcedureQuery() { + inTransaction( + session -> { + SessionDelegatorBaseImpl delegator = new SessionDelegatorBaseImpl( session ); + delegator.createStoredProcedureQuery( "findOneUser" ); + } + ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/event/EmbeddableCallbackTest.java b/hibernate-core/src/test/java/org/hibernate/event/EmbeddableCallbackTest.java index fbe07ebf5a971d407439606f164b9cd85f70f022..ad2be1ffacaf9c8cca8333ac2d4813a3eba7800a 100644 --- a/hibernate-core/src/test/java/org/hibernate/event/EmbeddableCallbackTest.java +++ b/hibernate-core/src/test/java/org/hibernate/event/EmbeddableCallbackTest.java @@ -18,11 +18,11 @@ import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; /** * @author Vlad Mihalcea */ -@TestForIssue(jiraKey = "HHH-12326") public class EmbeddableCallbackTest extends BaseEntityManagerFunctionalTestCase { @Override @@ -31,9 +31,11 @@ protected Class[] getAnnotatedClasses() { } @Test + @TestForIssue(jiraKey = "HHH-12326") public void test() { doInJPA( this::entityManagerFactory, entityManager -> { Employee employee = new Employee(); + employee.details = new EmployeeDetails(); employee.id = 1; entityManager.persist( employee ); @@ -47,6 +49,24 @@ public void test() { } ); } + @Test + @TestForIssue(jiraKey = "HHH-13110") + public void testNullEmbeddable() { + doInJPA( this::entityManagerFactory, entityManager -> { + Employee employee = new Employee(); + employee.id = 1; + + entityManager.persist( employee ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Employee employee = entityManager.find( Employee.class, 1 ); + + assertEquals( "Vlad", employee.name ); + assertNull( employee.details ); + } ); + } + @Entity(name = "Employee") public static class Employee { @@ -55,7 +75,7 @@ public static class Employee { private String name; - private EmployeeDetails details = new EmployeeDetails(); + private EmployeeDetails details; @PrePersist public void setUp() { diff --git a/hibernate-core/src/test/java/org/hibernate/id/CreateDeleteTest.java b/hibernate-core/src/test/java/org/hibernate/id/CreateDeleteTest.java index f893fccc1b6430876567cd59add1a0de35e2af11..468b511c8b3e1a8e9aefe59b94de0c9a3b28774b 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/CreateDeleteTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/CreateDeleteTest.java @@ -7,44 +7,20 @@ package org.hibernate.id; import org.hibernate.FlushMode; -import org.hibernate.action.internal.EntityAction; import org.hibernate.dialect.AbstractHANADialect; -import org.hibernate.internal.CoreMessageLogger; import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.hibernate.testing.logger.LoggerInspectionRule; -import org.hibernate.testing.logger.Triggerable; -import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.jboss.logging.Logger; - -import static junit.framework.TestCase.assertTrue; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; @RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) @TestForIssue(jiraKey = "HHH-12464") public class CreateDeleteTest extends BaseCoreFunctionalTestCase { - - @Rule - public LoggerInspectionRule logInspection = new LoggerInspectionRule( - Logger.getMessageLogger( CoreMessageLogger.class, EntityAction.class.getName() ) - ); - - private Triggerable triggerable; - - @Before - public void setUp() { - triggerable = logInspection.watchForLogMessages( - "Skipping action - the persistence context does not contain any entry for the entity" ); - triggerable.reset(); - } - @Test @SkipForDialect(value = AbstractHANADialect.class, comment = " HANA doesn't support tables consisting of only a single auto-generated column") public void createAndDeleteAnEntityInTheSameTransactionTest() { @@ -54,8 +30,6 @@ public void createAndDeleteAnEntityInTheSameTransactionTest() { session.persist( entity ); session.delete( entity ); } ); - - assertTrue( triggerable.wasTriggered() ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/id/QuotedIdentifierTest.java b/hibernate-core/src/test/java/org/hibernate/id/QuotedIdentifierTest.java index 78cb64dbb0f62f43d5d746615609ffe3a161f43d..ff75830628bb3b9f9f7bc1123ee95db3dce8b734 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/QuotedIdentifierTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/QuotedIdentifierTest.java @@ -13,39 +13,37 @@ import javax.persistence.Id; import javax.persistence.Table; -import org.hibernate.Session; -import org.hibernate.Transaction; +import org.hibernate.dialect.Oracle12cDialect; import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertNotNull; /** * @author Vlad Mihalcea */ @RequiresDialectFeature( value = DialectChecks.SupportsIdentityColumns.class, jiraKey = "HHH-9271") +@SkipForDialect(value = Oracle12cDialect.class, comment = "Oracle and identity column: java.sql.Connection#prepareStatement(String sql, int columnIndexes[]) does not work with quoted table names and/or quoted columnIndexes") public class QuotedIdentifierTest extends BaseCoreFunctionalTestCase { @Test - public void testDirectIdPropertyAccess() throws Exception { - Session s = openSession(); - Transaction transaction = s.beginTransaction(); - QuotedIdentifier o = new QuotedIdentifier(); - o.timestamp = System.currentTimeMillis(); - o.from = "HHH-9271"; - s.persist( o ); - transaction.commit(); - s.close(); + public void testDirectIdPropertyAccess() { + QuotedIdentifier quotedIdentifier = new QuotedIdentifier(); + doInHibernate( this::sessionFactory, session -> { + quotedIdentifier.timestamp = System.currentTimeMillis(); + quotedIdentifier.from = "HHH-9271"; + session.persist( quotedIdentifier ); + } ); - s = openSession(); - transaction = s.beginTransaction(); - o = session.get( QuotedIdentifier.class, o.index ); - assertNotNull(o); - transaction.commit(); - s.close(); + doInHibernate( this::sessionFactory, session -> { + QuotedIdentifier result = session.get( QuotedIdentifier.class, quotedIdentifier.index ); + assertNotNull( result ); + } ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/id/RootEntity.java b/hibernate-core/src/test/java/org/hibernate/id/RootEntity.java index 742917dc2afd0b166d994c0f1657481942c88f60..fd9cf6ed726f563df028e93b1f1d82815ee2b269 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/RootEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/id/RootEntity.java @@ -19,15 +19,18 @@ public class RootEntity implements Serializable { @Id @GeneratedValue(strategy= GenerationType.IDENTITY) - @Column(name = "universalid")// "uid" is a keywork in Oracle + @Column(name = "universalid")// "uid" is a keyword in Oracle private long uid; + public String description; + @javax.persistence.OneToMany(mappedBy = "linkedRoot") private java.util.List linkedEntities = new java.util.ArrayList(); public long getUid() { return uid; } + public void setUid(long uid) { this.uid = uid; } diff --git a/hibernate-core/src/test/java/org/hibernate/internal/MaskSensitiveInformationTest.java b/hibernate-core/src/test/java/org/hibernate/internal/MaskSensitiveInformationTest.java index 4cd9a4305b330f0f24ac0e164d6abe971824c45c..1e0c63982f0399c1f5c903a6c9648f535e384f68 100644 --- a/hibernate-core/src/test/java/org/hibernate/internal/MaskSensitiveInformationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/internal/MaskSensitiveInformationTest.java @@ -34,10 +34,8 @@ public void setUp() { } @Override - @SuppressWarnings("unchecked") protected void addConfigOptions(Map options) { - options.put( AvailableSettings.JPA_JDBC_USER, options.get( AvailableSettings.USER ) ); - options.put( AvailableSettings.JPA_JDBC_PASSWORD, options.get( AvailableSettings.PASS ) ); + super.addConfigOptions( options ); } @Test diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLHelper.java b/hibernate-core/src/test/java/org/hibernate/internal/util/xml/XMLHelper.java similarity index 34% rename from hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLHelper.java rename to hibernate-core/src/test/java/org/hibernate/internal/util/xml/XMLHelper.java index 4eb276dbfca239eb8f4a3c821d25c3c5db928ab6..599a7cd7813e42d8352132afff89e0a8f1405409 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLHelper.java +++ b/hibernate-core/src/test/java/org/hibernate/internal/util/xml/XMLHelper.java @@ -6,7 +6,8 @@ */ package org.hibernate.internal.util.xml; -import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import java.security.AccessController; +import java.security.PrivilegedAction; import org.dom4j.DocumentFactory; import org.dom4j.io.SAXReader; @@ -15,30 +16,42 @@ /** * Small helper class that lazy loads DOM and SAX reader and keep them for fast use afterwards. * - * @deprecated Currently only used for integration with HCANN. The rest of Hibernate uses StAX now - * for XML processing. See {@link org.hibernate.boot.jaxb.internal.stax} + * This was part of Hibernate ORM core, but moved into the testsuite helpers to not expose + * access to the dom4j types. It's also used by Hibernate Envers, so we will need two copies + * until Envers is able to remove its reliance on dom4j. + * The rest of Hibernate uses StAX now for XML processing. See {@link org.hibernate.boot.jaxb.internal.stax} */ -@Deprecated public final class XMLHelper { private final DocumentFactory documentFactory; - public XMLHelper(ClassLoaderService classLoaderService) { - this.documentFactory = classLoaderService.workWithClassLoader( - new ClassLoaderService.Work() { - @Override - public DocumentFactory doWork(ClassLoader classLoader) { - final ClassLoader originalTccl = Thread.currentThread().getContextClassLoader(); - try { - Thread.currentThread().setContextClassLoader( classLoader ); - return DocumentFactory.getInstance(); - } - finally { - Thread.currentThread().setContextClassLoader( originalTccl ); - } - } + public XMLHelper() { + PrivilegedAction action = new PrivilegedAction() { + public DocumentFactory run() { + final ClassLoader originalTccl = Thread.currentThread().getContextClassLoader(); + try { + // We need to make sure we get DocumentFactory + // loaded from the same ClassLoader that loads + // Hibernate classes, to make sure we get the + // proper version of DocumentFactory, This class + // is "internal", and should only be used for XML + // files generated by Envers. + + // Using the (Hibernate) ClassLoader that loads + // this Class will avoid collisions in the case + // that DocumentFactory can be loaded from, + // for example, the application ClassLoader. + Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() ); + return DocumentFactory.getInstance(); + } + finally { + Thread.currentThread().setContextClassLoader( originalTccl ); } - ); + } + }; + this.documentFactory = System.getSecurityManager() != null + ? AccessController.doPrivileged( action ) + : action.run(); } public DocumentFactory getDocumentFactory() { @@ -47,6 +60,14 @@ public DocumentFactory getDocumentFactory() { public SAXReader createSAXReader(ErrorLogger errorLogger, EntityResolver entityResolver) { SAXReader saxReader = new SAXReader(); + try { + saxReader.setFeature( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false ); + saxReader.setFeature( "http://xml.org/sax/features/external-general-entities", false ); + saxReader.setFeature( "http://xml.org/sax/features/external-parameter-entities", false ); + } + catch (Exception e) { + throw new RuntimeException( e ); + } saxReader.setMergeAdjacentText( true ); saxReader.setValidation( true ); saxReader.setErrorHandler( errorLogger ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java index 7a0ecd4ca91b5e5e9422dc234b8c99128fe891a9..4f0648baac3e503ee2c7d61114b537e084f5596b 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; + import javax.persistence.EntityManager; import javax.persistence.SharedCacheMode; import javax.persistence.ValidationMode; @@ -21,20 +22,17 @@ import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Environment; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.HibernatePersistenceProvider; import org.hibernate.jpa.boot.spi.Bootstrap; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; - import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.After; import org.junit.Before; -import org.jboss.logging.Logger; - /** * A base class for all ejb tests. * @@ -209,6 +207,8 @@ protected Map getConfig() { Map config = Environment.getProperties(); ArrayList classes = new ArrayList(); + config.put( AvailableSettings.CLASSLOADERS, getClass().getClassLoader() ); + classes.addAll( Arrays.asList( getAnnotatedClasses() ) ); config.put( AvailableSettings.LOADED_CLASSES, classes ); for ( Map.Entry entry : getCachedClasses().entrySet() ) { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/JpaProxyComplianceWithDebug.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/JpaProxyComplianceWithDebug.java new file mode 100644 index 0000000000000000000000000000000000000000..d4d04b23e9d22cbb506e4bea9769597b8737ac00 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/JpaProxyComplianceWithDebug.java @@ -0,0 +1,239 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.testing.TestForIssue; +import org.junit.Before; +import org.junit.Test; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; + +@TestForIssue(jiraKey = "HHH-13244") +public class JpaProxyComplianceWithDebug extends BaseEntityManagerFunctionalTestCase { + + @Override + protected void addConfigOptions(Map options) { + options.put( + AvailableSettings.JPA_PROXY_COMPLIANCE, + Boolean.TRUE); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + MvnoBillingAgreement.class, + MvnoOpcio.class, + }; + } + + @Before + public void setUp() { + List opciok = Arrays.asList(2008, 2010, 2012, 2014, 2015, 2026, 2027, 2103, 2110, 2145, 992068, 992070); + + doInJPA(this::entityManagerFactory, entityManager -> { + + MvnoBillingAgreement ba = new MvnoBillingAgreement(); + ba.setId(1); + ba.setName("1"); + entityManager.persist(ba); + + for (int opcioId : opciok) { + MvnoOpcio o = new MvnoOpcio(); + o.setId(opcioId); + o.setMegnevezes(Integer.toString(opcioId)); + o.getMvnoBillingAgreementekDefaultOpcioja().add(ba); + ba.getMvnoDefaultUniverzalisOpcioi().add(o); + entityManager.persist(o); + } + + ba.setBehajtasEgyiranyusitasOpcio(entityManager.find(MvnoOpcio.class, 2026)); + ba.setBehajtasFelfuggesztesOpcio(entityManager.find(MvnoOpcio.class, 992070)); + ba.setHotlimitEmeltDijasBarOpcio(entityManager.find(MvnoOpcio.class, 2145)); + + }); + } + + + @Test + @TestForIssue(jiraKey = "HHH-13244") + public void testJpaComplianceProxyWithDebug() { + LoggerContext context = (LoggerContext) LogManager.getContext( false ); + Configuration configuration = context.getConfiguration(); + + //This could be replaced with setting the root logger level, or the "org.hibernate" logger to debug. + //These are simply the narrowest log settings that trigger the bug + LoggerConfig entityLogger = configuration.getLoggerConfig( "org.hibernate.internal.util.EntityPrinter"); + LoggerConfig listenerLogger = configuration.getLoggerConfig("org.hibernate.event.internal.AbstractFlushingEventListener"); + + Level oldEntityLogLevel = entityLogger.getLevel(); + Level oldListenerLogLevel = listenerLogger.getLevel(); + + entityLogger.setLevel(Level.DEBUG); + listenerLogger.setLevel(Level.DEBUG); + try { + doInJPA(this::entityManagerFactory, entityManager -> { + entityManager.find(MvnoBillingAgreement.class, 1); + }); + } finally { + entityLogger.setLevel(oldEntityLogLevel); + listenerLogger.setLevel(oldListenerLogLevel); + } + + } + + @Entity + @Table(name = "mvno_billing_agreement") + public static class MvnoBillingAgreement implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + private int id; + + private String name; + + @ManyToMany + @JoinTable( + name = "mvnobillagr_def_univerzalis", joinColumns = { + @JoinColumn(name = "billing_agreement_id") + }, + inverseJoinColumns = { + @JoinColumn(name = "univerzalis_opcio_id") + }) + private Set mvnoDefaultUniverzalisOpcioi = new HashSet<>(); + + @JoinColumn(name = "egyiranyusitas_opcio_id") + @ManyToOne(fetch = FetchType.LAZY) + private MvnoOpcio behajtasEgyiranyusitasOpcio; + + @JoinColumn(name = "felfuggesztes_opcio_id") + @ManyToOne(fetch = FetchType.LAZY) + private MvnoOpcio behajtasFelfuggesztesOpcio; + + @JoinColumn(name = "emeltdijas_bar_opcio_id") + @ManyToOne(fetch = FetchType.LAZY) + private MvnoOpcio hotlimitEmeltDijasBarOpcio; + + public MvnoBillingAgreement() {} + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getMvnoDefaultUniverzalisOpcioi() { + return this.mvnoDefaultUniverzalisOpcioi; + } + + public void setMvnoDefaultUniverzalisOpcioi(Set mvnoDefaultUniverzalisOpcioi) { + this.mvnoDefaultUniverzalisOpcioi = mvnoDefaultUniverzalisOpcioi; + } + + public MvnoOpcio getBehajtasEgyiranyusitasOpcio() { + return this.behajtasEgyiranyusitasOpcio; + } + + public void setBehajtasEgyiranyusitasOpcio(MvnoOpcio behajtasEgyiranyusitasOpcio) { + this.behajtasEgyiranyusitasOpcio = behajtasEgyiranyusitasOpcio; + } + + public MvnoOpcio getBehajtasFelfuggesztesOpcio() { + return this.behajtasFelfuggesztesOpcio; + } + + public void setBehajtasFelfuggesztesOpcio(MvnoOpcio behajtasFelfuggesztesOpcio) { + this.behajtasFelfuggesztesOpcio = behajtasFelfuggesztesOpcio; + } + + public MvnoOpcio getHotlimitEmeltDijasBarOpcio() { + return this.hotlimitEmeltDijasBarOpcio; + } + + public void setHotlimitEmeltDijasBarOpcio(MvnoOpcio hotlimitEmeltDijasBarOpcio) { + this.hotlimitEmeltDijasBarOpcio = hotlimitEmeltDijasBarOpcio; + } + + } + + @Entity + @Table(name = "mvno_opcio") + public static class MvnoOpcio implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + private int id; + + @Column(name = "megnevezes") + private String megnevezes; + + @ManyToMany(mappedBy = "mvnoDefaultUniverzalisOpcioi") + private Set mvnoBillingAgreementekDefaultOpcioja = new HashSet<>(); + + public MvnoOpcio() {} + + public int getId() { + return this.id; + } + + public void setId(int id) { + this.id = id; + } + + public String getMegnevezes() { + return this.megnevezes; + } + + public void setMegnevezes(String megnevezes) { + this.megnevezes = megnevezes; + } + + public Set getMvnoBillingAgreementekDefaultOpcioja() { + return this.mvnoBillingAgreementekDefaultOpcioja; + } + + public void setMvnoBillingAgreementekDefaultOpcioja(Set mvnoBillingAgreementekDefaultOpcioja) { + this.mvnoBillingAgreementekDefaultOpcioja = mvnoBillingAgreementekDefaultOpcioja; + } + + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/DeprecatedProviderCheckerTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/DeprecatedProviderCheckerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b4f7075775053a6ab41a5090dc8f5c1fb7f5a302 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/boot/DeprecatedProviderCheckerTest.java @@ -0,0 +1,50 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.boot; + + +import org.hibernate.internal.HEMLogging; +import org.hibernate.jpa.boot.spi.ProviderChecker; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Rule; +import org.junit.Test; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Tests that deprecated (and removed) provider, "org.hibernate.ejb.HibernatePersistence", + * is recognized as a Hibernate persistence provider. + * + * @author Gail Badner + */ +public class DeprecatedProviderCheckerTest { + final static String DEPRECATED_PROVIDER_NAME = "org.hibernate.ejb.HibernatePersistence"; + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + HEMLogging.messageLogger( ProviderChecker.class.getName() ) + ); + + @Test + @TestForIssue( jiraKey = "HHH-13027") + public void testDeprecatedProvider() { + Triggerable triggerable = logInspection.watchForLogMessages( "HHH015016" ); + triggerable.reset(); + assertTrue( ProviderChecker.hibernateProviderNamesContain( DEPRECATED_PROVIDER_NAME ) ); + triggerable.wasTriggered(); + assertEquals( + "HHH015016: Encountered a deprecated javax.persistence.spi.PersistenceProvider [org.hibernate.ejb.HibernatePersistence]; [org.hibernate.jpa.HibernatePersistenceProvider] will be used instead.", + triggerable.triggerMessage() + ); + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateBytecodeEnhancementTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateBytecodeEnhancementTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0e9ab1b3215135fbf90f2c5c30ed16202c876bac --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateBytecodeEnhancementTest.java @@ -0,0 +1,127 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.callbacks; + +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import javax.persistence.Basic; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@TestForIssue(jiraKey = "HHH-12718") +@RunWith(BytecodeEnhancerRunner.class) +public class PreUpdateBytecodeEnhancementTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Override + protected void addConfigOptions(Map options) { + options.put( AvailableSettings.CLASSLOADERS, getClass().getClassLoader() ); + options.put( AvailableSettings.ENHANCER_ENABLE_LAZY_INITIALIZATION, "true" ); + options.put( AvailableSettings.ENHANCER_ENABLE_DIRTY_TRACKING, "true" ); + } + + @Test + public void testPreUpdateModifications() { + Person person = new Person(); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( person ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertNotNull( p ); + assertNotNull( p.createdAt ); + assertNull( p.lastUpdatedAt ); + + p.setName( "Changed Name" ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertNotNull( p.lastUpdatedAt ); + } ); + } + + @Entity(name = "Person") + private static class Person { + @Id + @GeneratedValue + private int id; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private Instant createdAt; + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + private Instant lastUpdatedAt; + + public Instant getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(Instant lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @ElementCollection + private List tags; + + @Lob + @Basic(fetch = FetchType.LAZY) + private ByteBuffer image; + + @PrePersist + void beforeCreate() { + this.setCreatedAt( Instant.now() ); + } + + @PreUpdate + void beforeUpdate() { + this.setLastUpdatedAt( Instant.now() ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateCustomEntityDirtinessStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateCustomEntityDirtinessStrategyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..bb45944c8112387683c08b86baa7c8a8b5a3cf03 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateCustomEntityDirtinessStrategyTest.java @@ -0,0 +1,180 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.callbacks; + +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import javax.persistence.Basic; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; + +import org.hibernate.CustomEntityDirtinessStrategy; +import org.hibernate.Session; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.persister.entity.EntityPersister; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +@TestForIssue(jiraKey = "HHH-12718") +public class PreUpdateCustomEntityDirtinessStrategyTest + extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Test + public void testPreUpdateModifications() { + Person person = new Person(); + + doInHibernate( this::sessionFactory, session -> { + session.persist( person ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Person p = session.find( Person.class, person.id ); + assertNotNull( p ); + assertNotNull( p.createdAt ); + assertNull( p.lastUpdatedAt ); + + p.setName( "Changed Name" ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Person p = session.find( Person.class, person.id ); + assertNotNull( p.lastUpdatedAt ); + } ); + + assertTrue( DefaultCustomEntityDirtinessStrategy.INSTANCE.isPersonNameChanged() ); + assertTrue( DefaultCustomEntityDirtinessStrategy.INSTANCE.isPersonLastUpdatedAtChanged() ); + } + + @Override + protected void addSettings(Map settings) { + settings.put( AvailableSettings.CUSTOM_ENTITY_DIRTINESS_STRATEGY, DefaultCustomEntityDirtinessStrategy.INSTANCE ); + } + + public static class DefaultCustomEntityDirtinessStrategy + implements CustomEntityDirtinessStrategy { + private static final DefaultCustomEntityDirtinessStrategy INSTANCE = + new DefaultCustomEntityDirtinessStrategy(); + + private boolean personNameChanged = false; + private boolean personLastUpdatedAtChanged = false; + + @Override + public boolean canDirtyCheck(Object entity, EntityPersister persister, Session session) { + return true; + } + + @Override + public boolean isDirty(Object entity, EntityPersister persister, Session session) { + Person person = (Person) entity; + if ( !personNameChanged ) { + personNameChanged = person.getName() != null; + return personNameChanged; + } + if ( !personLastUpdatedAtChanged ) { + personLastUpdatedAtChanged = person.getLastUpdatedAt() != null; + return personLastUpdatedAtChanged; + } + return false; + } + + @Override + public void resetDirty(Object entity, EntityPersister persister, Session session) { + } + + @Override + public void findDirty( + Object entity, + EntityPersister persister, + Session session, + DirtyCheckContext dirtyCheckContext) { + } + + public boolean isPersonNameChanged() { + return personNameChanged; + } + + public boolean isPersonLastUpdatedAtChanged() { + return personLastUpdatedAtChanged; + } + } + + @Entity(name = "Person") + @DynamicUpdate + private static class Person { + @Id + @GeneratedValue + private int id; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private Instant createdAt; + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + private Instant lastUpdatedAt; + + public Instant getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(Instant lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @ElementCollection + private List tags; + + @Lob + @Basic(fetch = FetchType.LAZY) + private ByteBuffer image; + + @PrePersist + void beforeCreate() { + this.setCreatedAt( Instant.now() ); + } + + @PreUpdate + void beforeUpdate() { + this.setLastUpdatedAt( Instant.now() ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateDirtyCheckingInterceptorTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateDirtyCheckingInterceptorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..93a3f80697213c5250879e19809f1226c21d95d1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateDirtyCheckingInterceptorTest.java @@ -0,0 +1,155 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.callbacks; + +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.List; +import java.util.Objects; +import javax.persistence.Basic; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; + +import org.hibernate.EmptyInterceptor; +import org.hibernate.SessionBuilder; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.type.Type; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernateSessionBuilder; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@TestForIssue(jiraKey = "HHH-12718") +public class PreUpdateDirtyCheckingInterceptorTest + extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + @Test + public void testPreUpdateModifications() { + Person person = new Person(); + + doInHibernate( this::sessionFactory, session -> { + session.persist( person ); + } ); + + doInHibernateSessionBuilder( this::sessionWithInterceptor, session -> { + Person p = session.find( Person.class, person.id ); + assertNotNull( p ); + assertNotNull( p.createdAt ); + assertNull( p.lastUpdatedAt ); + + p.setName( "Changed Name" ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Person p = session.find( Person.class, person.id ); + assertNotNull( p.lastUpdatedAt ); + } ); + } + + public static class OnFlushDirtyInterceptor extends EmptyInterceptor { + + private static OnFlushDirtyInterceptor INSTANCE = new OnFlushDirtyInterceptor(); + + @Override + public int[] findDirty( + Object entity, + Serializable id, + Object[] currentState, + Object[] previousState, + String[] propertyNames, + Type[] types) { + int[] result = new int[propertyNames.length]; + int span = 0; + + for ( int i = 0; i < previousState.length; i++ ) { + if( !Objects.deepEquals(previousState[i], currentState[i])) { + result[span++] = i; + } + } + + return result; + } + } + + private SessionBuilder sessionWithInterceptor() { + return sessionFactory() + .withOptions() + .interceptor( OnFlushDirtyInterceptor.INSTANCE ); + } + + @Entity(name = "Person") + @DynamicUpdate + private static class Person { + @Id + @GeneratedValue + private int id; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private Instant createdAt; + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + private Instant lastUpdatedAt; + + public Instant getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(Instant lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @ElementCollection + private List tags; + + @Lob + @Basic(fetch = FetchType.LAZY) + private ByteBuffer image; + + @PrePersist + void beforeCreate() { + this.setCreatedAt( Instant.now() ); + } + + @PreUpdate + void beforeUpdate() { + this.setLastUpdatedAt( Instant.now() ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewBidirectionalBagTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewBidirectionalBagTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4d7a2ab322d366c560a9395f830398d082a2dc68 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewBidirectionalBagTest.java @@ -0,0 +1,120 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.callbacks; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.PreUpdate; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@TestForIssue(jiraKey = "HHH-13466") +public class PreUpdateNewBidirectionalBagTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class, Tag.class }; + } + + @Test + public void testPreUpdateModifications() { + Person person = new Person(); + person.id = 1; + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( person ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertNotNull( p ); + final Tag tag = new Tag(); + tag.id = 2; + tag.description = "description"; + tag.person = p; + final Set tags = new HashSet(); + tags.add( tag ); + p.tags = tags; + entityManager.merge( p ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertEquals( 1, p.tags.size() ); + assertEquals( "description", p.tags.iterator().next().description ); + assertNotNull( p.getLastUpdatedAt() ); + } ); + } + + @Entity(name = "Person") + @EntityListeners( PersonListener.class ) + private static class Person { + @Id + private int id; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private Instant lastUpdatedAt; + + public Instant getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(Instant lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private Collection tags = new ArrayList(); + } + + @Entity(name = "Tag") + public static class Tag { + + @Id + private int id; + + private String description; + + @ManyToOne + private Person person; + } + + public static class PersonListener { + @PreUpdate + void onPreUpdate(Object o) { + if ( Person.class.isInstance( o ) ) { + ( (Person) o ).setLastUpdatedAt( Instant.now() ); + } + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewUnidirectionalBagTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewUnidirectionalBagTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c7febd65366ac42bbba708e0bf0c6bcaa7f4e1e4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewUnidirectionalBagTest.java @@ -0,0 +1,115 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.callbacks; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.PreUpdate; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@TestForIssue(jiraKey = "HHH-13466") +public class PreUpdateNewUnidirectionalBagTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class, Tag.class }; + } + + @Test + public void testPreUpdateModifications() { + Person person = new Person(); + person.id = 1; + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( person ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertNotNull( p ); + final Tag tag = new Tag(); + tag.id = 2; + tag.description = "description"; + final Set tags = new HashSet(); + tags.add( tag ); + p.tags = tags; + entityManager.merge( p ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertEquals( 1, p.tags.size() ); + assertEquals( "description", p.tags.iterator().next().description ); + assertNotNull( p.getLastUpdatedAt() ); + } ); + } + + @Entity(name = "Person") + @EntityListeners( PersonListener.class ) + private static class Person { + @Id + private int id; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private Instant lastUpdatedAt; + + public Instant getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(Instant lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private Collection tags = new ArrayList(); + } + + @Entity(name = "Tag") + public static class Tag { + + @Id + private int id; + + private String description; + } + + public static class PersonListener { + @PreUpdate + void onPreUpdate(Object o) { + if ( Person.class.isInstance( o ) ) { + ( (Person) o ).setLastUpdatedAt( Instant.now() ); + } + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewUnidirectionalIdBagTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewUnidirectionalIdBagTest.java new file mode 100644 index 0000000000000000000000000000000000000000..be3325a2e6b0a5e76908b12e9ca0259c289ba4b4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PreUpdateNewUnidirectionalIdBagTest.java @@ -0,0 +1,124 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.callbacks; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.PreUpdate; + +import org.hibernate.annotations.CollectionId; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Type; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@TestForIssue(jiraKey = "HHH-13466") +public class PreUpdateNewUnidirectionalIdBagTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class, Tag.class }; + } + + @Test + public void testPreUpdateModifications() { + Person person = new Person(); + person.id = 1; + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( person ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertNotNull( p ); + final Tag tag = new Tag(); + tag.id = 2; + tag.description = "description"; + final Set tags = new HashSet(); + tags.add( tag ); + p.tags = tags; + entityManager.merge( p ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Person p = entityManager.find( Person.class, person.id ); + assertEquals( 1, p.tags.size() ); + assertEquals( "description", p.tags.iterator().next().description ); + assertNotNull( p.getLastUpdatedAt() ); + } ); + } + + @Entity(name = "Person") + @EntityListeners( PersonListener.class ) + @GenericGenerator(name="increment", strategy = "increment") + private static class Person { + @Id + private int id; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private Instant lastUpdatedAt; + + public Instant getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(Instant lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @CollectionId( + columns = @Column(name = "n_key_tag"), + type = @Type(type = "long"), + generator = "increment" ) + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private Collection tags = new ArrayList(); + } + + @Entity(name = "Tag") + public static class Tag { + + @Id + private int id; + + private String description; + } + + public static class PersonListener { + @PreUpdate + void onPreUpdate(Object o) { + if ( Person.class.isInstance( o ) ) { + ( (Person) o ).setLastUpdatedAt( Instant.now() ); + } + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/DataSourceInjectionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/DataSourceInjectionTest.java index 8a113e016f89e4618c332600b53f6b7701605578..8c1125218c9dbbffee60a54e08890606364d58a3 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/DataSourceInjectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/DataSourceInjectionTest.java @@ -9,53 +9,106 @@ package org.hibernate.jpa.test.connection; -import java.io.File; -import javax.persistence.EntityManagerFactory; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; import javax.persistence.PersistenceException; +import javax.sql.DataSource; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jpa.HibernatePersistenceProvider; +import org.hibernate.jpa.test.Distributor; +import org.hibernate.jpa.test.Item; +import org.hibernate.jpa.test.xml.Light; +import org.hibernate.jpa.test.xml.Lighter; +import org.hibernate.testing.util.jpa.PersistenceUnitInfoAdapter; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; /** * @author Emmanuel Bernard */ public class DataSourceInjectionTest { - EntityManagerFactory emf; @Test public void testDatasourceInjection() throws Exception { - File current = new File("."); - File sub = new File(current, "puroot"); - sub.mkdir(); - PersistenceUnitInfoImpl info = new PersistenceUnitInfoImpl( sub.toURI().toURL(), new String[]{} ); - try { - emf = new HibernatePersistenceProvider().createContainerEntityManagerFactory( info, null ); - try { - emf.createEntityManager().createQuery( "select i from Item i" ).getResultList(); - } - finally { - try { - emf.close(); - } - catch (Exception ignore) { - int i = 0; + withPuRoot( + puRootUrl -> { + final PersistenceUnitInfoAdapter persistenceUnitInfo = createPuDescriptor( puRootUrl, new FakeDataSource() ); + + // otherwise the FakeDataSourceException will be eaten trying to resolve the Dialect + final Map intgOverrides = Collections.singletonMap( + AvailableSettings.DIALECT, + H2Dialect.class + ); + + final HibernatePersistenceProvider provider = new HibernatePersistenceProvider(); + try ( final SessionFactoryImplementor sf = provider.createContainerEntityManagerFactory( + persistenceUnitInfo, + intgOverrides + ).unwrap( SessionFactoryImplementor.class ) ) { + + try ( final SessionImplementor session = sf.openSession().unwrap( SessionImplementor.class ) ) { + session.createQuery( "select i from Item i" ).list(); + Assert.fail( "Expecting FakeDataSourceException" ); + } + catch (PersistenceException pe) { + try { + throw (RuntimeException) pe.getCause(); + } + catch (FakeDataSourceException fde) { + //success + } + } + catch (FakeDataSourceException fde) { + //success + } + } } + ); + } + + protected PersistenceUnitInfoAdapter createPuDescriptor(URL puRootUrl, DataSource dataSource) { + return new PersistenceUnitInfoAdapter() { + @Override + public DataSource getNonJtaDataSource() { + return dataSource; } - Assert.fail( "FakeDatasource should have been used" ); - } - catch (PersistenceException pe) { - if(emf != null){ - emf.close(); + + @Override + public URL getPersistenceUnitRootUrl() { + return puRootUrl; } - Assert.assertTrue( pe.getCause() instanceof FakeDataSourceException ); - } - catch (FakeDataSourceException fde) { - //success + + public List getManagedClassNames() { + List classes = new ArrayList<>(); + classes.add( Item.class.getName() ); + classes.add( Distributor.class.getName() ); + classes.add( Light.class.getName() ); + classes.add( Lighter.class.getName() ); + return classes; + } + }; + } + + private void withPuRoot(Consumer puRootUrlConsumer) throws Exception { + // create a temporary directory to serve as the "PU root URL" + final Path puroot = Files.createTempDirectory( "puroot" ); + final URL puRootUrl = puroot.toUri().toURL(); + + try { + puRootUrlConsumer.accept( puRootUrl ); } finally { - sub.delete(); + Files.deleteIfExists( puroot ); } } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/PersistenceUnitInfoImpl.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/PersistenceUnitInfoImpl.java index cec9460c9793774f5f7317a8ebcde76d2491e5c0..8e8ed9c0e26439d2d8675d94b99b5a4e060eb9c4 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/PersistenceUnitInfoImpl.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/connection/PersistenceUnitInfoImpl.java @@ -37,7 +37,7 @@ public class PersistenceUnitInfoImpl implements PersistenceUnitInfo { private URL puRoot; public PersistenceUnitInfoImpl(URL puRoot, String[] mappingFiles) { - this.mappingFiles = new ArrayList( mappingFiles.length ); + this.mappingFiles = new ArrayList<>( mappingFiles.length ); this.mappingFiles.addAll( Arrays.asList( mappingFiles ) ); this.puRoot = puRoot; } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/MultiTypedBasicAttributesEntity.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/MultiTypedBasicAttributesEntity.java index 3267b8b0e229126cfc6303e6aefee12ec0982aa1..a8ece15d23f3dcc3f01365c74f0a51deb09820e0 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/MultiTypedBasicAttributesEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/MultiTypedBasicAttributesEntity.java @@ -26,8 +26,8 @@ public class MultiTypedBasicAttributesEntity { @GeneratedValue( generator = "increment" ) @GenericGenerator( name = "increment", strategy = "increment" ) private Long id; - private byte[] someBytes; - private Byte[] someWrappedBytes; + private int[] someInts; + private Integer[] someWrappedIntegers; public Long getId() { return id; @@ -37,19 +37,19 @@ public void setId(Long id) { this.id = id; } - public byte[] getSomeBytes() { - return someBytes; + public int[] getSomeInts() { + return someInts; } - public void setSomeBytes(byte[] someBytes) { - this.someBytes = someBytes; + public void setSomeInts(int[] someInts) { + this.someInts = someInts; } - public Byte[] getSomeWrappedBytes() { - return someWrappedBytes; + public Integer[] getSomeWrappedIntegers() { + return someWrappedIntegers; } - public void setSomeWrappedBytes(Byte[] someWrappedBytes) { - this.someWrappedBytes = someWrappedBytes; + public void setSomeWrappedIntegers(Integer[] someWrappedIntegers) { + this.someWrappedIntegers = someWrappedIntegers; } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ParameterTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ParameterTest.java index 231466791896e8989ffc1004d4964cb4b4a4236a..7a201372b94ff08bb818c8fa2b28aa98eb764b87 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ParameterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/ParameterTest.java @@ -40,12 +40,12 @@ public void testPrimitiveArrayParameterBinding() { CriteriaQuery criteria = em.getCriteriaBuilder() .createQuery( MultiTypedBasicAttributesEntity.class ); Root rootEntity = criteria.from( MultiTypedBasicAttributesEntity.class ); - Path someBytesPath = rootEntity.get( MultiTypedBasicAttributesEntity_.someBytes ); - ParameterExpression param = em.getCriteriaBuilder().parameter( byte[].class, "theBytes" ); - criteria.where( em.getCriteriaBuilder().equal( someBytesPath, param ) ); + Path someIntsPath = rootEntity.get( MultiTypedBasicAttributesEntity_.someInts ); + ParameterExpression param = em.getCriteriaBuilder().parameter( int[].class, "theInts" ); + criteria.where( em.getCriteriaBuilder().equal( someIntsPath, param ) ); TypedQuery query = em.createQuery( criteria ); - query.setParameter( param, new byte[] { 1,1,1 } ); - assertThat( query.getParameterValue( param.getName() ), instanceOf( byte[].class) ); + query.setParameter( param, new int[] { 1,1,1 } ); + assertThat( query.getParameterValue( param.getName() ), instanceOf( int[].class) ); query.getResultList(); em.getTransaction().commit(); em.close(); @@ -58,12 +58,12 @@ public void testNonPrimitiveArrayParameterBinding() { CriteriaQuery criteria = em.getCriteriaBuilder() .createQuery( MultiTypedBasicAttributesEntity.class ); Root rootEntity = criteria.from( MultiTypedBasicAttributesEntity.class ); - Path thePath = rootEntity.get( MultiTypedBasicAttributesEntity_.someWrappedBytes ); - ParameterExpression param = em.getCriteriaBuilder().parameter( Byte[].class, "theBytes" ); + Path thePath = rootEntity.get( MultiTypedBasicAttributesEntity_.someWrappedIntegers ); + ParameterExpression param = em.getCriteriaBuilder().parameter( Integer[].class, "theIntegers" ); criteria.where( em.getCriteriaBuilder().equal( thePath, param ) ); TypedQuery query = em.createQuery( criteria ); - query.setParameter( param, new Byte[] { Byte.valueOf((byte)1), Byte.valueOf((byte)1), Byte.valueOf((byte)1) } ); - assertThat( query.getParameterValue( param.getName() ), instanceOf( Byte[].class ) ); + query.setParameter( param, new Integer[] { Integer.valueOf(1), Integer.valueOf(1), Integer.valueOf(1) } ); + assertThat( query.getParameterValue( param.getName() ), instanceOf( Integer[].class ) ); query.getResultList(); em.getTransaction().commit(); em.close(); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/basic/PredicateTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/basic/PredicateTest.java index bd21d68e8bdc2f89a2d8b5988fbf4ed54a1254cd..c88117627659f783301edd476f7607c3a06c644d 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/basic/PredicateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/basic/PredicateTest.java @@ -6,7 +6,11 @@ */ package org.hibernate.jpa.test.criteria.basic; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.util.List; + import javax.persistence.EntityManager; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -14,20 +18,20 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; -import org.junit.Before; -import org.junit.Test; - +import org.hibernate.dialect.Oracle12cDialect; +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.dialect.Oracle9Dialect; +import org.hibernate.dialect.OracleDialect; import org.hibernate.jpa.test.metamodel.AbstractMetamodelSpecificTest; import org.hibernate.jpa.test.metamodel.CreditCard; import org.hibernate.jpa.test.metamodel.CreditCard_; import org.hibernate.jpa.test.metamodel.Customer_; import org.hibernate.jpa.test.metamodel.Order; import org.hibernate.jpa.test.metamodel.Order_; - +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; /** * Test the various predicates. @@ -211,7 +215,7 @@ public void testCharArray() { em.getTransaction().begin(); CriteriaQuery orderCriteria = builder.createQuery( Order.class ); Root orderRoot = orderCriteria.from( Order.class ); - + orderCriteria.select( orderRoot ); Predicate p = builder.equal( orderRoot.get( "domen" ), new char[]{'r','u'} ); orderCriteria.where( p ); @@ -223,15 +227,17 @@ public void testCharArray() { } /** - * Check predicate for field which has simple char array type (byte[]). + * Check predicate for field which has simple byte array type (byte[]). */ @Test + @SkipForDialect(value = Oracle12cDialect.class, jiraKey = "HHH-10603", + comment = "Oracle12cDialect uses blob to store byte arrays and it's not possible to compare blobs with simple equality operators.") public void testByteArray() { EntityManager em = getOrCreateEntityManager(); em.getTransaction().begin(); CriteriaQuery orderCriteria = builder.createQuery( Order.class ); Root orderRoot = orderCriteria.from( Order.class ); - + orderCriteria.select( orderRoot ); Predicate p = builder.equal( orderRoot.get( "number" ), new byte[]{'1','2'} ); orderCriteria.where( p ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/AbstractCriteriaLiteralHandlingModeTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/AbstractCriteriaLiteralHandlingModeTest.java index 2df54e6482c3d7b9bbc236bba217c76bfc59eb86..1b5dab054677ee4162d551c85c28d9ab8bd032c0 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/AbstractCriteriaLiteralHandlingModeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/AbstractCriteriaLiteralHandlingModeTest.java @@ -20,41 +20,27 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Before; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.hibernate.testing.transaction.TransactionUtil.setJdbcTimeout; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; /** * @author Vlad Mihalcea */ public abstract class AbstractCriteriaLiteralHandlingModeTest extends BaseEntityManagerFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider; + private SQLStatementInterceptor sqlStatementInterceptor; @Override - protected Map getConfig() { - Map config = super.getConfig(); - config.put( - org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - return config; - } - - @Override - public void buildEntityManagerFactory() { - connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); - super.buildEntityManagerFactory(); - } - - @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); } @Override @@ -101,12 +87,12 @@ public void testLiteralHandlingMode() throws Exception { entity.get( "name" ) ); - connectionProvider.clear(); - List tuples = entityManager.createQuery( query ) - .getResultList(); + sqlStatementInterceptor.clear(); + + List tuples = entityManager.createQuery( query ).getResultList(); assertEquals( 1, tuples.size() ); - assertNotNull( connectionProvider.getPreparedStatement( expectedSQL() ) ); + sqlStatementInterceptor.assertExecuted( expectedSQL() ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralWithSingleQuoteTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralWithSingleQuoteTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c14b3c4dd717cb8472f3aa00466ed37ad77d5007 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralWithSingleQuoteTest.java @@ -0,0 +1,140 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria.literal; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; + +import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseASE157Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.TestForIssue; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +@TestForIssue( jiraKey = "HHH-14077") +public class CriteriaLiteralWithSingleQuoteTest extends BaseEntityManagerFunctionalTestCase { + + @Test + public void literalSingleQuoteTest() throws Exception { + + doInJPA( + this::entityManagerFactory, + entityManager -> { + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(); + query.select( cb.literal( '\'' ) ).from( Student.class ); + Object object = entityManager.createQuery( query ).getSingleResult(); + assertEquals( "'", object ); + } + ); + } + + @Test + public void literalProjectionTest() throws Exception { + + doInJPA( + this::entityManagerFactory, + entityManager -> { + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(); + query.multiselect( cb.literal( "' || aValue || '" ) ).from( Student.class ); + Object object = entityManager.createQuery( query ).getSingleResult(); + assertEquals( "' || aValue || '", object ); + } + ); + } + + @Test + @SkipForDialect(value = SybaseASE157Dialect.class, jiraKey = "HHH-14669") + @SkipForDialect(value = PostgreSQL81Dialect.class, comment = "PostgreSQL does not support literals in group by statement") + public void testLiteralProjectionAndGroupBy() throws Exception { + doInJPA( + this::entityManagerFactory, + entityManager -> { + + final String literal = "' || aValue || '"; + + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(); + query.multiselect( cb.literal( literal ) ) + .from( Student.class ); + query.groupBy( cb.literal( literal ) ); + + Object object = entityManager.createQuery( query ).getSingleResult(); + assertEquals( literal, object ); + } + ); + } + + @Before + public void setupData() { + doInJPA( + this::entityManagerFactory, + entityManager -> { + Student student = new Student(); + student.setAValue( "A Value" ); + entityManager.persist( student ); + } + ); + } + + @After + public void cleanupData() { + doInJPA( + this::entityManagerFactory, + entityManager -> { + entityManager.createQuery( "delete from Student" ); + } + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Student.class }; + } + + @Entity(name = "Student") + @Table(name = "Students") + public static class Student { + + @Id + @GeneratedValue + private Long id; + + @Column + private String aValue; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + this.id = id; + } + + public String getAValue() { + return aValue; + } + + public void setAValue(String value) { + this.aValue = value; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java index 8d4b0a06ce6a47403d604c257141641833b50f17..b38f9ab95efb805e17515f728bfea7c86019f10c 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/nulliteral/CriteriaLiteralsTest.java @@ -29,7 +29,7 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.RequiresDialect; -import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; import org.junit.Before; import org.junit.Test; @@ -44,29 +44,12 @@ */ @RequiresDialect(H2Dialect.class) public class CriteriaLiteralsTest extends BaseEntityManagerFunctionalTestCase { - - private PreparedStatementSpyConnectionProvider connectionProvider; - @Override - protected Map getConfig() { - Map config = super.getConfig(); - config.put( - org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - return config; - } - - @Override - public void buildEntityManagerFactory() { - connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); - super.buildEntityManagerFactory(); - } + private SQLStatementInterceptor sqlStatementInterceptor; @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); } @Override @@ -128,16 +111,15 @@ public void testLiteralsInWhereClause() throws Exception { entity.get( "name" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.clear(); List tuples = entityManager.createQuery( query ) .getResultList(); assertEquals( 1, - connectionProvider.getPreparedStatements().size() + sqlStatementInterceptor.getSqlQueries().size() ); - assertNotNull( connectionProvider.getPreparedStatement( - "select 'abc' as col_0_0_, criteriali0_.name as col_1_0_ from Book criteriali0_ where criteriali0_.name=?" ) ); + sqlStatementInterceptor.assertExecuted("select 'abc' as col_0_0_, criteriali0_.name as col_1_0_ from Book criteriali0_ where criteriali0_.name=?"); assertTrue( tuples.isEmpty() ); } ); } @@ -178,12 +160,12 @@ private void testNumericLiterals(EntityManager entityManager, String expectedSQL entity.get( "name" ) ); - connectionProvider.clear(); - List tuples = entityManager.createQuery( query ) - .getResultList(); + sqlStatementInterceptor.clear(); + + List tuples = entityManager.createQuery( query ).getResultList(); assertEquals( 1, tuples.size() ); - assertNotNull( connectionProvider.getPreparedStatement(expectedSQL) ); + sqlStatementInterceptor.assertExecuted( expectedSQL ); } @Test @@ -200,15 +182,13 @@ public void testCriteriaParameters() throws Exception { ), cb.equal( authors.index(), 0 ) ) .select( authors.get( "name" ) ); - connectionProvider.clear(); + sqlStatementInterceptor.clear(); entityManager.createQuery( query ).getResultList(); assertEquals( 1, - connectionProvider.getPreparedStatements().size() - ); - assertNotNull( connectionProvider.getPreparedStatement( - "select authors1_.name as col_0_0_ from Book criteriali0_ inner join Author authors1_ on criteriali0_.id=authors1_.book_id where criteriali0_.name=? and authors1_.index_id=0" ) + sqlStatementInterceptor.getSqlQueries().size() ); + sqlStatementInterceptor.assertExecuted( "select authors1_.name as col_0_0_ from Book criteriali0_ inner join Author authors1_ on criteriali0_.id=authors1_.book_id where criteriali0_.name=? and authors1_.index_id=0" ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/ImplicitJoinTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/ImplicitJoinTest.java index 069a1ca3b84cc7604e32382728618cae95999715..4c9cae742c847845f0cdde19aa0b4c3e51d38a01 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/ImplicitJoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/ImplicitJoinTest.java @@ -13,18 +13,19 @@ import javax.persistence.criteria.Join; import javax.persistence.criteria.Root; -import org.hibernate.jpa.test.metamodel.AbstractMetamodelSpecificTest; -import org.hibernate.jpa.test.metamodel.LineItem; -import org.hibernate.jpa.test.metamodel.LineItem_; -import org.hibernate.jpa.test.metamodel.Order; -import org.hibernate.jpa.test.metamodel.Order_; - +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.junit.Test; /** * @author Steve Ebersole */ -public class ImplicitJoinTest extends AbstractMetamodelSpecificTest { +public class ImplicitJoinTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Order.class, LineItem.class }; + } + @Test public void testImplicitJoinFromExplicitCollectionJoin() { EntityManager em = getOrCreateEntityManager(); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/LineItem.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/LineItem.java new file mode 100644 index 0000000000000000000000000000000000000000..03583422f1eb7207c0946c7baac2fc743036488d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/LineItem.java @@ -0,0 +1,66 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria.paths; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "LINEITEM_TABLE") +public class LineItem { + + private String id; + private int quantity; + private Order order; + + public LineItem() { + } + + public LineItem(String v1, int v2, Order v3) { + id = v1; + quantity = v2; + order = v3; + } + + public LineItem(String v1, int v2) { + id = v1; + quantity = v2; + } + + @Id + @Column(name = "ID") + public String getId() { + return id; + } + + public void setId(String v) { + id = v; + } + + @Column(name = "QUANTITY") + public int getQuantity() { + return quantity; + } + + public void setQuantity(int v) { + quantity = v; + } + + @ManyToOne + @JoinColumn(name = "FK1_FOR_ORDER_TABLE") + public Order getOrder() { + return order; + } + + public void setOrder(Order v) { + order = v; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/Order.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/Order.java new file mode 100644 index 0000000000000000000000000000000000000000..ff82ed9b80a4091f6a88b368e7c67967f7c914ca --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/Order.java @@ -0,0 +1,88 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria.paths; + +import java.util.Collection; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "ORDER_TABLE") +public class Order { + + private String id; + private double totalPrice; + private LineItem sampleLineItem; + private Collection lineItems = new java.util.ArrayList(); + + public Order() { + } + + public Order(String id, double totalPrice) { + this.id = id; + this.totalPrice = totalPrice; + } + + public Order(String id) { + this.id = id; + } + + // ==================================================================== + // getters and setters for State fields + + @Id + @Column(name = "ID") + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Column(name = "TOTALPRICE") + public double getTotalPrice() { + return totalPrice; + } + + public void setTotalPrice(double price) { + this.totalPrice = price; + } + + // ==================================================================== + // getters and setters for Association fields + + // 1x1 + + @OneToOne(cascade = CascadeType.REMOVE) + @JoinColumn(name = "FK0_FOR_LINEITEM_TABLE") + public LineItem getSampleLineItem() { + return sampleLineItem; + } + + public void setSampleLineItem(LineItem l) { + this.sampleLineItem = l; + } + + // 1xMANY + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "order") + public Collection getLineItems() { + return lineItems; + } + + public void setLineItems(Collection c) { + this.lineItems = c; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/SingularAttributeJoinTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/SingularAttributeJoinTest.java index ba2a4efda79f434e2d8332feb7289f6e5dfe2023..800f7dd4123b45256c9e078adb152a572b1c537b 100755 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/SingularAttributeJoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/SingularAttributeJoinTest.java @@ -1,85 +1,96 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.jpa.test.criteria.paths; - -import javax.persistence.EntityManager; -import javax.persistence.TypedQuery; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.From; -import javax.persistence.criteria.JoinType; -import javax.persistence.criteria.Path; -import javax.persistence.metamodel.Attribute; -import javax.persistence.metamodel.Bindable; -import javax.persistence.metamodel.SingularAttribute; -import javax.persistence.metamodel.Type; - -import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; -import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; -import org.hibernate.query.criteria.internal.PathSource; -import org.hibernate.query.criteria.internal.path.SingularAttributeJoin; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.withSettings; - -/** - * @author Brad Koehn - */ -public class SingularAttributeJoinTest extends BaseEntityManagerFunctionalTestCase { - @Override - protected String[] getMappings() { - return new String[] { - getClass().getPackage().getName().replace( '.', '/' ) + "/PolicyAndDistribution.hbm.xml" - }; - } - - /** - * When building a join from a non-class based entity (EntityMode.MAP), make sure you get the Bindable from - * the SingularAttribute as the join model. If you don't, you'll get the first non-classed based entity - * you added to your configuration. Regression for HHH-9142. - */ - @Test - public void testEntityModeMapJoins() throws Exception { - CriteriaBuilderImpl criteriaBuilder = mock( CriteriaBuilderImpl.class); - PathSource pathSource = mock( PathSource.class); - SingularAttribute joinAttribute = mock( SingularAttribute.class); - when(joinAttribute.getPersistentAttributeType()).thenReturn(Attribute.PersistentAttributeType.MANY_TO_ONE); - Type joinType = mock( Type.class, withSettings().extraInterfaces( Bindable.class)); - when(joinAttribute.getType()).thenReturn(joinType); - SingularAttributeJoin join = new SingularAttributeJoin(criteriaBuilder, null, pathSource, joinAttribute, JoinType.LEFT); - - assertEquals( joinType, join.getModel()); - } - - @Test - public void testEntityModeMapJoinCriteriaQuery() throws Exception { - final EntityManager entityManager = entityManagerFactory().createEntityManager(); - CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); - CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(); - javax.persistence.metamodel.EntityType distributionEntity = getEntityType("Distribution"); - From distributionFrom = criteriaQuery.from(distributionEntity); - From policyJoin = distributionFrom.join("policy"); - Path policyId = policyJoin.get("policyId"); - criteriaQuery.select(policyId); - TypedQuery typedQuery = entityManager.createQuery(criteriaQuery); -// typedQuery.getResultList(); - } - - private javax.persistence.metamodel.EntityType getEntityType(String entityName) { - for(javax.persistence.metamodel.EntityType entityType : entityManagerFactory().getMetamodel().getEntities()) { - if (entityType.getName().equals("Distribution")) { - return entityType; - } - } - - throw new IllegalStateException("Unable to find entity " + entityName); - } -} +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria.paths; + +import java.util.Map; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.From; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.Path; +import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.Bindable; +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.Type; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; +import org.hibernate.query.criteria.internal.PathSource; +import org.hibernate.query.criteria.internal.path.SingularAttributeJoin; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +/** + * @author Brad Koehn + */ +public class SingularAttributeJoinTest extends BaseEntityManagerFunctionalTestCase { + @Override + protected String[] getMappings() { + return new String[] { + getClass().getPackage().getName().replace( '.', '/' ) + "/PolicyAndDistribution.hbm.xml" + }; + } + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + + // make sure that dynamic-map mode entity types are returned in the metamodel. + options.put( AvailableSettings.JPA_METAMODEL_POPULATION, "enabled" ); + } + + /** + * When building a join from a non-class based entity (EntityMode.MAP), make sure you get the Bindable from + * the SingularAttribute as the join model. If you don't, you'll get the first non-classed based entity + * you added to your configuration. Regression for HHH-9142. + */ + @Test + public void testEntityModeMapJoins() throws Exception { + CriteriaBuilderImpl criteriaBuilder = mock( CriteriaBuilderImpl.class); + PathSource pathSource = mock( PathSource.class); + SingularAttribute joinAttribute = mock( SingularAttribute.class); + when(joinAttribute.getPersistentAttributeType()).thenReturn(Attribute.PersistentAttributeType.MANY_TO_ONE); + Type joinType = mock( Type.class, withSettings().extraInterfaces( Bindable.class)); + when(joinAttribute.getType()).thenReturn(joinType); + SingularAttributeJoin join = new SingularAttributeJoin(criteriaBuilder, null, pathSource, joinAttribute, JoinType.LEFT); + + assertEquals( joinType, join.getModel()); + } + + @Test + public void testEntityModeMapJoinCriteriaQuery() throws Exception { + final EntityManager entityManager = entityManagerFactory().createEntityManager(); + CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(); + javax.persistence.metamodel.EntityType distributionEntity = getEntityType("Distribution"); + From distributionFrom = criteriaQuery.from(distributionEntity); + From policyJoin = distributionFrom.join("policy"); + Path policyId = policyJoin.get("policyId"); + criteriaQuery.select(policyId); + TypedQuery typedQuery = entityManager.createQuery(criteriaQuery); +// typedQuery.getResultList(); + } + + private javax.persistence.metamodel.EntityType getEntityType(String entityName) { + for(javax.persistence.metamodel.EntityType entityType : entityManagerFactory().getMetamodel().getEntities()) { + if (entityType.getName().equals("Distribution")) { + return entityType; + } + } + + throw new IllegalStateException("Unable to find entity " + entityName); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/GroupBySelectCaseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/GroupBySelectCaseTest.java index 736c85fd1b3c81fcd4be8a6d946565391fe8e4ca..f68e6b9d8676b54c39fa35230d287c55b444da8a 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/GroupBySelectCaseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/GroupBySelectCaseTest.java @@ -16,11 +16,13 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.PostgreSQL95Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.jpa.test.metadata.Person_; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; @@ -29,6 +31,7 @@ import static org.junit.Assert.assertTrue; @TestForIssue(jiraKey = "HHH-12230") +@SkipForDialect(value = DB2Dialect.class, comment = "We would need casts in the case clauses. See HHH-12822.") public class GroupBySelectCaseTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/SelectCaseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/SelectCaseTest.java index f89eaa16a0d4debb6b2fedb2537f6d13c15cae5e..28486f516251491bf4052d1246a4c7a0f1080d5a 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/SelectCaseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/SelectCaseTest.java @@ -34,14 +34,17 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; @TestForIssue( jiraKey = "HHH-9731" ) +@SkipForDialect(value = DB2Dialect.class, comment = "We would need casts in the case clauses. See HHH-12822.") public class SelectCaseTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/DisableDiscardPersistenceContextOnCloseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/DisableDiscardPersistenceContextOnCloseTest.java index 6ef95aa974e62eec1982b000e55a763c5da65ec9..8ed46a1a832d9ccb0d159ab15b6ab5fdc101e1a4 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/DisableDiscardPersistenceContextOnCloseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/DisableDiscardPersistenceContextOnCloseTest.java @@ -14,6 +14,8 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.jpa.test.Wallet; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -24,6 +26,7 @@ /** * @author Vlad Mihalcea */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class DisableDiscardPersistenceContextOnCloseTest extends BaseEntityManagerFunctionalTestCase { private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/EnableDiscardPersistenceContextOnCloseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/EnableDiscardPersistenceContextOnCloseTest.java index 2fbe9cb98d9ae01e3a501ad756fc82df33719308..71fc4fda1722a146f26738669ecad72388621f8d 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/EnableDiscardPersistenceContextOnCloseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/EnableDiscardPersistenceContextOnCloseTest.java @@ -9,10 +9,14 @@ import java.util.Map; import javax.persistence.EntityManager; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.jpa.test.Wallet; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -23,6 +27,7 @@ /** * @author Vlad Mihalcea */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class EnableDiscardPersistenceContextOnCloseTest extends BaseEntityManagerFunctionalTestCase { private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/TestLazyPropertyOnPreUpdate.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/TestLazyPropertyOnPreUpdate.java index c1cb4e0f0912d0b1482c7afc8e03a5eb77a2c448..9719a0f673192f1e735e6224213e33dac9cb5445 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/TestLazyPropertyOnPreUpdate.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/enhancement/TestLazyPropertyOnPreUpdate.java @@ -51,7 +51,7 @@ protected void addConfigOptions(Map options) { @Before public void prepare() throws Exception { EntityPersister ep = entityManagerFactory().getMetamodel().entityPersister( EntityWithLazyProperty.class.getName() ); - assertTrue( ep.getInstrumentationMetadata().isEnhancedForLazyLoading() ); + assertTrue( ep.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ); byte[] testArray = new byte[]{0x2A}; diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphWithFetchAnnotationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphWithFetchAnnotationTest.java index 178fbf523426f4def5d7e1ea5f9a10f8efe84bef..cfcc2176b4b4f706e9e9964dcb10f1b084a4353b 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphWithFetchAnnotationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphWithFetchAnnotationTest.java @@ -23,6 +23,7 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -36,28 +37,11 @@ public class EntityGraphWithFetchAnnotationTest extends BaseEntityManagerFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider; + private SQLStatementInterceptor sqlStatementInterceptor; @Override - protected Map getConfig() { - Map config = super.getConfig(); - config.put( - org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - return config; - } - - @Override - public void buildEntityManagerFactory() { - connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); - super.buildEntityManagerFactory(); - } - - @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); } @Override @@ -79,7 +63,7 @@ public void testWithoutEntityGraph() { .createQuery( Order.class ); criteriaQuery.from( Order.class ); - connectionProvider.clear(); + sqlStatementInterceptor.clear(); entityManager .createQuery( criteriaQuery ) @@ -87,7 +71,7 @@ public void testWithoutEntityGraph() { .setMaxResults( 20 ) .getResultList(); - assertFalse( connectionProvider.getPreparedSQLStatements().get( 0 ).toLowerCase().contains( "left outer join" ) ); + assertFalse( sqlStatementInterceptor.getSqlQueries().get( 0 ).toLowerCase().contains( "left outer join" ) ); } ); } @@ -104,7 +88,7 @@ public void testWithEntityGraph() { EntityGraph entityGraph = entityManager.createEntityGraph( Order.class ); entityGraph.addAttributeNodes( "products" ); - connectionProvider.clear(); + sqlStatementInterceptor.clear(); entityManager .createQuery( criteriaQuery ) @@ -113,7 +97,7 @@ public void testWithEntityGraph() { .setHint( QueryHints.HINT_FETCHGRAPH, entityGraph ) .getResultList(); - assertTrue( connectionProvider.getPreparedSQLStatements().get( 0 ).toLowerCase().contains( "left outer join" ) ); + assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).toLowerCase().contains( "left outer join" ) ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Lock.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Lock.java index 93f41f3c2837130a9f0f241bc863dab95b8d82c9..ee50e511f558855e7e9604f19f282b38d9b14314 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Lock.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Lock.java @@ -10,12 +10,21 @@ import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; +import javax.persistence.LockModeType; +import javax.persistence.NamedQuery; +import javax.persistence.QueryHint; import javax.persistence.Version; /** * @author Emmanuel Bernard */ @Entity(name="Lock_") +@NamedQuery( + name="AllLocks", + query="from Lock_", + lockMode = LockModeType.PESSIMISTIC_WRITE, + hints = { @QueryHint( name = "javax.persistence.lock.timeout", value = "0")} +) public class Lock { private Integer id; private Integer version; diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java index 9f07cd12720108653e68634e724323e5beb87864..09c149d46cec5629d6d618d12182f5ae3515ecb1 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java @@ -116,6 +116,53 @@ public void testFindWithPessimisticWriteLockTimeoutException() { catch (PessimisticLockException pe) { fail( "Find with immediate timeout should have thrown LockTimeoutException." ); } + catch (PersistenceException pe) { + log.info( + "EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" + + "This is likely a consequence of " + getDialect().getClass() + .getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" + + "See HHH-7251 for an example of one such situation.", pe + ); + fail( "EntityManager should be throwing LockTimeoutException." ); + } + } ); + } ); + } + + @Test(timeout = 5 * 1000) //5 seconds + @TestForIssue( jiraKey = "HHH-13364" ) + @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class, + comment = "Test verifies proper exception throwing when a lock timeout is specified for Query#getSingleResult.", + jiraKey = "HHH-13364" ) + public void testQuerySingleResultPessimisticWriteLockTimeoutException() { + Lock lock = new Lock(); + lock.setName( "name" ); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, _entityManager -> { + + Lock lock2 = _entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE ); + assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, _entityManager.getLockMode( lock2 ) ); + + doInJPA( this::entityManagerFactory, entityManager -> { + try { + TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) ); + entityManager.createQuery( "from Lock_ where id = " + lock.getId(), Lock.class ) + .setLockMode( LockModeType.PESSIMISTIC_WRITE ) + .setHint( "javax.persistence.lock.timeout", 0 ) + .getSingleResult(); + fail( "Exception should be thrown" ); + } + catch (LockTimeoutException lte) { + // Proper exception thrown for dialect supporting lock timeouts when an immediate timeout is set. + lte.getCause(); + } + catch (PessimisticLockException pe) { + fail( "Find with immediate timeout should have thrown LockTimeoutException." ); + } catch (PersistenceException pe) { log.info("EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" + "This is likely a consequence of " + getDialect().getClass().getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" + @@ -126,9 +173,12 @@ public void testFindWithPessimisticWriteLockTimeoutException() { } ); } - @Test - @RequiresDialectFeature( value = DialectChecks.SupportSkipLocked.class ) - public void testUpdateWithPessimisticReadLockSkipLocked() { + @Test(timeout = 5 * 1000) //5 seconds + @TestForIssue( jiraKey = "HHH-13364" ) + @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class, + comment = "Test verifies proper exception throwing when a lock timeout is specified for Query#getResultList.", + jiraKey = "HHH-13364" ) + public void testQueryResultListPessimisticWriteLockTimeoutException() { Lock lock = new Lock(); lock.setName( "name" ); @@ -136,6 +186,96 @@ public void testUpdateWithPessimisticReadLockSkipLocked() { entityManager.persist( lock ); } ); + doInJPA( this::entityManagerFactory, _entityManager -> { + + Lock lock2 = _entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE ); + assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, _entityManager.getLockMode( lock2 ) ); + + doInJPA( this::entityManagerFactory, entityManager -> { + try { + TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) ); + entityManager.createQuery( "from Lock_ where id = " + lock.getId(), Lock.class ) + .setLockMode( LockModeType.PESSIMISTIC_WRITE ) + .setHint( "javax.persistence.lock.timeout", 0 ) + .getResultList(); + fail( "Exception should be thrown" ); + } + catch (LockTimeoutException lte) { + // Proper exception thrown for dialect supporting lock timeouts when an immediate timeout is set. + lte.getCause(); + } + catch (PessimisticLockException pe) { + fail( "Find with immediate timeout should have thrown LockTimeoutException." ); + } + catch (PersistenceException pe) { + log.info( + "EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" + + "This is likely a consequence of " + getDialect().getClass() + .getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" + + "See HHH-7251 for an example of one such situation.", pe + ); + fail( "EntityManager should be throwing LockTimeoutException." ); + } + } ); + } ); + } + + @Test(timeout = 5 * 1000) //5 seconds + @TestForIssue( jiraKey = "HHH-13364" ) + @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class, + comment = "Test verifies proper exception throwing when a lock timeout is specified for NamedQuery#getResultList.", + jiraKey = "HHH-13364" ) + public void testNamedQueryResultListPessimisticWriteLockTimeoutException() { + Lock lock = new Lock(); + lock.setName( "name" ); + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, _entityManager -> { + + Lock lock2 = _entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE ); + assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, _entityManager.getLockMode( lock2 ) ); + + doInJPA( this::entityManagerFactory, entityManager -> { + try { + TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) ); + entityManager.createNamedQuery( "AllLocks", Lock.class ).getResultList(); + fail( "Exception should be thrown" ); + } + catch (LockTimeoutException lte) { + // Proper exception thrown for dialect supporting lock timeouts when an immediate timeout is set. + lte.getCause(); + } + catch (PessimisticLockException pe) { + fail( "Find with immediate timeout should have thrown LockTimeoutException." ); + } + catch (PersistenceException pe) { + log.info( + "EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" + + "This is likely a consequence of " + getDialect().getClass() + .getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" + + "See HHH-7251 for an example of one such situation.", pe + ); + fail( "EntityManager should be throwing LockTimeoutException." ); + } + } ); + } ); + } + + @Test + @RequiresDialectFeature( value = DialectChecks.SupportSkipLocked.class ) + public void testUpdateWithPessimisticReadLockSkipLocked() { + Lock lock = new Lock(); + lock.setName( "name" ); + + doInJPA( + this::entityManagerFactory, entityManager -> { + entityManager.persist( lock ); + } + ); + doInJPA( this::entityManagerFactory, _entityManagaer -> { Map properties = new HashMap<>(); properties.put( org.hibernate.cfg.AvailableSettings.JPA_LOCK_TIMEOUT, LockOptions.SKIP_LOCKED ); @@ -419,7 +559,6 @@ public void testLockOptimisticForceIncrementDifferentEm() throws Exception { @SkipForDialect(HSQLDialect.class) // ASE15.5 will generate select...holdlock and fail at this test, but ASE15.7 passes it. Skip it for ASE15.5 // only. - @SkipForDialect(value = { SybaseASE15Dialect.class }, strictMatching = true, jiraKey = "HHH-6820") @SkipForDialect(value = { SQLServerDialect.class }) public void testContendedPessimisticLock() throws Exception { final CountDownLatch latch = new CountDownLatch( 1 ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/StatementIsClosedAfterALockExceptionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/StatementIsClosedAfterALockExceptionTest.java index c571b5c09f6fefa75ce6d1a22469e14dbde953a1..304839017aea6af50edbb52f5e2c9dca5e113295 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/StatementIsClosedAfterALockExceptionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/StatementIsClosedAfterALockExceptionTest.java @@ -15,6 +15,8 @@ import org.hibernate.Session; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.hibernate.testing.transaction.TransactionUtil; @@ -30,6 +32,7 @@ /** * @author Andrea Boriero */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class StatementIsClosedAfterALockExceptionTest extends BaseEntityManagerFunctionalTestCase { private static final PreparedStatementSpyConnectionProvider CONNECTION_PROVIDER = new PreparedStatementSpyConnectionProvider( false, false ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/NestedEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/NestedEmbeddableTest.java new file mode 100644 index 0000000000000000000000000000000000000000..dfba5e9e5c9f3eb71d44817a8f8e320fe12b1c9d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/mapping/NestedEmbeddableTest.java @@ -0,0 +1,318 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.mapping; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import javax.persistence.AssociationOverride; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; +import javax.persistence.MapKeyColumn; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Table; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Type; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.jpa.test.criteria.components.Alias; +import org.hibernate.jpa.test.criteria.components.Client; +import org.hibernate.jpa.test.criteria.components.Client_; +import org.hibernate.jpa.test.criteria.components.Name_; + +import org.hibernate.testing.TestForIssue; +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class NestedEmbeddableTest extends BaseEntityManagerFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Categorization.class, + Category.class, + CcmObject.class, + Domain.class + }; + } + + @Test + public void test() { + + } + + @Entity + @Table(name = "CATEGORIZATIONS") + public static class Categorization implements Serializable { + + @Id + @Column(name = "CATEGORIZATION_ID") + @GeneratedValue(strategy = GenerationType.AUTO) + private long categorizationId; + + @ManyToOne + @JoinColumn(name = "CATEGORY_ID") + private Category category; + + @ManyToOne + @JoinColumn(name = "OBJECT_ID") + private CcmObject categorizedObject; + + public long getCategorizationId() { + return categorizationId; + } + + public void setCategorizationId(long categorizationId) { + this.categorizationId = categorizationId; + } + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + + public CcmObject getCategorizedObject() { + return categorizedObject; + } + + public void setCategorizedObject(CcmObject categorizedObject) { + this.categorizedObject = categorizedObject; + } + } + + @Entity + @Table(name = "CATEGORIES") + public static class Category extends CcmObject implements Serializable { + + private static final long serialVersionUID = 1L; + + @Column(name = "NAME", nullable = false) + private String name; + + @Embedded + @AssociationOverride( + name = "values", + joinTable = @JoinTable(name = "CATEGORY_TITLES", + joinColumns = { + @JoinColumn(name = "OBJECT_ID")} + )) + private LocalizedString title; + + @Embedded + @AssociationOverride( + name = "values", + joinTable = @JoinTable(name = "CATEGORY_DESCRIPTIONS", + joinColumns = { + @JoinColumn(name = "OBJECT_ID")} + )) + private LocalizedString description; + + @OneToMany(mappedBy = "category", fetch = FetchType.LAZY) + @OrderBy("objectOrder ASC") + private List objects; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public LocalizedString getTitle() { + return title; + } + + public void setTitle(LocalizedString title) { + this.title = title; + } + + public LocalizedString getDescription() { + return description; + } + + public void setDescription(LocalizedString description) { + this.description = description; + } + } + + @Entity + @Table(name = "CCM_OBJECTS") + @Inheritance(strategy = InheritanceType.JOINED) + public static class CcmObject implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @Column(name = "OBJECT_ID") + @GeneratedValue(strategy = GenerationType.AUTO) + private long objectId; + + @Column(name = "DISPLAY_NAME") + private String displayName; + + @OneToMany(mappedBy = "categorizedObject", fetch = FetchType.LAZY) + @OrderBy("categoryOrder ASC") + private List categories; + + public long getObjectId() { + return objectId; + } + + public void setObjectId(long objectId) { + this.objectId = objectId; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + } + + @Entity + @Table(name = "CATEGORY_DOMAINS") + public static class Domain extends CcmObject { + + private static final long serialVersionUID = 1L; + + @Column(name = "DOMAIN_KEY", nullable = false, unique = true, length = 255) + private String domainKey; + + @Embedded + @AssociationOverride( + name = "values", + joinTable = @JoinTable(name = "DOMAIN_TITLES", + joinColumns = { + @JoinColumn(name = "OBJECT_ID")})) + private LocalizedString title; + + @Embedded + @AssociationOverride( + name = "values", + joinTable = @JoinTable(name = "DOMAIN_DESCRIPTIONS", + joinColumns = { + @JoinColumn(name = "OBJECT_ID")})) + private LocalizedString description; + + public String getDomainKey() { + return domainKey; + } + + public void setDomainKey(String domainKey) { + this.domainKey = domainKey; + } + + public LocalizedString getTitle() { + return title; + } + + public void setTitle(LocalizedString title) { + this.title = title; + } + + public LocalizedString getDescription() { + return description; + } + + public void setDescription(LocalizedString description) { + this.description = description; + } + } + + @Embeddable + public static class LocalizedString implements Serializable { + + private static final long serialVersionUID = 1L; + + @ElementCollection(fetch = FetchType.EAGER) + @MapKeyColumn(name = "LOCALE") + @Column(name = "LOCALIZED_VALUE") + @Lob + @Type(type = "org.hibernate.type.TextType") + private Map values; + + public LocalizedString() { + values = new HashMap<>(); + } + + public Map getValues() { + if (values == null) { + return null; + } else { + return Collections.unmodifiableMap( values); + } + } + + protected void setValues(final Map values) { + if (values == null) { + this.values = new HashMap<>(); + } else { + this.values = new HashMap<>(values); + } + } + + public String getValue() { + return getValue(Locale.getDefault()); + } + + public String getValue(final Locale locale) { + return values.get(locale); + } + + public void addValue(final Locale locale, final String value) { + values.put(locale, value); + } + + public void removeValue(final Locale locale) { + values.remove(locale); + } + + public boolean hasValue(final Locale locale) { + return values.containsKey(locale); + } + + public Set getAvailableLocales() { + return values.keySet(); + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/AbstractJpaMetamodelPopulationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/AbstractJpaMetamodelPopulationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6f487f2b8219618661a5ac117cdafaea62f1d136 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/AbstractJpaMetamodelPopulationTest.java @@ -0,0 +1,183 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.metamodel; + +import java.io.Serializable; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.metamodel.EmbeddableType; +import javax.persistence.metamodel.EntityType; +import javax.persistence.metamodel.Metamodel; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-12871") +public abstract class AbstractJpaMetamodelPopulationTest extends BaseEntityManagerFunctionalTestCase { + @Entity(name = "SimpleAnnotatedEntity") + public static class SimpleAnnotatedEntity { + @Id + @GeneratedValue + private Integer id; + private String data; + } + + @Entity(name = "CompositeIdAnnotatedEntity") + public static class CompositeIdAnnotatedEntity { + @EmbeddedId + private CompositeIdId id; + private String data; + } + + @Embeddable + public static class CompositeIdId implements Serializable { + private Integer id1; + private Integer id2; + } + + @Override + protected String[] getMappings() { + return new String[] { + "org/hibernate/jpa/test/metamodel/SimpleEntity.hbm.xml", + "org/hibernate/jpa/test/metamodel/CompositeIdEntity.hbm.xml", + "org/hibernate/jpa/test/metamodel/CompositeId2Entity.hbm.xml" + }; + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { SimpleAnnotatedEntity.class, CompositeIdAnnotatedEntity.class }; + } + + protected abstract String getJpaMetamodelPopulationValue(); + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + options.put( AvailableSettings.JPA_METAMODEL_POPULATION, getJpaMetamodelPopulationValue() ); + } + + @Test + public void testMetamodel() { + EntityManager entityManager = getOrCreateEntityManager(); + try { + final Metamodel metamodel = entityManager.getMetamodel(); + + if ( getJpaMetamodelPopulationValue().equalsIgnoreCase( "disabled" ) ) { + // In 5.1, metamodel returned null. + // In 5.2+, metamodel erturned as a non-null instance. + assertNotNull( metamodel ); + assertEquals( 0, metamodel.getManagedTypes().size() ); + assertEquals( 0, metamodel.getEntities().size() ); + assertEquals( 0, metamodel.getEmbeddables().size() ); + return; + } + + assertNotNull( metamodel ); + + assertManagedTypes( metamodel ); + assertEntityTypes( metamodel ); + assertEmbeddableTypes( metamodel ); + } + finally { + entityManager.close(); + } + } + + private void assertManagedTypes(Metamodel metamodel) { + if ( getJpaMetamodelPopulationValue().equalsIgnoreCase( "enabled" ) ) { + // All managed types should be included, dynamic-map and annotation based. + // EntityType(SimpleAnnotatedEntity) + // EntityType(CompositeIdAnnotatedEntity) + // EntityType(null) - SimpleEntity (dynamic-map entity) + // EntityType(null) - CompositeIdEntity (dynamic-map entity) + // EntityType(null) - CompositeId2Entity (dynamic-map entity) + // EmbeddableType(CompositeIdId) - CompositeIdAnnotatedEntity's EmbeddedId identifier + // EmbeddableType(Map) - CompositeIdEntity's (dynamic-map entity) identifier + // EmbeddableType(Map) - CompositeId2Entity's (dynamic-map entity) identifier + assertEquals( 8, metamodel.getManagedTypes().size() ); + } + else { + // When ignoreUnsupported is used, any managed-type that refers to a dynamic-map entity type + // or a managed type that is owned by said dynamic-map entity type should be excluded. + // Therefore this collection should only include 3 elements + // EntityType(SimpleAnnotated) + // EntityType(CompositeIdAnnotatedEntity) + // EmbeddableType(CompositeIdId) - CompositeIdAnnotatedEntity's EmbeddedId identifier + assertEquals( 3, metamodel.getManagedTypes().size() ); + } + } + + private void assertEntityTypes(Metamodel metamodel) { + final Set entityNames = metamodel.getEntities().stream() + .map( EntityType::getName ) + .collect( Collectors.toSet() ); + + if ( getJpaMetamodelPopulationValue().equalsIgnoreCase( "enabled" ) ) { + // Should include all entity types + // EntityType(SimpleAnnotatedEntity) + // EntityType(CompositeIdAnnotatedEntity) + // EntityType(null) - SimpleEntity (dynamic-map entity) + // EntityType(null) - CompositeIdEntity (dynamic-map entity) + // EntityType(null) - CompositeId2Entity (dynamic-map entity) + assertEquals( 5, entityNames.size() ); + assertTrue( entityNames.contains( "SimpleAnnotatedEntity" ) ); + assertTrue( entityNames.contains( "CompositeIdAnnotatedEntity" ) ); + assertTrue( entityNames.contains( "SimpleEntity" ) ); + assertTrue( entityNames.contains( "CompositeIdEntity" ) ); + assertTrue( entityNames.contains( "CompositeId2Entity" ) ); + } + else { + // In 5.1, this returns 5 elements + // CompositeIdAnnotatedEntity + // SimpleAnnotatedEntity + // SimpleEntity <-- this should not exist since its entity-type is filtered + // CompositeIdEntity <-- this should not exist since its entity-type is filtered + // CompsoiteId2Entity <-- this should not exist since its entity-type is filtered + // + // In 5.2, this returns 5 elements too. + // In 5.3, this returns 5 elements too. + assertEquals( 2, entityNames.size() ); + assertTrue( entityNames.contains( "SimpleAnnotatedEntity" ) ); + assertTrue( entityNames.contains( "CompositeIdAnnotatedEntity" ) ); + } + } + + private void assertEmbeddableTypes(Metamodel metamodel) { + final Set> embeddableTypes = metamodel.getEmbeddables(); + if ( getJpaMetamodelPopulationValue().equalsIgnoreCase( "enabled" ) ) { + // EmbeddableType(CompositeIdId) - CompositeIdAnnotatedEntity's EmbeddedId identifier + // EmbeddableType(Map) - CompositeIdEntity (dynamic-map entity) identifier + // EmbeddableType(Map) - CompositeId2Entity (dynamic-map entity) identifier + assertEquals( 3, embeddableTypes.size() ); + } + else { + // This should return only 1 element + // EmbeddableType(CompositeIdId) - CompositeIdAnnotatedEntity's EmbeddedId identifier + // The dynamic-map entity type's composite-id embeddable types should be excluded. + assertEquals( 1, embeddableTypes.size() ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelDisabledPopulationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelDisabledPopulationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f8e9ee2c378e71dafbef0445f5d484f91e3f623b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelDisabledPopulationTest.java @@ -0,0 +1,20 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.metamodel; + +import org.hibernate.testing.TestForIssue; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-12871") +public class JpaMetamodelDisabledPopulationTest extends AbstractJpaMetamodelPopulationTest { + @Override + protected String getJpaMetamodelPopulationValue() { + return "disabled"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelEnabledPopulationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelEnabledPopulationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..fa1eed102447a0872f5fad8b69f74e1f5a2a693f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelEnabledPopulationTest.java @@ -0,0 +1,20 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.metamodel; + +import org.hibernate.testing.TestForIssue; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-12871") +public class JpaMetamodelEnabledPopulationTest extends AbstractJpaMetamodelPopulationTest { + @Override + protected String getJpaMetamodelPopulationValue() { + return "enabled"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelIgnoreUnsupportedPopulationTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelIgnoreUnsupportedPopulationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b54d40d7bb72d072daa80bbc4c39c38942eb7bdb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/JpaMetamodelIgnoreUnsupportedPopulationTest.java @@ -0,0 +1,20 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.metamodel; + +import org.hibernate.testing.TestForIssue; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-12871") +public class JpaMetamodelIgnoreUnsupportedPopulationTest extends AbstractJpaMetamodelPopulationTest { + @Override + protected String getJpaMetamodelPopulationValue() { + return "ignoreUnsupported"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/MergeJpaComplianceTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/MergeJpaComplianceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1da7fd553494f792bb0f2398e05d63a18ead771e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ops/MergeJpaComplianceTest.java @@ -0,0 +1,211 @@ +package org.hibernate.jpa.test.ops; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.persistence.CascadeType; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil2.fromTransaction; +import static org.hibernate.testing.transaction.TransactionUtil2.inTransaction; + +@TestForIssue( jiraKey = "HHH-14608") +public class MergeJpaComplianceTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, Occupation.class, PersonOccupation.class + }; + } + + @Override + protected void addConfigOptions(Map config) { + config.put( org.hibernate.cfg.AvailableSettings.JPA_PROXY_COMPLIANCE, true ); + } + + @Test + public void testMerge() { + Person person = fromTransaction( + entityManagerFactory(), + entityManager -> { + Person p; + p = new Person( "1", "Fab" ); + Occupation t = new Occupation( 1l, "Some work" ); + + entityManager.persist( p ); + entityManager.persist( t ); + + entityManager.flush(); + + PersonOccupation participant = new PersonOccupation( p, t ); + entityManager.persist( participant ); + return p; + } + ); + + inTransaction( + entityManagerFactory(), + entityManager -> { + person.setName( "Fabiana" ); + entityManager.merge( person ); + } + ); + } + + @Entity(name = "Person") + public static class Person { + @Id + private String id; + + private String name; + + @OneToMany(mappedBy = "pk.person", cascade = CascadeType.ALL, orphanRemoval = true) + private List occupations; + + public Person() { + + } + + public Person(String id, String name) { + this.id = id; + this.name = name; + } + + protected void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public List getOccupations() { + return occupations; + } + + protected void addOccupationPeoplet(PersonOccupation personOccupation) { + if ( this.occupations == null ) { + occupations = new ArrayList<>(); + } + this.occupations.add( personOccupation ); + personOccupation.getPk().setPerson( this ); + } + + protected void setOccupations(List occupations) { + this.occupations = occupations; + } + } + + @Entity(name = "Occupation") + public static class Occupation { + @Id + private long id; + + private String name; + + @OneToMany(mappedBy = "pk.occupation", cascade = CascadeType.ALL) + private List personOccupations; + + protected Occupation() { + } + + public Occupation(long id, String name) { + this.id = id; + this.name = name; + } + + public long getId() { + return id; + } + + protected void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + protected void setName(String name) { + this.name = name; + } + + public List getPersonOccupations() { + return personOccupations; + } + + protected void addPersonOccupation(PersonOccupation participant) { + if ( personOccupations == null ) { + personOccupations = new ArrayList<>(); + } + personOccupations.add( participant ); + participant.getPk().setOccupation( this ); + } + + protected void setPersonOccupations(List personOccupations) { + this.personOccupations = personOccupations; + } + } + + @Entity(name = "PersonOccupation") + public static class PersonOccupation { + @EmbeddedId + private PersonOccupationPK pk = new PersonOccupationPK(); + + protected PersonOccupation() { + } + + public PersonOccupation(Person person, Occupation occupation) { + person.addOccupationPeoplet( this ); + occupation.addPersonOccupation( this ); + } + + public PersonOccupationPK getPk() { + return pk; + } + + public void setPk(PersonOccupationPK pk) { + this.pk = pk; + } + } + + @Embeddable + public static class PersonOccupationPK implements Serializable { + + @ManyToOne(fetch = FetchType.LAZY) + private Person person; + + @ManyToOne(fetch = FetchType.LAZY) + private Occupation occupation; + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + + public Occupation getOccupation() { + return occupation; + } + + public void setOccupation(Occupation occupation) { + this.occupation = occupation; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/AddNamedQueryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/AddNamedQueryTest.java index f46e7b9eb0ab6b7d0e936e8512a39110a282fe72..8fa7aa71ef4f79d8e1af695a6b933f43b77b6122 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/AddNamedQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/AddNamedQueryTest.java @@ -54,6 +54,41 @@ public void basicTest() { em.close(); } + @Test + public void replaceTest() { + EntityManager em = getOrCreateEntityManager(); + final String name = "myReplaceItemQuery"; + + // create a jpql query + String sql = "from Item"; + Query query = em.createQuery( sql ); + query.setHint( "org.hibernate.comment", sql ); + em.getEntityManagerFactory().addNamedQuery( name, query ); + query = em.createNamedQuery( name ); + assertEquals( sql, query.getHints().get( "org.hibernate.comment" ) ); + assertEquals( 0, query.getResultList().size() ); + + // create a native query and replace the previous jpql + sql = "select * from Item"; + query = em.createNativeQuery( sql, Item.class ); + query.setHint( "org.hibernate.comment", sql ); + em.getEntityManagerFactory().addNamedQuery( name, query ); + query = em.createNamedQuery( name ); + assertEquals( sql, query.getHints().get( "org.hibernate.comment" ) ); + assertEquals( 0, query.getResultList().size() ); + + // define back a named query + sql = "from Item"; + query = em.createQuery( sql ); + query.setHint( "org.hibernate.comment", sql ); + em.getEntityManagerFactory().addNamedQuery( name, query ); + query = em.createNamedQuery( name ); + assertEquals( sql, query.getHints().get( "org.hibernate.comment" ) ); + assertEquals( 0, query.getResultList().size() ); + + em.close(); + } + @Test public void testLockModeHandling() { final String name = "lock-mode-handling"; @@ -121,7 +156,8 @@ public void testConfigValueHandling() { // NOTE: here we check "query options" via the Hibernate contract (allowing nullness checking); see below for access via the JPA contract assertNull( hibernateQuery.getQueryOptions().getFirstRow() ); assertNull( hibernateQuery.getQueryOptions().getMaxRows() ); - assertEquals( FlushMode.AUTO, hibernateQuery.getHibernateFlushMode() ); + assertEquals( FlushMode.MANUAL, hibernateQuery.getHibernateFlushMode() ); + assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() ); assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() ); assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() ); // jpa timeout is in milliseconds, whereas Hibernate's is in seconds @@ -137,7 +173,8 @@ public void testConfigValueHandling() { // NOTE: here we check "query options" via the JPA contract assertEquals( 0, hibernateQuery.getFirstResult() ); assertEquals( Integer.MAX_VALUE, hibernateQuery.getMaxResults() ); - assertEquals( FlushModeType.AUTO, hibernateQuery.getFlushMode() ); + assertEquals( FlushMode.MANUAL, hibernateQuery.getHibernateFlushMode() ); + assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() ); assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() ); assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() ); assertEquals( (Integer) 10, hibernateQuery.getTimeout() ); @@ -150,7 +187,8 @@ public void testConfigValueHandling() { // assert the state of the query config settings based on the initial named query assertEquals( 0, hibernateQuery.getFirstResult() ); assertEquals( Integer.MAX_VALUE, hibernateQuery.getMaxResults() ); - assertEquals( FlushModeType.AUTO, hibernateQuery.getFlushMode() ); + assertEquals( FlushMode.MANUAL, hibernateQuery.getHibernateFlushMode() ); + assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() ); assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() ); assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() ); assertEquals( (Integer) 10, hibernateQuery.getTimeout() ); @@ -163,7 +201,8 @@ public void testConfigValueHandling() { // assert the state of the query config settings based on the initial named query assertEquals( 51, hibernateQuery.getFirstResult() ); assertEquals( Integer.MAX_VALUE, hibernateQuery.getMaxResults() ); - assertEquals( FlushModeType.AUTO, hibernateQuery.getFlushMode() ); + assertEquals( FlushMode.MANUAL, hibernateQuery.getHibernateFlushMode() ); + assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() ); assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() ); assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() ); assertEquals( (Integer) 10, hibernateQuery.getTimeout() ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryCommentTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryCommentTest.java index 73d29fa163a4e27770b14528b542567050619766..c0e07bcea0a6d5a1fb77c851e2ba1811228c8101 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryCommentTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryCommentTest.java @@ -26,6 +26,7 @@ import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.After; import org.junit.Before; @@ -41,32 +42,12 @@ @TestForIssue(jiraKey = "HHH-11640") public class NamedQueryCommentTest extends BaseEntityManagerFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider; + private SQLStatementInterceptor sqlStatementInterceptor; @Override - protected Map getConfig() { - Map config = super.getConfig(); - config.put( - org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - config.put( - AvailableSettings.USE_SQL_COMMENTS, - Boolean.TRUE.toString() - ); - return config; - } - - @Override - public void buildEntityManagerFactory() { - connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); - super.buildEntityManagerFactory(); - } - - @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); + options.put( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() ); } private static final String[] GAME_TITLES = { "Halo", "Grand Theft Auto", "NetHack" }; @@ -76,11 +57,6 @@ public Class[] getAnnotatedClasses() { return new Class[] { Game.class }; } - @Override - protected void addConfigOptions(Map options) { - options.put( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() ); - } - @Before public void setUp() { doInJPA( this::entityManagerFactory, entityManager -> { @@ -101,22 +77,17 @@ public void tearDown() { @Test public void testSelectNamedQueryWithSqlComment() { doInJPA( this::entityManagerFactory, entityManager -> { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); TypedQuery query = entityManager.createNamedQuery( "SelectNamedQuery", Game.class ); query.setParameter( "title", GAME_TITLES[0] ); List list = query.getResultList(); assertEquals( 1, list.size() ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); + sqlStatementInterceptor.assertExecutedCount(1); - assertNotNull( - connectionProvider.getPreparedStatement( - "/* COMMENT_SELECT_INDEX_game_title */ select namedquery0_.id as id1_0_, namedquery0_.title as title2_0_ from game namedquery0_ where namedquery0_.title=?" - ) + sqlStatementInterceptor.assertExecuted( + "/* COMMENT_SELECT_INDEX_game_title */ select namedquery0_.id as id1_0_, namedquery0_.title as title2_0_ from game namedquery0_ where namedquery0_.title=?" ); } ); } @@ -124,22 +95,18 @@ public void testSelectNamedQueryWithSqlComment() { @Test public void testSelectNamedNativeQueryWithSqlComment() { doInJPA( this::entityManagerFactory, entityManager -> { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); TypedQuery query = entityManager.createNamedQuery( "SelectNamedNativeQuery", Game.class ); query.setParameter( "title", GAME_TITLES[0] ); List list = query.getResultList(); assertEquals( 1, list.size() ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); + sqlStatementInterceptor.assertExecutedCount(1); - assertNotNull( - connectionProvider.getPreparedStatement( + sqlStatementInterceptor.assertExecuted( "/* + INDEX (game idx_game_title) */ select * from game g where title = ?" - ) + ); } ); } @@ -147,7 +114,7 @@ public void testSelectNamedNativeQueryWithSqlComment() { @Test public void testUpdateNamedQueryWithSqlComment() { doInJPA( this::entityManagerFactory, entityManager -> { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); Query query = entityManager.createNamedQuery( "UpdateNamedNativeQuery" ); query.setParameter( "title", GAME_TITLES[0] ); @@ -155,15 +122,10 @@ public void testUpdateNamedQueryWithSqlComment() { int updateCount = query.executeUpdate(); assertEquals( 1, updateCount ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); + sqlStatementInterceptor.assertExecutedCount(1); - assertNotNull( - connectionProvider.getPreparedStatement( + sqlStatementInterceptor.assertExecuted( "/* COMMENT_INDEX_game_title */ update game set title = ? where id = ?" - ) ); } ); } @@ -171,7 +133,7 @@ public void testUpdateNamedQueryWithSqlComment() { @Test public void testUpdateNamedNativeQueryWithSqlComment() { doInJPA( this::entityManagerFactory, entityManager -> { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); Query query = entityManager.createNamedQuery( "UpdateNamedNativeQuery" ); query.setParameter( "title", GAME_TITLES[0] ); @@ -179,15 +141,10 @@ public void testUpdateNamedNativeQueryWithSqlComment() { int updateCount = query.executeUpdate(); assertEquals( 1, updateCount ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); + sqlStatementInterceptor.assertExecutedCount(1); - assertNotNull( - connectionProvider.getPreparedStatement( + sqlStatementInterceptor.assertExecuted( "/* COMMENT_INDEX_game_title */ update game set title = ? where id = ?" - ) ); } ); } @@ -196,7 +153,7 @@ public void testUpdateNamedNativeQueryWithSqlComment() { @RequiresDialect(Oracle8iDialect.class) public void testUpdateNamedNativeQueryWithQueryHintUsingOracle() { doInJPA( this::entityManagerFactory, entityManager -> { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); Query query = entityManager.createNamedQuery( "UpdateNamedNativeQuery" ); query.setParameter( "title", GAME_TITLES[0] ); @@ -205,15 +162,10 @@ public void testUpdateNamedNativeQueryWithQueryHintUsingOracle() { int updateCount = query.executeUpdate(); assertEquals( 1, updateCount ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); + sqlStatementInterceptor.assertExecutedCount(1); - assertNotNull( - connectionProvider.getPreparedStatement( + sqlStatementInterceptor.assertExecuted( "/* COMMENT_INDEX_game_title */ update /*+ INDEX (game idx_game_id) */ game set title = ? where id = ?" - ) ); } ); } @@ -222,7 +174,7 @@ public void testUpdateNamedNativeQueryWithQueryHintUsingOracle() { @RequiresDialect(H2Dialect.class) public void testUpdateNamedNativeQueryWithQueryHintUsingIndex() { doInJPA( this::entityManagerFactory, entityManager -> { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); Query query = entityManager.createNamedQuery( "UpdateNamedNativeQuery" ); query.setParameter( "title", GAME_TITLES[0] ); @@ -231,15 +183,10 @@ public void testUpdateNamedNativeQueryWithQueryHintUsingIndex() { int updateCount = query.executeUpdate(); assertEquals( 1, updateCount ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); + sqlStatementInterceptor.assertExecutedCount(1); - assertNotNull( - connectionProvider.getPreparedStatement( + sqlStatementInterceptor.assertExecuted( "/* COMMENT_INDEX_game_title */ update game set title = ? where id = ?" - ) ); } ); } @@ -249,7 +196,7 @@ public void testUpdateNamedNativeQueryWithQueryHintUsingIndex() { @RequiresDialect(H2Dialect.class) public void testSelectNamedNativeQueryWithQueryHintUsingIndex() { doInJPA( this::entityManagerFactory, entityManager -> { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); Query query = entityManager.createNamedQuery( "SelectNamedQuery" ); query.setParameter( "title", GAME_TITLES[0] ); @@ -257,16 +204,11 @@ public void testSelectNamedNativeQueryWithQueryHintUsingIndex() { List list = query.getResultList(); assertEquals( 1, list.size() ); - assertEquals( - 1, - connectionProvider.getPreparedStatements().size() - ); + sqlStatementInterceptor.assertExecutedCount(1); - assertNotNull( - connectionProvider.getPreparedStatement( - "/* COMMENT_SELECT_INDEX_game_title */ select namedquery0_.id as id1_0_, namedquery0_.title as title2_0_ from game namedquery0_ USE INDEX (idx_game_id) where namedquery0_.title=?" - ) - ); + sqlStatementInterceptor.assertExecuted( + "/* COMMENT_SELECT_INDEX_game_title */ select namedquery0_.id as id1_0_, namedquery0_.title as title2_0_ from game namedquery0_ USE INDEX (idx_game_id) where namedquery0_.title=?" ) + ; } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryFlushModeTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryFlushModeTest.java new file mode 100644 index 0000000000000000000000000000000000000000..30d52f4f381198b49b69c61b2049b52b85616c18 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NamedQueryFlushModeTest.java @@ -0,0 +1,257 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.query; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.FlushMode; +import org.hibernate.annotations.FlushModeType; +import org.hibernate.annotations.NamedNativeQuery; +import org.hibernate.annotations.NamedQuery; +import org.hibernate.query.NativeQuery; +import org.hibernate.query.Query; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author Yoann Rodiere + */ +@TestForIssue(jiraKey = "HHH-12795") +public class NamedQueryFlushModeTest extends BaseCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { TestEntity.class }; + } + + @Test + public void testNamedQueryWithFlushModeManual() { + String queryName = "NamedQueryFlushModeManual"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + Query query = s.getNamedQuery( queryName ); + Assert.assertEquals( FlushMode.MANUAL, query.getHibernateFlushMode() ); + // JPA flush mode is an approximation + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + } ); + } + + @Test + public void testNamedQueryWithFlushModeCommit() { + String queryName = "NamedQueryFlushModeCommit"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + Query query = s.getNamedQuery( queryName ); + Assert.assertEquals( FlushMode.COMMIT, query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + } ); + } + + @Test + public void testNamedQueryWithFlushModeAuto() { + String queryName = "NamedQueryFlushModeAuto"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + Query query = s.getNamedQuery( queryName ); + Assert.assertEquals( FlushMode.AUTO, query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + } ); + } + + @Test + public void testNamedQueryWithFlushModeAlways() { + String queryName = "NamedQueryFlushModeAlways"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + Query query = s.getNamedQuery( queryName ); + Assert.assertEquals( FlushMode.ALWAYS, query.getHibernateFlushMode() ); + // JPA flush mode is an approximation + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + } ); + } + + @Test + public void testNamedQueryWithFlushModePersistenceContext() { + String queryName = "NamedQueryFlushModePersistenceContext"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + Query query; + + // A null Hibernate flush mode means we will use whatever mode is set on the session + // JPA doesn't allow null flush modes, so we expect some approximation of the flush mode to be returned + + s.setHibernateFlushMode( FlushMode.MANUAL ); + query = s.getNamedQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.COMMIT ); + query = s.getNamedQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.AUTO ); + query = s.getNamedQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.ALWAYS ); + query = s.getNamedQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + } ); + } + + @Test + public void testNamedNativeQueryWithFlushModeManual() { + String queryName = "NamedNativeQueryFlushModeManual"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + NativeQuery query = s.getNamedNativeQuery( queryName ); + Assert.assertEquals( FlushMode.MANUAL, query.getHibernateFlushMode() ); + } ); + } + + @Test + public void testNamedNativeQueryWithFlushModeCommit() { + String queryName = "NamedNativeQueryFlushModeCommit"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + NativeQuery query = s.getNamedNativeQuery( queryName ); + Assert.assertEquals( FlushMode.COMMIT, query.getHibernateFlushMode() ); + } ); + } + + @Test + public void testNamedNativeQueryWithFlushModeAuto() { + String queryName = "NamedNativeQueryFlushModeAuto"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + NativeQuery query = s.getNamedNativeQuery( queryName ); + Assert.assertEquals( FlushMode.AUTO, query.getHibernateFlushMode() ); + } ); + } + + @Test + public void testNamedNativeQueryWithFlushModeAlways() { + String queryName = "NamedNativeQueryFlushModeAlways"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + NativeQuery query = s.getNamedNativeQuery( queryName ); + Assert.assertEquals( FlushMode.ALWAYS, query.getHibernateFlushMode() ); + } ); + } + + @Test + public void testNamedNativeQueryWithFlushModePersistenceContext() { + String queryName = "NamedNativeQueryFlushModePersistenceContext"; + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + NativeQuery query; + + // A null Hibernate flush mode means we will use whatever mode is set on the session + // JPA doesn't allow null flush modes, so we expect some approximation of the flush mode to be returned + + s.setHibernateFlushMode( FlushMode.MANUAL ); + query = s.getNamedNativeQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.COMMIT ); + query = s.getNamedNativeQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.COMMIT, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.AUTO ); + query = s.getNamedNativeQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + + s.setHibernateFlushMode( FlushMode.ALWAYS ); + query = s.getNamedNativeQuery( queryName ); + Assert.assertNull( query.getHibernateFlushMode() ); + Assert.assertEquals( javax.persistence.FlushModeType.AUTO, query.getFlushMode() ); + } ); + } + + @Entity(name = "TestEntity") + @NamedQuery( + name = "NamedQueryFlushModeManual", + query = "select e from TestEntity e where e.text = :text", + flushMode = FlushModeType.MANUAL + ) + @NamedQuery( + name = "NamedQueryFlushModeCommit", + query = "select e from TestEntity e where e.text = :text", + flushMode = FlushModeType.COMMIT + ) + @NamedQuery( + name = "NamedQueryFlushModeAuto", + query = "select e from TestEntity e where e.text = :text", + flushMode = FlushModeType.AUTO + ) + @NamedQuery( + name = "NamedQueryFlushModeAlways", + query = "select e from TestEntity e where e.text = :text", + flushMode = FlushModeType.ALWAYS + ) + @NamedQuery( + name = "NamedQueryFlushModePersistenceContext", + query = "select e from TestEntity e where e.text = :text", + flushMode = FlushModeType.PERSISTENCE_CONTEXT + ) + @NamedNativeQuery( + name = "NamedNativeQueryFlushModeManual", + query = "select * from TestEntity e where e.text = :text", + resultClass = TestEntity.class, + flushMode = FlushModeType.MANUAL + ) + @NamedNativeQuery( + name = "NamedNativeQueryFlushModeCommit", + query = "select * from TestEntity e where e.text = :text", + resultClass = TestEntity.class, + flushMode = FlushModeType.COMMIT + ) + @NamedNativeQuery( + name = "NamedNativeQueryFlushModeAuto", + query = "select * from TestEntity e where e.text = :text", + resultClass = TestEntity.class, + flushMode = FlushModeType.AUTO + ) + @NamedNativeQuery( + name = "NamedNativeQueryFlushModeAlways", + query = "select * from TestEntity e where e.text = :text", + resultClass = TestEntity.class, + flushMode = FlushModeType.ALWAYS + ) + @NamedNativeQuery( + name = "NamedNativeQueryFlushModePersistenceContext", + query = "select * from TestEntity e where e.text = :text", + resultClass = TestEntity.class, + flushMode = FlushModeType.PERSISTENCE_CONTEXT + ) + public static class TestEntity { + + @Id + @GeneratedValue + private Long id; + + private String text; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ScalarResultNativeQueryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ScalarResultNativeQueryTest.java index 8f4fb20fc9fd090a2bf1e42ea2a815c8965ae805..6e7e18432a2ad148394930af8652bbcf8c9ffe75 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ScalarResultNativeQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ScalarResultNativeQueryTest.java @@ -6,7 +6,6 @@ */ package org.hibernate.jpa.test.query; -import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; import java.util.List; @@ -22,9 +21,7 @@ import org.hibernate.dialect.H2Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; - import org.hibernate.testing.RequiresDialect; -import org.hibernate.testing.TestForIssue; import org.junit.Test; /** @@ -64,64 +61,24 @@ protected Class[] getAnnotatedClasses() { @Test public void shouldApplyConfiguredTypeForProjectionOfScalarValue() { - doInJPA( this::entityManagerFactory, entityManager -> { - entityManager.persist( new Person( 1, 29 ) ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - List results = entityManager.createNamedQuery( "personAge", String.class ).getResultList(); - assertEquals( 1, results.size() ); - assertEquals( "29", results.get( 0 ) ); - } ); - } - - @Test - @TestForIssue( jiraKey = "HHH-12670" ) - public void testNativeSQLWithExplicitScalarMapping() { - doInJPA( this::entityManagerFactory, entityManager -> { - entityManager.persist( new Person( 1, 29 ) ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - List results = entityManager.createNativeQuery( - "select p.age from person p", Integer.class ) - .getResultList(); - assertEquals( 1, results.size() ); - assertEquals( Integer.valueOf( 29 ), results.get( 0 ) ); - } ); - } - - @Test - @TestForIssue( jiraKey = "HHH-12670" ) - public void testNativeSQLWithExplicitTypedArrayMapping() { - doInJPA( this::entityManagerFactory, entityManager -> { - entityManager.persist( new Person( 1, 29 ) ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - List results = entityManager.createNativeQuery( - "select p.id, p.age from person p", Integer[].class ) - .getResultList(); - assertEquals( 1, results.size() ); - assertEquals( Integer.valueOf( 1 ), results.get( 0 )[0] ); - assertEquals( Integer.valueOf( 29 ), results.get( 0 )[1] ); - } ); - } - - @Test - @TestForIssue( jiraKey = "HHH-12670" ) - public void testNativeSQLWithObjectArrayMapping() { - doInJPA( this::entityManagerFactory, entityManager -> { - entityManager.persist( new Person( 1, 29 ) ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - List results = entityManager.createNativeQuery( - "select p.id, p.age from person p", Object[].class ) - .getResultList(); - assertEquals( 1, results.size() ); - assertEquals( Integer.valueOf( 1 ), results.get( 0 )[0] ); - assertEquals( Integer.valueOf( 29 ), results.get( 0 )[1] ); - } ); + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.persist( new Person( 1, 29 ) ); + em.getTransaction().commit(); + em.close(); + + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + List results = em.createNamedQuery( "personAge", String.class ).getResultList(); + assertEquals( 1, results.size() ); + assertEquals( "29", results.get( 0 ) ); + em.getTransaction().commit(); + em.close(); + + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.createQuery( "delete from Person" ).executeUpdate(); + em.getTransaction().commit(); + em.close(); } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/AbstractNonOptionalSecondaryTableTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/AbstractNonOptionalSecondaryTableTest.java new file mode 100644 index 0000000000000000000000000000000000000000..31b8f549d6a7acdbdb07ed791a67879087447e83 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/AbstractNonOptionalSecondaryTableTest.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.secondarytable; + +import java.util.Arrays; +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.runners.Parameterized; + +public abstract class AbstractNonOptionalSecondaryTableTest extends BaseEntityManagerFunctionalTestCase { + public enum JpaComplianceCachingSetting{ DEFAULT, TRUE, FALSE }; + + private final JpaComplianceCachingSetting jpaComplianceCachingSetting; + + @Parameterized.Parameters(name = "JpaComplianceCachingSetting={0}") + public static Iterable parameters() { + return Arrays.asList( + new Object[][] { + { JpaComplianceCachingSetting.DEFAULT }, + { JpaComplianceCachingSetting.FALSE }, + { JpaComplianceCachingSetting.TRUE } + } + ); + } + + AbstractNonOptionalSecondaryTableTest(JpaComplianceCachingSetting jpaComplianceCachingSetting) { + this.jpaComplianceCachingSetting = jpaComplianceCachingSetting; + } + + @Override + protected void addConfigOptions(Map options) { + switch ( jpaComplianceCachingSetting ) { + case DEFAULT: + // Keep the default (false) + break; + case TRUE: + options.put( + AvailableSettings.JPA_CACHING_COMPLIANCE, + Boolean.TRUE + ); + break; + case FALSE: + options.put( + AvailableSettings.JPA_CACHING_COMPLIANCE, + Boolean.FALSE + ); + break; + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/JoinedTableNullNonOptionalSecondaryTableTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/JoinedTableNullNonOptionalSecondaryTableTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5dbb0e75a513d8f6af5cc51e0a2dbf1445ad8a10 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/JoinedTableNullNonOptionalSecondaryTableTest.java @@ -0,0 +1,141 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.secondarytable; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.SecondaryTable; + +import org.hibernate.annotations.Table; + +import org.hibernate.testing.junit4.CustomParameterized; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * @author Gail Badner + */ +@RunWith(CustomParameterized.class) +public class JoinedTableNullNonOptionalSecondaryTableTest extends AbstractNonOptionalSecondaryTableTest { + + public JoinedTableNullNonOptionalSecondaryTableTest(JpaComplianceCachingSetting jpaComplianceCachingSetting) { + super( jpaComplianceCachingSetting ); + } + + @Test + public void testRowAddedForNullValue() { + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntity anEntity = new AnEntity( 1 ); + entityManager.persist( anEntity ); + } + ); + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntity anEntity = entityManager.find( AnEntity.class, 1 ); + assertNotNull( anEntity ); + assertNull( anEntity.aDetail ); + // assert that a row was inserted into Details when its property is null + final Number id = (Number) entityManager.createNativeQuery( + "select id from Details where aDetail is null" + ).getSingleResult(); + assertEquals( + 1, + id.intValue() + ); + } + ); + } + + @Test + public void testRowAddedForNullValueInSubclassTable() { + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntitySubclass anEntity = new AnEntitySubclass( 1 ); + entityManager.persist( anEntity ); + } + ); + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntity anEntity = entityManager.find( AnEntity.class, 1 ); + assertNotNull( anEntity ); + assertNull( anEntity.aDetail ); + // assert that a row was inserted into Details when its property is null + Number id = (Number) entityManager.createNativeQuery( + "select id from Details where aDetail is null" + ).getSingleResult(); + + assertEquals( 1, id.intValue() ); + // assert that a row was inserted into MoreDetails when its property is null + id = (Number) entityManager.createNativeQuery( + "select id from MoreDetails where anotherDetail is null" + ).getSingleResult(); + assertEquals( 1,id.intValue() ); + } + ); + } + + @After + public void cleanupData() { + doInJPA( + this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery( "delete from Details" ).executeUpdate(); + entityManager.createNativeQuery( "delete from MoreDetails" ).executeUpdate(); + entityManager.createNativeQuery( "delete from AnEntitySubclass" ).executeUpdate(); + entityManager.createNativeQuery( "delete from AnEntity" ).executeUpdate(); + } + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { AnEntity.class, AnEntitySubclass.class }; + } + + @Entity(name = "AnEntity") + @SecondaryTable(name = "Details") + @Table(appliesTo = "Details", optional = false) + @Inheritance(strategy = InheritanceType.JOINED) + public static class AnEntity { + @Id + private int id; + + @Column(name = "aDetail", table="Details") + private String aDetail; + + public AnEntity() { + } + + public AnEntity(int id) { + this.id = id; + } + } + + @Entity(name = "AnEntitySubclass") + @SecondaryTable( name = "MoreDetails" ) + @Table(appliesTo = "MoreDetails", optional = false) + public static class AnEntitySubclass extends AnEntity { + @Column(name = "anotherDetail", table="MoreDetails") + private String anotherDetail; + + public AnEntitySubclass() { + } + + public AnEntitySubclass(int id) { + super( id ); + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/SingleTableNullNonOptionalSecondaryTableTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/SingleTableNullNonOptionalSecondaryTableTest.java new file mode 100644 index 0000000000000000000000000000000000000000..13bceb4535039f342c0ade0b987c8da1a532a0bb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/secondarytable/SingleTableNullNonOptionalSecondaryTableTest.java @@ -0,0 +1,175 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.secondarytable; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.SecondaryTable; + +import org.hibernate.annotations.Table; + +import org.hibernate.testing.junit4.CustomParameterized; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * @author Gail Badner + */ +@RunWith(CustomParameterized.class) +public class SingleTableNullNonOptionalSecondaryTableTest extends AbstractNonOptionalSecondaryTableTest { + + public SingleTableNullNonOptionalSecondaryTableTest(JpaComplianceCachingSetting jpaComplianceCachingSetting) { + super( jpaComplianceCachingSetting ); + } + + @Test + public void testRowAddedForNullValue() { + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntity anEntity = new AnEntity( 1 ); + entityManager.persist( anEntity ); + } + ); + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntity anEntity = entityManager.find( AnEntity.class, 1 ); + assertNotNull( anEntity ); + assertNull( anEntity.aDetail ); + // assert that a row was inserted into Details when its property is null + final Number id = (Number) entityManager.createNativeQuery( + "select id from Details where aDetail is null" + ).getSingleResult(); + assertEquals( 1, id.intValue() ); + } + ); + } + + @Test + public void testRowAddedForNullValueInSubclassTable() { + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntitySubclass anEntity = new AnEntitySubclass( 1 ); + entityManager.persist( anEntity ); + } + ); + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntity anEntity = entityManager.find( AnEntity.class, 1 ); + assertNotNull( anEntity ); + assertNull( anEntity.aDetail ); + // assert that a row was inserted into Details when its property is null + Number id = (Number) entityManager.createNativeQuery( + "select id from Details where aDetail is null" + ).getSingleResult(); + assertEquals( 1, id.intValue() ); + // assert that a row was inserted into MoreDetails when its property is null + id = (Number) entityManager.createNativeQuery( + "select id from MoreDetails where anotherDetail is null" + ).getSingleResult(); + assertEquals( 1, id.intValue() ); + } + ); + } + + @Test + public void testEntityWithBadDataInBaseSecondaryTableIgnored() { + // Not sure we really want to support this; + // It only works with single-table inheritance. + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntitySubclass anEntity = new AnEntitySubclass( 1 ); + entityManager.persist( anEntity ); + } + ); + + doInJPA( + this::entityManagerFactory, entityManager -> { + // Delete the data in a secondary table + entityManager.createNativeQuery( "delete from Details where id = 1" ).executeUpdate(); + // The entity with bad data should be ignored. + AnEntitySubclass anEntity = entityManager.find( AnEntitySubclass.class, 1 ); + assertNull( anEntity ); + } + ); + } + + @Test + public void testEntityWithBadDataInSubclassSecondaryTableIgnored() { + doInJPA( + this::entityManagerFactory, entityManager -> { + AnEntitySubclass anEntity = new AnEntitySubclass( 1 ); + entityManager.persist( anEntity ); + } + ); + + doInJPA( + this::entityManagerFactory, entityManager -> { + // Delete the data in a secondary table + entityManager.createNativeQuery( "delete from MoreDetails where id = 1" ).executeUpdate(); + // The entity with bad data should be ignored. + AnEntitySubclass anEntity = entityManager.find( AnEntitySubclass.class, 1 ); + assertNull( anEntity ); + } + ); + } + + @After + public void cleanupData() { + doInJPA( + this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery( "delete from Details" ).executeUpdate(); + entityManager.createNativeQuery( "delete from MoreDetails" ).executeUpdate(); + entityManager.createNativeQuery( "delete from AnEntity" ).executeUpdate(); + } + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { AnEntity.class, AnEntitySubclass.class }; + } + + @Entity(name = "AnEntity") + @SecondaryTable(name = "Details") + @Table(appliesTo = "Details", optional = false) + public static class AnEntity { + @Id + private int id; + + @Column(name = "aDetail", table="Details") + private String aDetail; + + public AnEntity() { + } + + public AnEntity(int id) { + this.id = id; + } + } + + @Entity(name = "AnEntitySubclass") + @SecondaryTable( name = "MoreDetails" ) + @Table(appliesTo = "MoreDetails", optional = false) + public static class AnEntitySubclass extends AnEntity { + @Column(name = "anotherDetail", table="MoreDetails") + private String anotherDetail; + + public AnEntitySubclass() { + } + + public AnEntitySubclass(int id) { + super( id ); + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4342954524f2e2ef7bf28521d945184c477562e8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java @@ -0,0 +1,142 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.test.transaction.batch; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.jta.TestingJtaBootstrap; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Before; +import org.junit.Rule; + +import org.jboss.logging.Logger; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * @author Andrea Boriero + */ +public abstract class AbstractJtaBatchTest extends BaseEntityManagerFunctionalTestCase { + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, AbstractBatchImpl.class.getName() ) + ); + + protected Triggerable triggerable; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Comment.class, EventLog.class }; + } + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + TestingJtaBootstrap.prepare( options ); + options.put( BatchBuilderInitiator.BUILDER, getBatchBuilderClassName() ); + options.put( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "true" ); + options.put( AvailableSettings.JPA_TRANSACTION_TYPE, "JTA" ); + options.put( AvailableSettings.STATEMENT_BATCH_SIZE, "50" ); + } + + @Before + public void setUp() { + triggerable = logInspection.watchForLogMessages( + "HHH000352: Unable to release batch statement..." ); + triggerable.reset(); + } + + protected void assertAllStatementsAreClosed(List statements) { + statements.forEach( statement -> { + try { + assertThat( "A PreparedStatement has not been closed", statement.isClosed(), is( true ) ); + } + catch (SQLException e) { + fail( e.getMessage() ); + } + } ); + } + + protected abstract String getBatchBuilderClassName(); + + @Entity(name = "Comment") + @Table(name = "COMMENTS") + public static class Comment { + private Long id; + private String message; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } + + @Entity(name = "EventLog") + public static class EventLog { + private Long id; + private String message; + + @Id + @GeneratedValue(generator = "eventLogIdGenerator") + @GenericGenerator(name = "eventLogIdGenerator", strategy = "org.hibernate.id.enhanced.TableGenerator", parameters = { + @Parameter(name = "table_name", value = "primaryKeyPools"), + @Parameter(name = "segment_value", value = "eventLog"), + @Parameter(name = "optimizer", value = "pooled"), + @Parameter(name = "increment_size", value = "500"), + @Parameter(name = "initial_value", value = "1") + }) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithFailingBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithFailingBatchTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ee38b55626ad081e241e0f022a47c73107b2628f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithFailingBatchTest.java @@ -0,0 +1,170 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.test.transaction.batch; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.EntityManager; +import javax.persistence.FlushModeType; +import javax.transaction.Status; +import javax.transaction.TransactionManager; + +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchingBatch; +import org.hibernate.engine.jdbc.batch.spi.Batch; +import org.hibernate.engine.jdbc.batch.spi.BatchKey; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jta.TestingJtaPlatformImpl; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +/** + * @author Gail Badner + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-13050") +@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) +public class JtaWithFailingBatchTest extends AbstractJtaBatchTest { + + private static TestBatch testBatch; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Comment.class, EventLog.class }; + } + + @Test + public void testAllStatementsAreClosedInCaseOfBatchExecutionFailure() throws Exception { + TransactionManager transactionManager = TestingJtaPlatformImpl.INSTANCE.getTransactionManager(); + EntityManager em = createEntityManager(); + try { + transactionManager.begin(); + + em.setFlushMode( FlushModeType.AUTO ); + + // Persist entity with non-generated id + EventLog eventLog1 = new EventLog(); + eventLog1.setMessage( "Foo1" ); + em.persist( eventLog1 ); + + // Persist entity with non-generated id + EventLog eventLog2 = new EventLog(); + eventLog2.setMessage( "Foo2" ); + em.persist( eventLog2 ); + + Comment comment = new Comment(); + comment.setMessage( "Bar" ); + + try { + em.persist( comment ); + transactionManager.commit(); + } + catch (Exception expected) { + //expected + if ( transactionManager.getStatus() == Status.STATUS_ACTIVE ) { + transactionManager.rollback(); + } + } + + assertThat( + "AbstractBatchImpl#releaseStatements() has not been callled", + testBatch.calledReleaseStatements, + is( true ) + ); + assertAllStatementsAreClosed( testBatch.createdStatements ); + assertStatementsListIsCleared(); + } + finally { + + em.close(); + } + + assertFalse( "HHH000352: Unable to release batch statement... has been thrown", triggerable.wasTriggered() ); + } + + private void assertStatementsListIsCleared() { + assertThat( testBatch.createdStatements.size(), not( 0 ) ); + assertThat( + "Not all PreparedStatements have been released", + testBatch.numberOfStatementsAfterReleasing, + is( 0 ) + ); + } + + public static class TestBatch extends BatchingBatch { + private int numberOfStatementsAfterReleasing; + private List createdStatements = new ArrayList<>(); + private boolean calledReleaseStatements; + + private String currentStatementSql; + + public TestBatch(BatchKey key, JdbcCoordinator jdbcCoordinator, int batchSize) { + super( key, jdbcCoordinator, batchSize ); + } + + @Override + public PreparedStatement getBatchStatement(String sql, boolean callable) { + currentStatementSql = sql; + PreparedStatement batchStatement = super.getBatchStatement( sql, callable ); + createdStatements.add( batchStatement ); + return batchStatement; + } + + @Override + public void addToBatch() { + // Implementations really should call abortBatch() before throwing an exception. + // Purposely skipping the call to abortBatch() to ensure that Hibernate works properly when + // a legacy implementation does not call abortBatch(). + throw sqlExceptionHelper().convert( + new SQLException( "fake SQLException" ), + "could not perform addBatch", + currentStatementSql + ); + } + + @Override + protected void releaseStatements() { + super.releaseStatements(); + calledReleaseStatements = true; + numberOfStatementsAfterReleasing += getStatements().size(); + } + } + + @Override + protected String getBatchBuilderClassName() { + return TestBatchBuilder.class.getName(); + } + + public static class TestBatchBuilder extends BatchBuilderImpl { + private int jdbcBatchSize; + + @Override + public void setJdbcBatchSize(int jdbcBatchSize) { + this.jdbcBatchSize = jdbcBatchSize; + } + + @Override + public Batch buildBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) { + return buildBatchTest( key, jdbcCoordinator, jdbcBatchSize ); + } + + protected BatchingBatch buildBatchTest(BatchKey key, JdbcCoordinator jdbcCoordinator, int jdbcBatchSize) { + testBatch = new TestBatch( key, jdbcCoordinator, jdbcBatchSize ); + return testBatch; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithStatementsBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithStatementsBatchTest.java new file mode 100644 index 0000000000000000000000000000000000000000..da0459e800a6a6978937f4fbe5ca4c1a0822b95e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithStatementsBatchTest.java @@ -0,0 +1,147 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.test.transaction.batch; + +import java.sql.PreparedStatement; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.EntityManager; +import javax.persistence.FlushModeType; +import javax.transaction.Status; +import javax.transaction.TransactionManager; + +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchingBatch; +import org.hibernate.engine.jdbc.batch.spi.Batch; +import org.hibernate.engine.jdbc.batch.spi.BatchKey; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jta.TestingJtaPlatformImpl; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-13050") +@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) +public class JtaWithStatementsBatchTest extends AbstractJtaBatchTest { + + private static TestBatch testBatch; + + @Test + public void testUnableToReleaseStatementMessageIsNotLogged() + throws Exception { + TransactionManager transactionManager = TestingJtaPlatformImpl.INSTANCE.getTransactionManager(); + EntityManager em = createEntityManager(); + try { + transactionManager.begin(); + + em.setFlushMode( FlushModeType.AUTO ); + + // Persist entity with non-generated id + EventLog eventLog1 = new EventLog(); + eventLog1.setMessage( "Foo1" ); + em.persist( eventLog1 ); + + // Persist entity with non-generated id + EventLog eventLog2 = new EventLog(); + eventLog2.setMessage( "Foo2" ); + em.persist( eventLog2 ); + + Comment comment = new Comment(); + comment.setMessage( "Bar" ); + em.persist( comment ); + + transactionManager.commit(); + assertStatementsListIsCleared(); + assertAllStatementsAreClosed( testBatch.createtdStatements ); + } + finally { + if ( transactionManager.getStatus() == Status.STATUS_ACTIVE ) { + transactionManager.rollback(); + } + em.close(); + } + + assertFalse( "HHH000352: Unable to release batch statement... has been thrown", triggerable.wasTriggered() ); + + em = createEntityManager(); + + try { + transactionManager.begin(); + Integer savedComments + = em.createQuery( "from Comment" ).getResultList().size(); + assertThat( savedComments, is( 1 ) ); + + Integer savedEventLogs + = em.createQuery( "from EventLog" ).getResultList().size(); + assertThat( savedEventLogs, is( 2 ) ); + } + finally { + if ( transactionManager.getStatus() == Status.STATUS_ACTIVE ) { + transactionManager.rollback(); + } + em.close(); + } + } + + private void assertStatementsListIsCleared() { + assertThat( testBatch.createtdStatements.size(), not( 0 ) ); + assertThat( + "Not all PreparedStatements have been released", + testBatch.numberOfStatementsAfterReleasing, + is( 0 ) + ); + } + + public static class TestBatch extends BatchingBatch { + private int numberOfStatementsAfterReleasing; + private List createtdStatements = new ArrayList<>(); + + public TestBatch(BatchKey key, JdbcCoordinator jdbcCoordinator, int batchSize) { + super( key, jdbcCoordinator, batchSize ); + } + + protected void releaseStatements() { + createtdStatements.addAll( getStatements().values() ); + super.releaseStatements(); + numberOfStatementsAfterReleasing += getStatements().size(); + } + } + + @Override + protected String getBatchBuilderClassName() { + return TestBatchBuilder.class.getName(); + } + + public static class TestBatchBuilder extends BatchBuilderImpl { + private int jdbcBatchSize; + + @Override + public void setJdbcBatchSize(int jdbcBatchSize) { + this.jdbcBatchSize = jdbcBatchSize; + } + + @Override + public Batch buildBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) { + return buildBatchTest( key, jdbcCoordinator, jdbcBatchSize ); + } + + protected BatchingBatch buildBatchTest(BatchKey key, JdbcCoordinator jdbcCoordinator, int jdbcBatchSize) { + testBatch = new TestBatch( key, jdbcCoordinator, jdbcBatchSize ); + return testBatch; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/SchemaToolingAutoActionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/SchemaToolingAutoActionTests.java new file mode 100644 index 0000000000000000000000000000000000000000..cc581d334645a4580ff5349f21461dbd8fa32759 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/SchemaToolingAutoActionTests.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.bootstrap; + +import java.util.Properties; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.tool.schema.Action; +import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +public class SchemaToolingAutoActionTests { + @Test + public void testLegacySettingAsAction() { + final Properties props = new Properties(); + props.put( AvailableSettings.HBM2DDL_AUTO, Action.CREATE_DROP ); + + final SchemaManagementToolCoordinator.ActionGrouping actionGrouping = SchemaManagementToolCoordinator.ActionGrouping.interpret( props ); + + assertThat( actionGrouping.getDatabaseAction(), is( Action.CREATE_DROP ) ); + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitInfoTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitInfoTests.java new file mode 100644 index 0000000000000000000000000000000000000000..1c0ca4544421be1b6d78fa0b21b5346c01a4092a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitInfoTests.java @@ -0,0 +1,105 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.bootstrap.jpa; + +import java.util.Collections; +import java.util.Map; +import javax.persistence.EntityManagerFactory; +import javax.persistence.spi.PersistenceProvider; +import javax.sql.DataSource; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.hibernate.testing.jdbc.DataSourceStub; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.hibernate.testing.util.jpa.PersistenceUnitInfoAdapter; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +/** + * @author Steve Ebersole + */ +public class PersistenceUnitInfoTests extends BaseUnitTestCase { + @Test + @TestForIssue( jiraKey = "HHH-13432" ) + public void testNonJtaDataExposedAsProperty() { + final DataSource puDataSource = new DataSourceStub( "puDataSource" ); + final PersistenceUnitInfoAdapter info = new PersistenceUnitInfoAdapter() { + + @Override + public DataSource getNonJtaDataSource() { + return puDataSource; + } + }; + + final PersistenceProvider provider = new HibernatePersistenceProvider(); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + info, + Collections.emptyMap() + ); + + // first let's check the DataSource used in the EMF... + final ConnectionProvider connectionProvider = emf.unwrap( SessionFactoryImplementor.class ) + .getServiceRegistry() + .getService( ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DatasourceConnectionProviderImpl.class ) ); + final DatasourceConnectionProviderImpl dsCp = (DatasourceConnectionProviderImpl) connectionProvider; + assertThat( dsCp.getDataSource(), is( puDataSource ) ); + + // now let's check that it is exposed via the EMF properties + // - note : the spec does not indicate that this should work, but + // it worked this way in previous versions + final Object o = emf.getProperties().get( AvailableSettings.JPA_NON_JTA_DATASOURCE ); + assertThat( o, is( puDataSource ) ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13432" ) + public void testJtaDataExposedAsProperty() { + final DataSource puDataSource = new DataSourceStub( "puDataSource" ); + final PersistenceUnitInfoAdapter info = new PersistenceUnitInfoAdapter() { + + @Override + public DataSource getJtaDataSource() { + return puDataSource; + } + }; + + final PersistenceProvider provider = new HibernatePersistenceProvider(); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + info, + Collections.emptyMap() + ); + + // first let's check the DataSource used in the EMF... + final ConnectionProvider connectionProvider = emf.unwrap( SessionFactoryImplementor.class ) + .getServiceRegistry() + .getService( ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DatasourceConnectionProviderImpl.class ) ); + final DatasourceConnectionProviderImpl dsCp = (DatasourceConnectionProviderImpl) connectionProvider; + assertThat( dsCp.getDataSource(), is( puDataSource ) ); + + // now let's check that it is exposed via the EMF properties + // - again, the spec does not indicate that this should work, but + // it worked this way in previous versions + final Map properties = emf.getProperties(); + final Object o = properties.get( AvailableSettings.JPA_JTA_DATASOURCE ); + assertEquals( puDataSource, o ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitOverridesTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitOverridesTests.java new file mode 100644 index 0000000000000000000000000000000000000000..422ee62c7e159c7beb9f94783354594260265eab --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitOverridesTests.java @@ -0,0 +1,544 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.bootstrap.jpa; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import javax.persistence.Entity; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Id; +import javax.persistence.metamodel.EntityType; +import javax.persistence.spi.PersistenceProvider; +import javax.persistence.spi.PersistenceUnitInfo; +import javax.sql.DataSource; + +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl; +import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.hql.internal.ast.HqlSqlWalker; +import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.hibernate.persister.entity.EntityPersister; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.env.ConnectionProviderBuilder; +import org.hibernate.testing.jdbc.DataSourceStub; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.hibernate.testing.util.jpa.DelegatingPersistenceUnitInfo; +import org.hibernate.testing.util.jpa.PersistenceUnitInfoAdapter; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +public class PersistenceUnitOverridesTests extends BaseUnitTestCase { + + @Test + public void testPassingIntegrationJpaJdbcOverrides() { + + // the integration overrides say to use the "db2" JPA connection settings (which should override the persistence unit values) + final Map integrationOverrides = ConnectionProviderBuilder.getJpaConnectionProviderProperties( "db2" ); + + final EntityManagerFactory emf = new HibernatePersistenceProvider().createContainerEntityManagerFactory( + new PersistenceUnitInfoAdapter() { + @Override + public Properties getProperties() { + // effectively, the `persistence.xml` defines `db1` as the connection settings + return ConnectionProviderBuilder.getJpaConnectionProviderProperties( "db1" ); + } + }, + integrationOverrides + ); + + try { + final Map properties = emf.getProperties(); + + final Object hibernateJdbcDriver = properties.get( AvailableSettings.URL ); + assertThat( hibernateJdbcDriver, notNullValue() ); + + final Object jpaJdbcDriver = properties.get( AvailableSettings.JPA_JDBC_URL ); + assertThat( (String) jpaJdbcDriver, containsString( "db2" ) ); + } + finally { + emf.close(); + } + } + + @Test + public void testPassingIntegrationJtaDataSourceOverrideForJpaJdbcSettings() { + final PersistenceUnitInfoAdapter puInfo = new PersistenceUnitInfoAdapter( + ConnectionProviderBuilder.getJpaConnectionProviderProperties( "db2" ) + ); + + final DataSource integrationDataSource = new DataSourceStub( "integrationDataSource" ); + + final HibernatePersistenceProvider provider = new HibernatePersistenceProvider(); + puInfo.getProperties().setProperty( AvailableSettings.HQL_BULK_ID_STRATEGY, MultiTableBulkIdStrategyStub.class.getName() ); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + puInfo, + Collections.singletonMap( AvailableSettings.JPA_JTA_DATASOURCE, integrationDataSource ) + ); + + try { + // first let's check the DataSource used in the EMF... + final ConnectionProvider connectionProvider = emf.unwrap( SessionFactoryImplementor.class ) + .getServiceRegistry() + .getService( ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DatasourceConnectionProviderImpl.class ) ); + final DatasourceConnectionProviderImpl dsCp = (DatasourceConnectionProviderImpl) connectionProvider; + assertThat( dsCp.getDataSource(), is( integrationDataSource ) ); + + // now let's check that it is exposed via the EMF properties + // - note : the spec does not indicate that this should work, but + // it worked this way in previous versions + final Object jtaDs = emf.getProperties().get( AvailableSettings.JPA_JTA_DATASOURCE ); + assertThat( jtaDs, is( integrationDataSource ) ); + + // Additionally, we should have set Hibernate's DATASOURCE setting + final Object hibDs = emf.getProperties().get( AvailableSettings.JPA_JTA_DATASOURCE ); + assertThat( hibDs, is( integrationDataSource ) ); + + // Make sure the non-jta-data-source setting was cleared or otherwise null + final Object nonJtaDs = emf.getProperties().get( AvailableSettings.JPA_NON_JTA_DATASOURCE ); + assertThat( nonJtaDs, nullValue() ); + } + finally { + emf.close(); + } + } + + @Test + public void testPassingIntegrationJpaJdbcOverrideForJtaDataSourceProperty() { + PersistenceProvider provider = new HibernatePersistenceProvider() { + @Override + public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map integrationOverrides) { + return super.createContainerEntityManagerFactory( + new DelegatingPersistenceUnitInfo( info ) { + + // inject a JPA JTA DataSource setting into the PU + final DataSource puDataSource; + final Properties puProperties; + + { + puDataSource = new DataSourceStub( "puDataSource" ); + + puProperties = new Properties(); + puProperties.putAll( info.getProperties() ); + puProperties.put( AvailableSettings.JPA_JTA_DATASOURCE, puDataSource ); + } + + @Override + public DataSource getJtaDataSource() { + return null; + } + + @Override + public DataSource getNonJtaDataSource() { + return null; + } + + @Override + public Properties getProperties() { + return puProperties; + } + }, + integrationOverrides + ); + } + }; + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + new PersistenceUnitInfoAdapter(), + // however, provide JPA connection settings as "integration settings", which according to JPA spec should override the persistence unit values. + // - note that it is unclear in the spec whether JDBC value in the integration settings should override + // a JTA DataSource (nor the reverse). However, that is a useful thing to support + ConnectionProviderBuilder.getJpaConnectionProviderProperties( "db2" ) + ); + + try { + final Map properties = emf.getProperties(); + + final Object hibernateJdbcDriver = properties.get( AvailableSettings.URL ); + assertThat( hibernateJdbcDriver, notNullValue() ); + + final Object jpaJdbcDriver = properties.get( AvailableSettings.JPA_JDBC_URL ); + assertThat( (String) jpaJdbcDriver, containsString( "db2" ) ); + + // see if the values had the affect to adjust the `ConnectionProvider` used + final ConnectionProvider connectionProvider = emf.unwrap( SessionFactoryImplementor.class ) + .getServiceRegistry() + .getService( ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DriverManagerConnectionProviderImpl.class ) ); + } + finally { + emf.close(); + } + } + + @Test +// @FailureExpected( +// jiraKey = "HHH-12858", +// message = "Even though the JDBC settings override a DataSource *property*, it" + +// " does not override a DataSource defined using the dedicated persistence.xml element" +// ) + public void testPassingIntegrationJpaJdbcOverridesForJtaDataSourceElement() { + PersistenceProvider provider = new HibernatePersistenceProvider() { + @Override + public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map integrationOverrides) { + return super.createContainerEntityManagerFactory( + new DelegatingPersistenceUnitInfo( info ) { + // inject a JPA JTA DataSource setting into the PU + final DataSource puDataSource = new DataSourceStub( "puDataSource" ); + + @Override + public DataSource getJtaDataSource() { + return puDataSource; + } + }, + integrationOverrides + ); + } + }; + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + new PersistenceUnitInfoAdapter(), + // however, provide JPA connection settings as "integration settings", which according to JPA spec should override the persistence unit values. + // - note that it is unclear in the spec whether JDBC value in the integration settings should override + // a JTA DataSource (nor the reverse). However, that is a useful thing to support + ConnectionProviderBuilder.getJpaConnectionProviderProperties( "db2" ) + ); + + try { + final Map properties = emf.getProperties(); + + final Object hibernateJdbcDriver = properties.get( AvailableSettings.URL ); + assertThat( hibernateJdbcDriver, notNullValue() ); + + final Object jpaJdbcDriver = properties.get( AvailableSettings.JPA_JDBC_URL ); + assertThat( (String) jpaJdbcDriver, containsString( "db2" ) ); + + // see if the values had the affect to adjust the `ConnectionProvider` used + final ConnectionProvider connectionProvider = emf.unwrap( SessionFactoryImplementor.class ) + .getServiceRegistry() + .getService( ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DriverManagerConnectionProviderImpl.class ) ); + } + finally { + emf.close(); + } + } + + @Test +// @FailureExpected( +// jiraKey = "HHH-12858", +// message = "So it appears any use of the persistence.xml `jta-data-source` or `non-jta-data-source` " + +// "have precedence over integration settings, which is also incorrect" +// ) + public void testPassingIntegrationJpaDataSourceOverrideForJtaDataSourceElement() { + final DataSource puDataSource = new DataSourceStub( "puDataSource" ); + final DataSource integrationDataSource = new DataSourceStub( "integrationDataSource" ); + + PersistenceProvider provider = new HibernatePersistenceProvider() { + @Override + public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map integrationOverrides) { + return super.createContainerEntityManagerFactory( + new DelegatingPersistenceUnitInfo( info ) { + @Override + public DataSource getJtaDataSource() { + // pretend the DataSource was defined using the `jta-data-source` element in persistence.xml + // - as opposed using `javax.persistence.jtaDataSource` under the `properties` element + return puDataSource; + } + }, + integrationOverrides + ); + } + }; + + final Map integrationOverrides = new HashMap(); + //noinspection unchecked + integrationOverrides.put( AvailableSettings.JPA_JTA_DATASOURCE, integrationDataSource ); + integrationOverrides.put( AvailableSettings.HQL_BULK_ID_STRATEGY, new MultiTableBulkIdStrategyStub() ); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + new PersistenceUnitInfoAdapter(), + integrationOverrides + ); + + try { + final Map properties = emf.getProperties(); + + final Object datasource = properties.get( AvailableSettings.JPA_JTA_DATASOURCE ); + assertThat( datasource, is( integrationDataSource ) ); + + // see if the values had the affect to adjust the `ConnectionProvider` used + final ConnectionProvider connectionProvider = emf.unwrap( SessionFactoryImplementor.class ) + .getServiceRegistry() + .getService( ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DatasourceConnectionProviderImpl.class ) ); + + final DatasourceConnectionProviderImpl datasourceConnectionProvider = (DatasourceConnectionProviderImpl) connectionProvider; + assertThat( datasourceConnectionProvider.getDataSource(), is( integrationDataSource ) ); + } + finally { + emf.close(); + } + } + + @Test + @TestForIssue( jiraKey = "HHH-13640" ) + public void testIntegrationOverridesOfPersistenceXmlDataSource() { + + // mimics a DataSource defined in the persistence.xml + final DataSourceStub dataSource = new DataSourceStub( "puDataSource" ); + final PersistenceUnitInfoAdapter info = new PersistenceUnitInfoAdapter() { + + @Override + public DataSource getNonJtaDataSource() { + return dataSource; + } + }; + + + // Now create "integration Map" that overrides the DataSource to use + final DataSource override = new DataSourceStub( "integrationDataSource" ); + final Map integrationSettings = new HashMap<>(); + integrationSettings.put( AvailableSettings.JPA_NON_JTA_DATASOURCE, override ); + integrationSettings.put( AvailableSettings.HQL_BULK_ID_STRATEGY, new MultiTableBulkIdStrategyStub() ); + + final PersistenceProvider provider = new HibernatePersistenceProvider(); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + info, + integrationSettings + ); + + try { + final Map properties = emf.getProperties(); + + assertThat( properties.get( AvailableSettings.JPA_NON_JTA_DATASOURCE ), notNullValue() ); + assertThat( properties.get( AvailableSettings.JPA_NON_JTA_DATASOURCE ), is( override ) ); + + final SessionFactoryImplementor sessionFactory = emf.unwrap( SessionFactoryImplementor.class ); + final ConnectionProvider connectionProvider = sessionFactory.getServiceRegistry().getService( ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DatasourceConnectionProviderImpl.class ) ); + + final DatasourceConnectionProviderImpl dsProvider = (DatasourceConnectionProviderImpl) connectionProvider; + assertThat( dsProvider.getDataSource(), is( override ) ); + } + finally { + emf.close(); + } + } + + @Test + @TestForIssue( jiraKey = "HHH-13640" ) + public void testIntegrationOverridesOfPersistenceXmlDataSourceWithDriverManagerInfo() { + + // mimics a DataSource defined in the persistence.xml + final DataSourceStub dataSource = new DataSourceStub( "puDataSource" ); + final PersistenceUnitInfoAdapter info = new PersistenceUnitInfoAdapter() { + + @Override + public DataSource getNonJtaDataSource() { + return dataSource; + } + }; + + final Map integrationSettings = new HashMap<>(); + integrationSettings.put( AvailableSettings.JPA_JDBC_DRIVER, ConnectionProviderBuilder.DRIVER ); + integrationSettings.put( AvailableSettings.JPA_JDBC_URL, ConnectionProviderBuilder.URL ); + integrationSettings.put( AvailableSettings.JPA_JDBC_USER, ConnectionProviderBuilder.USER ); + integrationSettings.put( AvailableSettings.JPA_JDBC_PASSWORD, ConnectionProviderBuilder.PASS ); + + final PersistenceProvider provider = new HibernatePersistenceProvider(); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + info, + integrationSettings + ); + + try { + final SessionFactoryImplementor sessionFactory = emf.unwrap( SessionFactoryImplementor.class ); + final ConnectionProvider connectionProvider = sessionFactory.getServiceRegistry().getService( + ConnectionProvider.class ); + assertThat( connectionProvider, instanceOf( DriverManagerConnectionProviderImpl.class ) ); + } + finally { + emf.close(); + } + } + + @Test + public void testCfgXmlBaseline() { + final PersistenceUnitInfoAdapter info = new PersistenceUnitInfoAdapter() { + private final Properties props = new Properties(); + { + props.put( org.hibernate.jpa.AvailableSettings.CFG_FILE, "org/hibernate/orm/test/bootstrap/jpa/hibernate.cfg.xml" ); + } + + @Override + public Properties getProperties() { + return props; + } + }; + + final PersistenceProvider provider = new HibernatePersistenceProvider(); + + final Map integrationSettings = Collections.emptyMap(); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + info, + integrationSettings + ); + + try { + assertThat( + emf.getProperties().get( AvailableSettings.DIALECT ), + is( PersistenceUnitDialect.class.getName() ) + ); + assertThat( + emf.unwrap( SessionFactoryImplementor.class ).getJdbcServices().getDialect(), + instanceOf( PersistenceUnitDialect.class ) + ); + + final EntityType entityMapping = emf.getMetamodel().entity( MappedEntity.class ); + assertThat( entityMapping, notNullValue() ); + } + finally { + emf.close(); + } + } + + @Test + public void testIntegrationOverridesOfCfgXml() { + final PersistenceUnitInfoAdapter info = new PersistenceUnitInfoAdapter() { + private final Properties props = new Properties(); + { + props.put( org.hibernate.jpa.AvailableSettings.CFG_FILE, "org/hibernate/orm/test/bootstrap/jpa/hibernate.cfg.xml" ); + } + + @Override + public Properties getProperties() { + return props; + } + }; + + final PersistenceProvider provider = new HibernatePersistenceProvider(); + + final Map integrationSettings = Collections.singletonMap( + AvailableSettings.DIALECT, + IntegrationDialect.class.getName() + ); + + final EntityManagerFactory emf = provider.createContainerEntityManagerFactory( + info, + integrationSettings + ); + + try { + assertThat( + emf.getProperties().get( AvailableSettings.DIALECT ), + is( IntegrationDialect.class.getName() ) + ); + assertThat( + emf.unwrap( SessionFactoryImplementor.class ).getJdbcServices().getDialect(), + instanceOf( IntegrationDialect.class ) + ); + + final EntityPersister entityMapping = emf.unwrap( SessionFactoryImplementor.class ) + .getMetamodel() + .entityPersister( MappedEntity.class ); + assertThat( entityMapping, notNullValue() ); + assertThat( + entityMapping.getCacheAccessStrategy().getAccessType(), + is( AccessType.READ_ONLY ) + ); + } + finally { + emf.close(); + } + } + + public static class PersistenceUnitDialect extends Dialect { + } + + @SuppressWarnings("WeakerAccess") + public static class IntegrationDialect extends Dialect { + } + + @Entity + public static class MappedEntity { + private Integer id; + private String name; + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static class MultiTableBulkIdStrategyStub implements MultiTableBulkIdStrategy { + + @Override + public void prepare( + JdbcServices jdbcServices, + JdbcConnectionAccess connectionAccess, + MetadataImplementor metadata, + SessionFactoryOptions sessionFactoryOptions) { + + } + + @Override + public void release( + JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess) { + + } + + @Override + public UpdateHandler buildUpdateHandler( + SessionFactoryImplementor factory, HqlSqlWalker walker) { + return null; + } + + @Override + public DeleteHandler buildDeleteHandler( + SessionFactoryImplementor factory, HqlSqlWalker walker) { + return null; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/package-info.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..17cbbb7c3cfbd66ab3fe74860b3a145590c6ce3c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/package-info.java @@ -0,0 +1,11 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +/** + * Tests for Hibernate bootstrapping Hibernate + */ +package org.hibernate.orm.test.bootstrap; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/FkRefTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/FkRefTests.java new file mode 100644 index 0000000000000000000000000000000000000000..44458e4872c2e4b35146af0733c0c887e6221227 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/FkRefTests.java @@ -0,0 +1,271 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.orm.test.notfound; + +import java.io.Serializable; +import java.util.List; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; + +import org.hibernate.QueryException; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.boot.SessionFactoryBuilder; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.fail; + +/** + * Tests for the new `{fk}` HQL token + * + * @author Steve Ebersole + */ +public class FkRefTests extends BaseNonConfigCoreFunctionalTestCase { + + private SQLStatementInterceptor statementInspector; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Coin.class, Currency.class + }; + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + statementInspector = new SQLStatementInterceptor( sfb ); + } + + + @Test + @TestForIssue(jiraKey = "HHH-15106") + public void testSimplePredicateUse() { + statementInspector.clear(); + + // there is a Coin which has a currency_fk = 1 + inTransaction( (session) -> { + final String hql = "select c from Coin c where fk(c.currency) = 1"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins.size(), is( 1 ) ); + assertThat( coins.get( 0 ), notNullValue() ); + assertThat( coins.get( 0 ).getCurrency(), nullValue() ); + + assertThat( statementInspector.getSqlQueries().size(), is( 2 ) ); + assertThat( statementInspector.getSqlQueries().get( 0 ), not( containsString( " join " ) ) ); + } ); + + statementInspector.clear(); + + // However, the "matching" Currency does not exist + inTransaction( (session) -> { + final String hql = "select c from Coin c where c.currency.id = 1"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins.size(), is( 0 ) ); + } ); + + statementInspector.clear(); + + // check using `currency` as a naked "property-ref" + inTransaction( (session) -> { + final String hql = "select c from Coin c where fk(currency) = 1"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins.size(), is( 1 ) ); + assertThat( coins.get( 0 ), notNullValue() ); + assertThat( coins.get( 0 ).getCurrency(), nullValue() ); + + assertThat( statementInspector.getSqlQueries().size(), is( 2 ) ); + assertThat( statementInspector.getSqlQueries().get( 0 ), not( containsString( " join " ) ) ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-15106") + public void testNullnessPredicateUse() { + statementInspector.clear(); + + // there is one Coin (id=3) which has a null currency_fk + inTransaction( (session) -> { + final String hql = "select c from Coin c where fk(c.currency) is null"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins.size(), is( 1 ) ); + assertThat( coins.get( 0 ), notNullValue() ); + assertThat( coins.get( 0 ).getId(), is( 3 ) ); + assertThat( coins.get( 0 ).getCurrency(), nullValue() ); + + assertThat( statementInspector.getSqlQueries().size(), is( 1 ) ); + assertThat( statementInspector.getSqlQueries().get( 0 ), not( containsString( " join " ) )); + } ); + + statementInspector.clear(); + + // check using `currency` as a naked "property-ref" + inTransaction( (session) -> { + final String hql = "select c from Coin c where fk(currency) is null"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins.size(), is( 1 ) ); + assertThat( coins.get( 0 ), notNullValue() ); + assertThat( coins.get( 0 ).getId(), is( 3 ) ); + assertThat( coins.get( 0 ).getCurrency(), nullValue() ); + + assertThat( statementInspector.getSqlQueries().size(), is( 1 ) ); + assertThat( statementInspector.getSqlQueries().get( 0 ), not( containsString( " join " ) )); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-15106") + public void testFkRefDereferenceNotAllowed() { + statementInspector.clear(); + + inTransaction( (session) -> { + try { + final String hql = "select c from Coin c where fk(c.currency).something"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + fail( "Expecting failure" ); + } + catch (IllegalArgumentException expected) { + assertThat( expected.getCause(), instanceOf( QueryException.class ) ); + } + catch (Exception e) { + fail( "Unexpected failure type : " + e ); + } + } ); + + inTransaction( (session) -> { + try { + final String hql = "select c from Coin c where currency.{fk}.something"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + } + catch (IllegalArgumentException expected) { + assertThat( expected.getCause(), instanceOf( QueryException.class ) ); + } + } ); + } + + @Before + public void prepareTestData() { + inTransaction( (session) -> { + Currency euro = new Currency( 1, "Euro" ); + Coin fiveC = new Coin( 1, "Five cents", euro ); + session.persist( euro ); + session.persist( fiveC ); + + Currency usd = new Currency( 2, "USD" ); + Coin penny = new Coin( 2, "Penny", usd ); + session.persist( usd ); + session.persist( penny ); + + Coin noCurrency = new Coin( 3, "N/A", null ); + session.persist( noCurrency ); + } ); + + inTransaction( (session) -> { + session.createQuery( "delete Currency where id = 1" ).executeUpdate(); + } ); + } + + @After + public void cleanupTest() throws Exception { + inTransaction( (session) -> { + session.createQuery( "delete Coin" ).executeUpdate(); + session.createQuery( "delete Currency" ).executeUpdate(); + } ); + } + + @Entity(name = "Coin") + public static class Coin { + private Integer id; + private String name; + private Currency currency; + + public Coin() { + } + + public Coin(Integer id, String name, Currency currency) { + this.id = id; + this.name = name; + this.currency = currency; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToOne(fetch = FetchType.LAZY) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn(name = "currency_fk", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) + public Currency getCurrency() { + return currency; + } + + public void setCurrency(Currency currency) { + this.currency = currency; + } + } + + @Entity(name = "Currency") + public static class Currency implements Serializable { + private Integer id; + private String name; + + public Currency() { + } + + public Currency(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/SynchronizedSpaceTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/SynchronizedSpaceTests.java new file mode 100644 index 0000000000000000000000000000000000000000..800220092b4a4818f975f1e6f67ae75d55b7c763 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/SynchronizedSpaceTests.java @@ -0,0 +1,402 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.orm.test.query.sql; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.persistence.Cacheable; +import javax.persistence.Entity; +import javax.persistence.EntityResult; +import javax.persistence.Id; +import javax.persistence.Query; +import javax.persistence.QueryHint; +import javax.persistence.SqlResultSetMapping; +import javax.persistence.Table; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.NamedNativeQuery; +import org.hibernate.cache.spi.CacheImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.jpa.QueryHints; +import org.hibernate.query.spi.NativeQueryImplementor; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +public class SynchronizedSpaceTests extends BaseNonConfigCoreFunctionalTestCase { + @Test + public void testNonSyncedCachedScenario() { + // CachedEntity updated by native-query without adding query spaces + // - the outcome should be all cached data being invalidated + + checkUseCase( + "cached_entity", + query -> {}, + // the 2 CachedEntity entries should not be there + false + ); + + // and of course, let's make sure the update happened :) + inTransaction( + session -> { + session.createQuery( "from CachedEntity", CachedEntity.class ).list().forEach( + cachedEntity -> assertThat( cachedEntity.name, is( "updated" ) ) + ); + } + ); + } + + private void checkUseCase( + String table, + Consumer queryConsumer, + boolean shouldExistAfter) { + + checkUseCase( + (session) -> { + final Query nativeQuery = session.createNativeQuery( "update " + table + " set name = 'updated'" ); + queryConsumer.accept( nativeQuery ); + return nativeQuery; + }, + Query::executeUpdate, + shouldExistAfter + ); + } + + private void checkUseCase( + Function queryProducer, + Consumer executor, + boolean shouldExistAfter) { + + // first, load both `CachedEntity` instances into the L2 cache + loadAll(); + + final CacheImplementor cacheSystem = sessionFactory().getCache(); + + // make sure they are there + assertThat( cacheSystem.containsEntity( CachedEntity.class, 1 ), is( true ) ); + assertThat( cacheSystem.containsEntity( CachedEntity.class, 2 ), is( true ) ); + + // create a query to update the specified table - allowing the passed consumer to register a space if needed + inTransaction( + session -> { + // notice the type is the JPA Query interface + final Query nativeQuery = queryProducer.apply( session ); + executor.accept( nativeQuery ); + } + ); + + // see if the entries exist based on the expectation + assertThat( cacheSystem.containsEntity( CachedEntity.class, 1 ), is( shouldExistAfter ) ); + assertThat( cacheSystem.containsEntity( CachedEntity.class, 2 ), is( shouldExistAfter ) ); + } + + @Test + public void testSyncedCachedScenario() { + final String tableName = "cached_entity"; + + checkUseCase( + tableName, + query -> ( (NativeQueryImplementor) query ).addSynchronizedQuerySpace( tableName ), + // the 2 CachedEntity entries should not be there + false + ); + + // and of course, let's make sure the update happened :) + inTransaction( + session -> { + session.createQuery( "from CachedEntity", CachedEntity.class ).list().forEach( + cachedEntity -> assertThat( cachedEntity.name, is( "updated" ) ) + ); + } + ); + } + + @Test + public void testNonSyncedNonCachedScenario() { + // NonCachedEntity updated by native-query without adding query spaces + // - the outcome should be all cached data being invalidated + + checkUseCase( + "non_cached_entity", + query -> {}, + // the 2 CachedEntity entries should not be there + false + ); + + // and of course, let's make sure the update happened :) + inTransaction( + session -> { + session.createQuery( "from NonCachedEntity", NonCachedEntity.class ).list().forEach( + cachedEntity -> assertThat( cachedEntity.name, is( "updated" ) ) + ); + } + ); + } + + @Test + public void testSyncedNonCachedScenario() { + // NonCachedEntity updated by native-query with query spaces + // - the caches for CachedEntity are not invalidated - they are not affected by the specified query-space + + final String tableName = "non_cached_entity"; + + checkUseCase( + tableName, + query -> ( (NativeQueryImplementor) query ).addSynchronizedQuerySpace( tableName ), + // the 2 CachedEntity entries should still be there + true + ); + + // and of course, let's make sure the update happened :) + inTransaction( + session -> { + session.createQuery( "from NonCachedEntity", NonCachedEntity.class ).list().forEach( + cachedEntity -> assertThat( cachedEntity.name, is( "updated" ) ) + ); + } + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingHint() { + // same as `#testSyncedNonCachedScenario`, but here using the hint + + final String tableName = "non_cached_entity"; + + checkUseCase( + tableName, + query -> query.setHint( QueryHints.HINT_NATIVE_SPACES, tableName ), + // the 2 CachedEntity entries should still be there + true + ); + + // and of course, let's make sure the update happened :) + inTransaction( + session -> { + session.createQuery( "from NonCachedEntity", NonCachedEntity.class ).list().forEach( + cachedEntity -> assertThat( cachedEntity.name, is( "updated" ) ) + ); + } + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingHintWithCollection() { + // same as `#testSyncedNonCachedScenario`, but here using the hint + + final String tableName = "non_cached_entity"; + final Set spaces = new HashSet<>(); + spaces.add( tableName ); + + checkUseCase( + tableName, + query -> query.setHint( QueryHints.HINT_NATIVE_SPACES, spaces ), + // the 2 CachedEntity entries should still be there + true + ); + + // and of course, let's make sure the update happened :) + inTransaction( + session -> { + session.createQuery( "from NonCachedEntity", NonCachedEntity.class ).list().forEach( + cachedEntity -> assertThat( cachedEntity.name, is( "updated" ) ) + ); + } + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingHintWithArray() { + // same as `#testSyncedNonCachedScenario`, but here using the hint + + final String tableName = "non_cached_entity"; + final String[] spaces = { tableName }; + + checkUseCase( + tableName, + query -> query.setHint( QueryHints.HINT_NATIVE_SPACES, spaces ), + // the 2 CachedEntity entries should still be there + true + ); + + // and of course, let's make sure the update happened :) + inTransaction( + session -> { + session.createQuery( "from NonCachedEntity", NonCachedEntity.class ).list().forEach( + cachedEntity -> assertThat( cachedEntity.name, is( "updated" ) ) + ); + } + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingAnnotationWithReturnClass() { + checkUseCase( + (session) -> session.createNamedQuery( "NonCachedEntity_return_class" ), + Query::getResultList, + true + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingAnnotationWithResultSetMapping() { + checkUseCase( + (session) -> session.createNamedQuery( "NonCachedEntity_resultset_mapping" ), + Query::getResultList, + true + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingAnnotationWithSpaces() { + checkUseCase( + (session) -> session.createNamedQuery( "NonCachedEntity_spaces" ), + Query::getResultList, + true + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingJpaAnnotationWithNoResultMapping() { + checkUseCase( + (session) -> session.createNamedQuery( "NonCachedEntity_raw_jpa" ), + Query::getResultList, + true + ); + } + + @Test + public void testSyncedNonCachedScenarioUsingJpaAnnotationWithHint() { + checkUseCase( + (session) -> session.createNamedQuery( "NonCachedEntity_hint_jpa" ), + Query::getResultList, + true + ); + } + + private void loadAll() { + inTransaction( + session -> { + session.createQuery( "from CachedEntity" ).list(); + + // this one is not strictly needed since this entity is not cached. + // but it helps my OCD feel better to have it ;) + session.createQuery( "from NonCachedEntity" ).list(); + } + ); + } + + public void prepareTest() { + inTransaction( + session -> { + session.persist( new CachedEntity( 1, "first cached" ) ); + session.persist( new CachedEntity( 2, "second cached" ) ); + + session.persist( new NonCachedEntity( 1, "first non-cached" ) ); + session.persist( new NonCachedEntity( 2, "second non-cached" ) ); + } + ); + + cleanupCache(); + } + + public void cleanupTest() { + cleanupCache(); + + inTransaction( + session -> { + session.createQuery( "delete CachedEntity" ).executeUpdate(); + session.createQuery( "delete NonCachedEntity" ).executeUpdate(); + } + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { CachedEntity.class, NonCachedEntity.class }; + } + + @Override + protected boolean overrideCacheStrategy() { + return false; + } + + @Entity( name = "CachedEntity" ) + @Table( name = "cached_entity" ) + @Cacheable( true ) + @Cache( usage = CacheConcurrencyStrategy.READ_WRITE ) + public static class CachedEntity { + @Id + private Integer id; + private String name; + + public CachedEntity() { + } + + public CachedEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + } + + @Entity( name = "NonCachedEntity" ) + @Table( name = "non_cached_entity" ) + @Cacheable( false ) + @NamedNativeQuery( + name = "NonCachedEntity_return_class", + query = "select * from non_cached_entity", + resultClass = NonCachedEntity.class + ) + @NamedNativeQuery( + name = "NonCachedEntity_resultset_mapping", + query = "select * from non_cached_entity", + resultSetMapping = "NonCachedEntity_resultset_mapping" + ) + @SqlResultSetMapping( + name = "NonCachedEntity_resultset_mapping", + entities = @EntityResult( entityClass = NonCachedEntity.class ) + ) + @NamedNativeQuery( + name = "NonCachedEntity_spaces", + query = "select * from non_cached_entity", + querySpaces = "non_cached_entity" + ) + @javax.persistence.NamedNativeQuery( + name = "NonCachedEntity_raw_jpa", + query = "select * from non_cached_entity" + ) + @javax.persistence.NamedNativeQuery( + name = "NonCachedEntity_hint_jpa", + query = "select * from non_cached_entity", + hints = { + @QueryHint( name = QueryHints.HINT_NATIVE_SPACES, value = "non_cached_entity" ) + } + ) + public static class NonCachedEntity { + @Id + private Integer id; + private String name; + + public NonCachedEntity() { + } + + public NonCachedEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/InClauseParameterPaddingTest.java b/hibernate-core/src/test/java/org/hibernate/query/InClauseParameterPaddingTest.java index f2bc0a869fb219941735bb1c6c31c3855beb947a..77860ab63fdc11ad562d47d2f6b340cae22d7571 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/InClauseParameterPaddingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/query/InClauseParameterPaddingTest.java @@ -15,12 +15,11 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** @@ -29,18 +28,13 @@ @TestForIssue( jiraKey = "HHH-12469" ) public class InClauseParameterPaddingTest extends BaseEntityManagerFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider; + private SQLStatementInterceptor sqlStatementInterceptor; @Override - public void buildEntityManagerFactory() { - connectionProvider = new PreparedStatementSpyConnectionProvider(); - super.buildEntityManagerFactory(); - } - - @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); + options.put( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() ); + options.put( AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, Boolean.TRUE.toString() ); } @Override @@ -50,15 +44,6 @@ public Class[] getAnnotatedClasses() { }; } - @Override - protected void addConfigOptions(Map options) { - options.put( AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, Boolean.TRUE.toString() ); - options.put( - org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - } - @Override protected void afterEntityManagerFactoryBuilt() { doInJPA( this::entityManagerFactory, entityManager -> { @@ -87,7 +72,7 @@ public void testInClauseParameterPadding() { } private void validateInClauseParameterPadding(String expectedInClause, Integer... ids) { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); doInJPA( this::entityManagerFactory, entityManager -> { return entityManager.createQuery( @@ -98,7 +83,7 @@ private void validateInClauseParameterPadding(String expectedInClause, Integer.. .getResultList(); } ); - assertTrue(connectionProvider.getPreparedSQLStatements().get( 0 ).endsWith( expectedInClause )); + assertTrue(sqlStatementInterceptor.getSqlQueries().get( 0 ).endsWith( expectedInClause )); } @Entity(name = "Person") diff --git a/hibernate-core/src/test/java/org/hibernate/query/MaxInExpressionParameterPaddingTest.java b/hibernate-core/src/test/java/org/hibernate/query/MaxInExpressionParameterPaddingTest.java index b60e60286037341413279c939a8505a9b5c887fe..92fddb131521bba58b54c726e9181cd8cb86ba93 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/MaxInExpressionParameterPaddingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/query/MaxInExpressionParameterPaddingTest.java @@ -19,6 +19,7 @@ import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -32,20 +33,15 @@ @RequiresDialect(H2Dialect.class) public class MaxInExpressionParameterPaddingTest extends BaseEntityManagerFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider; - public static final int MAX_COUNT = 15; - @Override - public void buildEntityManagerFactory() { - connectionProvider = new PreparedStatementSpyConnectionProvider(); - super.buildEntityManagerFactory(); - } + private SQLStatementInterceptor sqlStatementInterceptor; @Override - public void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); + options.put( AvailableSettings.USE_SQL_COMMENTS, Boolean.TRUE.toString() ); + options.put( AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, Boolean.TRUE.toString() ); } @Override @@ -55,15 +51,6 @@ public Class[] getAnnotatedClasses() { }; } - @Override - protected void addConfigOptions(Map options) { - options.put( AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, Boolean.TRUE.toString() ); - options.put( - AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - } - @Override protected Dialect getDialect() { return new MaxCountInExpressionH2Dialect(); @@ -84,7 +71,7 @@ protected void afterEntityManagerFactoryBuilt() { @Test public void testInClauseParameterPadding() { - connectionProvider.clear(); + sqlStatementInterceptor.clear(); doInJPA( this::entityManagerFactory, entityManager -> { return entityManager.createQuery( @@ -102,7 +89,7 @@ public void testInClauseParameterPadding() { } expectedInClause.append( ")" ); - assertTrue(connectionProvider.getPreparedSQLStatements().get( 0 ).endsWith( expectedInClause.toString() )); + assertTrue(sqlStatementInterceptor.getSqlQueries().get( 0 ).endsWith( expectedInClause.toString() )); } @Entity(name = "Person") diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpressionTest.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpressionTest.java index 29a055efa43991adbc190f763bb0d52620aceb03..228df1c5170afe2d3e7e082ea31e1cd3f61faae0 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpressionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpressionTest.java @@ -18,6 +18,7 @@ import javax.persistence.criteria.Path; import javax.persistence.criteria.Root; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; @@ -61,6 +62,7 @@ public void testCaseClause() { } @Test + @SkipForDialect(value = DB2Dialect.class, comment = "We would need casts in the case clauses. See HHH-12822.") public void testEqualClause() { doInHibernate( this::sessionFactory, session -> { CriteriaBuilder cb = session.getCriteriaBuilder(); diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/CacheKeyEmbeddedIdEnanchedTest.java b/hibernate-core/src/test/java/org/hibernate/serialization/CacheKeyEmbeddedIdEnanchedTest.java new file mode 100644 index 0000000000000000000000000000000000000000..134f541fc4fcc19e5d9b7300b953883e006541af --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/serialization/CacheKeyEmbeddedIdEnanchedTest.java @@ -0,0 +1,86 @@ +package org.hibernate.serialization; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Map; + +import org.hibernate.cache.internal.DefaultCacheKeysFactory; +import org.hibernate.cache.internal.SimpleCacheKeysFactory; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cfg.Environment; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.serialization.entity.BuildRecord; +import org.hibernate.serialization.entity.BuildRecordId; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.cache.CachingRegionFactory; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions(lazyLoading = true, inlineDirtyChecking = true) +@TestForIssue( jiraKey = "HHH-14843") +public class CacheKeyEmbeddedIdEnanchedTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected void addSettings(Map settings) { + settings.put( Environment.USE_SECOND_LEVEL_CACHE, "true" ); + settings.put( Environment.CACHE_REGION_FACTORY, CachingRegionFactory.class.getName() ); + settings.put( Environment.DEFAULT_CACHE_CONCURRENCY_STRATEGY, "transactional" ); + settings.put( "javax.persistence.sharedCache.mode", "ALL" ); + settings.put( Environment.CACHE_KEYS_FACTORY, DefaultCacheKeysFactory.INSTANCE.getClass().getName() ); + } + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { BuildRecord.class }; + } + + @Test + public void testDefaultCacheKeysFactorySerialization() throws Exception { + testId( DefaultCacheKeysFactory.INSTANCE, BuildRecord.class.getName(), new BuildRecordId( 2l ) ); + } + + @Test + public void testSimpleCacheKeysFactorySerialization() throws Exception { + testId( SimpleCacheKeysFactory.INSTANCE, BuildRecord.class.getName(), new BuildRecordId( 2l ) ); + } + + private void testId(CacheKeysFactory cacheKeysFactory, String entityName, Object id) throws Exception { + final EntityPersister persister = sessionFactory().getMetamodel().entityPersister( entityName ); + final Object key = cacheKeysFactory.createEntityKey( + id, + persister, + sessionFactory(), + null + ); + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final ObjectOutputStream oos = new ObjectOutputStream( baos ); + oos.writeObject( key ); + + final ObjectInputStream ois = new ObjectInputStream( new ByteArrayInputStream( baos.toByteArray() ) ); + final Object keyClone = ois.readObject(); + + assertEquals( key, keyClone ); + assertEquals( keyClone, key ); + + assertEquals( key.hashCode(), keyClone.hashCode() ); + + final Object idClone = cacheKeysFactory.getEntityId( keyClone ); + + assertEquals( id.hashCode(), idClone.hashCode() ); + assertEquals( id, idClone ); + assertEquals( idClone, id ); + assertTrue( persister.getIdentifierType().isEqual( id, idClone, sessionFactory() ) ); + assertTrue( persister.getIdentifierType().isEqual( idClone, id, sessionFactory() ) ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..566e3681166a355ca5716372d86cbb527aa0a851 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java @@ -0,0 +1,320 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.serialization; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.annotations.LazyCollection; +import org.hibernate.annotations.LazyCollectionOption; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.internal.util.SerializationHelper; +import org.hibernate.proxy.AbstractLazyInitializer; +import org.hibernate.proxy.HibernateProxy; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Selaron + */ +public class EntityProxySerializationTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { SimpleEntity.class, ChildEntity.class }; + } + + @Override + protected void configure(final Configuration configuration) { + // enable LL without TX, which used to cause problems when serializing proxies (see HHH-12720) + configuration.setProperty( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, Boolean.TRUE.toString() ); + } + + /** + * Prepare and persist a {@link SimpleEntity} with two {@link ChildEntity}. + */ + @Before + public void prepare() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + + try { + final Number count = (Number) s.createQuery("SELECT count(ID) FROM SimpleEntity").getSingleResult(); + if (count.longValue() > 0L) { + // entity already added previously + return; + } + + final SimpleEntity entity = new SimpleEntity(); + entity.setId( 1L ); + entity.setName( "TheParent" ); + + final ChildEntity c1 = new ChildEntity(); + c1.setId( 1L ); + c1.setParent( entity ); + + final ChildEntity c2 = new ChildEntity(); + c2.setId( 2L ); + c2.setParent( entity ); + + s.save( entity ); + s.save( c1 ); + s.save( c2 ); + } + finally { + t.commit(); + s.close(); + } + } + + /** + * Tests that serializing an initialized proxy will serialize the target instead. + */ + @SuppressWarnings("unchecked") + @Test + public void testInitializedProxySerializationIfTargetInPersistenceContext() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final ChildEntity child = s.find( ChildEntity.class, 1L ); + + final SimpleEntity parent = child.getParent(); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof HibernateProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + // Initialize the proxy + parent.getName(); + assertTrue( Hibernate.isInitialized( parent ) ); + + // serialize/deserialize the proxy + final SimpleEntity deserializedParent = (SimpleEntity) SerializationHelper.clone( parent ); + + // assert the deserialized object is no longer a proxy, but the target of the proxy + assertFalse( deserializedParent instanceof HibernateProxy ); + assertEquals( "TheParent", deserializedParent.getName() ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that serializing a proxy which is not initialized + * but whose target has been (separately) added to the persistence context + * will serialize the target instead. + */ + @SuppressWarnings("unchecked") + @Test + public void testUninitializedProxySerializationIfTargetInPersistenceContext() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final ChildEntity child = s.find( ChildEntity.class, 1L ); + + final SimpleEntity parent = child.getParent(); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof HibernateProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + // Load the target of the proxy without the proxy being made aware of it + s.detach( parent ); + s.find( SimpleEntity.class, 1L ); + s.update( parent ); + + // assert we still have an uninitialized proxy + assertFalse( Hibernate.isInitialized( parent ) ); + + // serialize/deserialize the proxy + final SimpleEntity deserializedParent = (SimpleEntity) SerializationHelper.clone( parent ); + + // assert the deserialized object is no longer a proxy, but the target of the proxy + assertFalse( deserializedParent instanceof HibernateProxy ); + assertEquals( "TheParent", deserializedParent.getName() ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that lazy loading without transaction nor open session is generally + * working. The magic is done by {@link AbstractLazyInitializer} who opens a + * temporary session. + */ + @SuppressWarnings("unchecked") + @Test + public void testProxyInitializationWithoutTX() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final ChildEntity child = s.find( ChildEntity.class, 1L ); + + final SimpleEntity parent = child.getParent(); + + t.rollback(); + session.close(); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof HibernateProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + assertEquals( "TheParent", parent.getName() ); + + // assert we have an initialized proxy now + assertTrue( Hibernate.isInitialized( parent ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that lazy loading without transaction nor open session is generally + * working. The magic is done by {@link AbstractLazyInitializer} who opens a + * temporary session. + */ + @SuppressWarnings("unchecked") + @Test + @TestForIssue(jiraKey = "HHH-12720") + public void testProxyInitializationWithoutTXAfterDeserialization() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final ChildEntity child = s.find( ChildEntity.class, 1L ); + + final SimpleEntity parent = child.getParent(); + + // destroy AbstractLazyInitializer internal state + final SimpleEntity deserializedParent = (SimpleEntity) SerializationHelper.clone( parent ); + + t.rollback(); + session.close(); + + // assert we have an uninitialized proxy + assertTrue( deserializedParent instanceof HibernateProxy ); + assertFalse( Hibernate.isInitialized( deserializedParent ) ); + + assertEquals( "TheParent", deserializedParent.getName() ); + + // assert we have an initialized proxy now + assertTrue( Hibernate.isInitialized( deserializedParent ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + @Entity(name = "SimpleEntity") + static class SimpleEntity implements Serializable { + + private Long id; + + private String name; + + Set children = new HashSet<>(); + + @Id + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + @OneToMany(targetEntity = ChildEntity.class, mappedBy = "parent") + @LazyCollection(LazyCollectionOption.EXTRA) + @Fetch(FetchMode.SELECT) + public Set getChildren() { + return children; + } + + public void setChildren(final Set children) { + this.children = children; + } + + } + + @Entity(name = "ChildEntity") + static class ChildEntity { + private Long id; + + private SimpleEntity parent; + + @Id + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + @LazyToOne(LazyToOneOption.PROXY) + public SimpleEntity getParent() { + return parent; + } + + public void setParent(final SimpleEntity parent) { + this.parent = parent; + } + + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java b/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0b8eb3993c150d764358f6d3fdc79044c3bcec13 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java @@ -0,0 +1,243 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.serialization; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.EntityMode; +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.internal.util.SerializationHelper; +import org.hibernate.proxy.AbstractLazyInitializer; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.map.MapProxy; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Selaron + */ +public class MapProxySerializationTest extends BaseCoreFunctionalTestCase { + + @Override + protected String[] getMappings() { + return new String[] { "serialization/DynamicMapMappings.hbm.xml" }; + } + + @Override + protected void configure(final Configuration configuration) { + // enable LL without TX, which used to cause problems when serializing proxies (see HHH-12720) + configuration.setProperty( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, Boolean.TRUE.toString() ); + + // dynamic-map by default. + configuration.setProperty( AvailableSettings.DEFAULT_ENTITY_MODE, EntityMode.MAP.getExternalName() ); + } + + @Before + public void prepare() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + + try { + final Number count = (Number) s.createQuery("SELECT count(ID) FROM SimpleEntity").getSingleResult(); + if (count.longValue() > 0L) { + // entity already added previously + return; + } + + final Map entity = new HashMap<>(); + entity.put( "id", 1L ); + entity.put( "name", "TheParent" ); + + final Map c1 = new HashMap<>(); + c1.put( "id", 1L ); + c1.put( "parent", entity ); + + s.save( "SimpleEntity", entity ); + s.save( "ChildEntity", c1 ); + } + finally { + t.commit(); + s.close(); + } + } + + /** + * Tests that serializing an initialized proxy will serialize the target instead. + */ + @SuppressWarnings("unchecked") + @Test + @TestForIssue(jiraKey = "HHH-7686") + public void testInitializedProxySerializationIfTargetInPersistenceContext() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final Map child = (Map) s.load( "ChildEntity", 1L ); + + final Map parent = (Map) child.get( "parent" ); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof MapProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + // Initialize the proxy + parent.get( "name" ); + assertTrue( Hibernate.isInitialized( parent ) ); + + // serialize/deserialize the proxy + final Map deserializedParent = + (Map) SerializationHelper.clone( (Serializable) parent ); + + // assert the deserialized object is no longer a proxy, but the target of the proxy + assertFalse( deserializedParent instanceof HibernateProxy ); + assertEquals( "TheParent", deserializedParent.get( "name" ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that serializing a proxy which is not initialized + * but whose target has been (separately) added to the persistence context + * will serialized the target instead. + */ + @SuppressWarnings("unchecked") + @Test + @TestForIssue(jiraKey = "HHH-7686") + public void testUninitializedProxySerializationIfTargetInPersistenceContext() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final Map child = (Map) s.load( "ChildEntity", 1L ); + + final Map parent = (Map) child.get( "parent" ); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof MapProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + // Load the target of the proxy without the proxy being made aware of it + s.detach( parent ); + s.byId( "SimpleEntity" ).load( 1L ); + s.update( parent ); + + // assert we still have an uninitialized proxy + assertFalse( Hibernate.isInitialized( parent ) ); + + // serialize/deserialize the proxy + final Map deserializedParent = + (Map) SerializationHelper.clone( (Serializable) parent ); + + // assert the deserialized object is no longer a proxy, but the target of the proxy + assertFalse( deserializedParent instanceof HibernateProxy ); + assertEquals( "TheParent", deserializedParent.get( "name" ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that lazy loading without transaction nor open session is generally + * working. The magic is done by {@link AbstractLazyInitializer} who opens a + * temporary session. + */ + @SuppressWarnings("unchecked") + @Test + public void testProxyInitializationWithoutTX() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final Map child = (Map) s.load( "ChildEntity", 1L ); + + final Map parent = (Map) child.get( "parent" ); + + t.rollback(); + session.close(); + + // assert we have an uninitialized proxy + assertTrue( parent instanceof MapProxy ); + assertFalse( Hibernate.isInitialized( parent ) ); + + assertEquals( "TheParent", parent.get( "name" ) ); + + // assert we have an initialized proxy now + assertTrue( Hibernate.isInitialized( parent ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + + /** + * Tests that lazy loading without transaction nor open session is generally + * working. The magic is done by {@link AbstractLazyInitializer} who opens a + * temporary session. + */ + @SuppressWarnings("unchecked") + @Test + @TestForIssue(jiraKey = "HHH-7686") + public void testProxyInitializationWithoutTXAfterDeserialization() { + final Session s = openSession(); + + final Transaction t = s.beginTransaction(); + try { + final Map child = (Map) s.load( "ChildEntity", 1L ); + + final Map parent = (Map) child.get( "parent" ); + + // destroy AbstractLazyInitializer internal state + final Map deserializedParent = + (Map) SerializationHelper.clone( (Serializable) parent ); + + t.rollback(); + session.close(); + + // assert we have an uninitialized proxy + assertTrue( deserializedParent instanceof MapProxy ); + assertFalse( Hibernate.isInitialized( deserializedParent ) ); + + assertEquals( "TheParent", deserializedParent.get( "name" ) ); + + // assert we have an initialized proxy now + assertTrue( Hibernate.isInitialized( deserializedParent ) ); + } + finally { + if ( t.isActive() ) { + t.rollback(); + } + s.close(); + } + } + +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/entity/BuildRecord.java b/hibernate-core/src/test/java/org/hibernate/serialization/entity/BuildRecord.java new file mode 100644 index 0000000000000000000000000000000000000000..f4a173a2f66260dde2275f4ba34f266dd4afedb9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/serialization/entity/BuildRecord.java @@ -0,0 +1,29 @@ +package org.hibernate.serialization.entity; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; + +@Entity +public class BuildRecord { + + @EmbeddedId + private BuildRecordId id; + + private String name; + + public BuildRecordId getId() { + return id; + } + + public void setId(BuildRecordId id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/entity/BuildRecordId.java b/hibernate-core/src/test/java/org/hibernate/serialization/entity/BuildRecordId.java new file mode 100644 index 0000000000000000000000000000000000000000..b3c15f593d82e85f8eabf689ed0dfff769c5a09d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/serialization/entity/BuildRecordId.java @@ -0,0 +1,43 @@ +package org.hibernate.serialization.entity; + +import java.io.Serializable; +import java.util.Objects; +import javax.persistence.Embeddable; + +@Embeddable +public class BuildRecordId implements Serializable { + private long id; + + public BuildRecordId() { + } + + public BuildRecordId(long id) { + this.id = id; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + BuildRecordId longId = (BuildRecordId) o; + return id == longId.id; + } + + @Override + public int hashCode() { + return Objects.hash( id ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/stat/internal/StatsNamedContainerNullComputedValueTest.java b/hibernate-core/src/test/java/org/hibernate/stat/internal/StatsNamedContainerNullComputedValueTest.java new file mode 100644 index 0000000000000000000000000000000000000000..afd5b1088d0752421914936204fb287baa80d937 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/stat/internal/StatsNamedContainerNullComputedValueTest.java @@ -0,0 +1,30 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.stat.internal; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.junit.Assert.assertNull; + +@TestForIssue(jiraKey = "HHH-13645") +public class StatsNamedContainerNullComputedValueTest { + + @Test + public void testNullComputedValue() { + final StatsNamedContainer statsNamedContainer = new StatsNamedContainer(); + assertNull( + statsNamedContainer.getOrCompute( + "key", + v -> { + return null; + } + ) + ); + } + +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/EntityTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/EntityTest.java index afaf60ebd8cfdc3588d6f85ff9ee0c07f28fab37..5dd684f87056baceeb098b548146089c3d815980 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/EntityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/EntityTest.java @@ -24,9 +24,7 @@ import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl; import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.MySQL57Dialect; -import org.hibernate.dialect.MySQL8Dialect; -import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.Oracle10gDialect; import org.hibernate.query.Query; import org.hibernate.tool.hbm2ddl.SchemaExport; @@ -373,7 +371,7 @@ public void testNonGetter() throws Exception { @SkipForDialect(value = Oracle10gDialect.class, comment = "oracle12c returns time in getDate. For now, skip.") public void testTemporalType() throws Exception { - final ZoneId zoneId = ( Dialect.getDialect() instanceof MySQL8Dialect ) ? ZoneId.of( "UTC") + final ZoneId zoneId = ( Dialect.getDialect() instanceof MySQL5Dialect ) ? ZoneId.of( "UTC") : ZoneId.systemDefault(); Flight airFrance = doInHibernate( this::sessionFactory, session -> { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/any/EmbeddedAnyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/any/EmbeddedAnyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b64902b8729f8b9c74715b7b09ebb381c36cf65c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/any/EmbeddedAnyTest.java @@ -0,0 +1,213 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.any; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Table; + +import org.hibernate.annotations.Any; +import org.hibernate.annotations.AnyMetaDef; +import org.hibernate.annotations.MetaValue; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.junit.Test; + +public class EmbeddedAnyTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ Foo.class, Bar1.class, Bar2.class }; + } + + @Test + public void testEmbeddedAny() { + doInJPA( this::entityManagerFactory, em -> { + Foo foo1 = new Foo(); + foo1.setId( 1 ); + + Bar1 bar1 = new Bar1(); + bar1.setId( 1 ); + bar1.setBar1( "bar 1" ); + bar1.setBarType( "1" ); + + FooEmbeddable foo1Embedded = new FooEmbeddable(); + foo1Embedded.setBar( bar1 ); + + foo1.setFooEmbedded( foo1Embedded ); + + em.persist( bar1 ); + em.persist( foo1 ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Foo foo2 = new Foo(); + foo2.setId( 2 ); + + Bar2 bar2 = new Bar2(); + bar2.setId( 2 ); + bar2.setBar2( "bar 2" ); + bar2.setBarType( "2" ); + + FooEmbeddable foo2Embedded = new FooEmbeddable(); + foo2Embedded.setBar( bar2 ); + + foo2.setFooEmbedded( foo2Embedded ); + + em.persist( bar2 ); + em.persist( foo2 ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Foo foo1 = em.find( Foo.class, 1 ); + + assertTrue( foo1.getFooEmbedded().getBar() instanceof Bar1 ); + assertEquals( "bar 1", ( (Bar1) foo1.getFooEmbedded().getBar() ).getBar1() ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Foo foo2 = em.find( Foo.class, 2 ); + + assertTrue( foo2.getFooEmbedded().getBar() instanceof Bar2 ); + assertEquals( "bar 2", ( (Bar2) foo2.getFooEmbedded().getBar() ).getBar2() ); + } ); + } + + @Entity(name = "Foo") + public static class Foo { + + @Id + private Integer id; + + @Embedded + private FooEmbeddable fooEmbedded; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public FooEmbeddable getFooEmbedded() { + return fooEmbedded; + } + + public void setFooEmbedded(FooEmbeddable fooEmbedded) { + this.fooEmbedded = fooEmbedded; + } + } + + @Embeddable + public static class FooEmbeddable { + + @AnyMetaDef(idType = "integer", metaType = "string", metaValues = { + @MetaValue(value = "1", targetEntity = Bar1.class), + @MetaValue(value = "2", targetEntity = Bar2.class) + }) + @Any(metaColumn = @Column(name = "bar_type")) + @JoinColumn(name = "bar_id") + private BarInt bar; + + public BarInt getBar() { + return bar; + } + + public void setBar(BarInt bar) { + this.bar = bar; + } + } + + public interface BarInt { + + String getBarType(); + } + + @Entity(name = "Bar1") + @Table(name = "bar") + public static class Bar1 implements BarInt { + + @Id + private Integer id; + + private String bar1; + + @Column(name = "bar_type") + private String barType; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getBar1() { + return bar1; + } + + public void setBar1(String bar1) { + this.bar1 = bar1; + } + + @Override + public String getBarType() { + return barType; + } + + public void setBarType(String barType) { + this.barType = barType; + } + } + + @Entity(name = "Bar2") + @Table(name = "bar") + public static class Bar2 implements BarInt { + + @Id + private Integer id; + + private String bar2; + + @Column(name = "bar_type") + private String barType; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getBar2() { + return bar2; + } + + public void setBar2(String bar2) { + this.bar2 = bar2; + } + + @Override + public String getBarType() { + return barType; + } + + public void setBarType(String barType) { + this.barType = barType; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/collection/xml/EmbeddableWithOneToMany_HHH_11302_xml_Test.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/collection/xml/EmbeddableWithOneToMany_HHH_11302_xml_Test.java index 1e47fd62eef732a79456f7ae88b94b78ad1f8c7d..ee345f54421545e0d8c0aac3bd4b39199c8d0613 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/collection/xml/EmbeddableWithOneToMany_HHH_11302_xml_Test.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embeddables/collection/xml/EmbeddableWithOneToMany_HHH_11302_xml_Test.java @@ -26,8 +26,6 @@ public class EmbeddableWithOneToMany_HHH_11302_xml_Test extends BaseEntityManagerFunctionalTestCase { - PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( false, false ); - @Override public String[] getEjb3DD() { return new String[] { @@ -45,15 +43,6 @@ public void buildEntityManagerFactory() { "@OneToMany, @ManyToMany or @ElementCollection cannot be used inside an @Embeddable that is also contained within an @ElementCollection" ) ); } - finally { - connectionProvider.stop(); - } - } - - protected Map buildSettings() { - Map settings = super.buildSettings(); - settings.put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider ); - return settings; } @Test diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java index 83cdea6f8e7711ac900ee811d0489816b6f0c4d8..526a7e062d815b7298fb4aa1aa83b62c575aaeb1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java @@ -18,8 +18,10 @@ import org.hibernate.Transaction; import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.testing.transaction.TransactionUtil; @@ -27,6 +29,7 @@ import org.hibernate.test.annotations.embedded.Leg.Frequency; import org.hibernate.test.util.SchemaUtil; +import org.junit.After; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -39,6 +42,16 @@ * @author Emmanuel Bernard */ public class EmbeddedTest extends BaseNonConfigCoreFunctionalTestCase { + + @After + public void cleanup() { + TransactionUtil.doInHibernate( this::sessionFactory, session ->{ + for ( Person person : session.createQuery( "from Person", Person.class ).getResultList() ) { + session.delete( person ); + } + } ); + } + @Test public void testSimple() throws Exception { Person person = new Person(); @@ -145,6 +158,7 @@ public void testQueryWithEmbeddedParameterAllNull() throws Exception { @Test @TestForIssue(jiraKey = "HHH-8172") + @SkipForDialect( value = SybaseDialect.class, comment = "skip for Sybase because (null = null) evaluates to true") @FailureExpected(jiraKey = "HHH-8172") public void testQueryWithEmbeddedParameterOneNull() throws Exception { Person person = new Person(); @@ -601,7 +615,7 @@ public void testEmbeddedAndOneToManyHql() throws Exception { public void testDefaultCollectionTable() throws Exception { //are the tables correct? assertTrue( SchemaUtil.isTablePresent( "WealthyPerson_vacationHomes", metadata() ) ); - assertTrue( SchemaUtil.isTablePresent( "WealthyPerson_legacyVacationHomes", metadata() ) ); + assertTrue( SchemaUtil.isTablePresent( "WelPers_LegacyVacHomes", metadata() ) ); assertTrue( SchemaUtil.isTablePresent( "WelPers_VacHomes", metadata() ) ); //just to make sure, use the mapping diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java index 3b24b37908a7fa2b24966476d78063985799e77f..05df8f1df60b6a09d9f58d528649f18fbd84c8af 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java @@ -19,6 +19,7 @@ public class WealthyPerson extends Person { protected Set
        vacationHomes = new HashSet
        (); @ElementCollection + @CollectionTable(name = "WelPers_LegacyVacHomes") protected Set
        legacyVacationHomes = new HashSet
        (); @ElementCollection diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableEntityUpdateQueryHandlingModeExceptionTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableEntityUpdateQueryHandlingModeExceptionTest.java index 60ed5a7dc8f705903a49fb3d7d4f3ba83b2298c5..32412378a29c88101eaece88e2c4823dd0d44d03 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableEntityUpdateQueryHandlingModeExceptionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/immutable/ImmutableEntityUpdateQueryHandlingModeExceptionTest.java @@ -8,6 +8,8 @@ import java.util.Map; +import javax.persistence.PersistenceException; + import org.hibernate.HibernateException; import org.hibernate.cfg.AvailableSettings; @@ -18,6 +20,7 @@ import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** @@ -54,10 +57,14 @@ public void testBulkUpdate(){ .setParameter( "name", "N/A" ) .executeUpdate(); } ); - fail("Should throw HibernateException"); + fail("Should throw PersistenceException"); } - catch (HibernateException e) { - assertEquals( "The query: [update Country set name = :name] attempts to update an immutable entity: [Country]", e.getMessage() ); + catch (PersistenceException e) { + assertTrue( e.getCause() instanceof HibernateException ); + assertEquals( + "The query: [update Country set name = :name] attempts to update an immutable entity: [Country]", + e.getCause().getMessage() + ); } doInHibernate( this::sessionFactory, session -> { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/loader/collectioninembedded/LoaderCollectionInEmbeddedTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/loader/collectioninembedded/LoaderCollectionInEmbeddedTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6a63338bce6078e1392c2cab89004b4f84c2bf69 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/loader/collectioninembedded/LoaderCollectionInEmbeddedTest.java @@ -0,0 +1,76 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.loader.collectioninembedded; + +import java.util.Iterator; +import java.util.Set; + +import org.hibernate.ObjectNotFoundException; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.resource.transaction.spi.TransactionStatus; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +@TestForIssue(jiraKey = "") +public class LoaderCollectionInEmbeddedTest extends BaseCoreFunctionalTestCase { + @Override + protected String[] getXmlFiles() { + return new String[] { + "org/hibernate/test/annotations/loader/collectioninembedded/Loader.hbm.xml" + }; + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Player.class, + Team.class + }; + } + + @Test + public void testBasic() throws Exception { + // set up data... + Session s = openSession( ); + Transaction tx = s.beginTransaction(); + Team t = new Team(); + Player p = new Player(); + p.setName( "me" ); + t.getDetails().getPlayers().add( p ); + p.setTeam( t ); + s.persist(p); + s.persist( t ); + tx.commit(); + s.close(); + + s = openSession(); + tx = s.beginTransaction(); + Team t2 = s.load( Team.class, t.getId() ); + Set players = t2.getDetails().getPlayers(); + Iterator iterator = players.iterator(); + assertEquals( "me", iterator.next().getName() ); + tx.commit(); + s.close(); + + // clean up data + s = openSession(); + tx = s.beginTransaction(); + t = s.get( Team.class, t2.getId() ); + p = s.get( Player.class, p.getId() ); + s.delete( p ); + s.delete( t ); + tx.commit(); + s.close(); + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/loader/collectioninembedded/Player.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/loader/collectioninembedded/Player.java new file mode 100644 index 0000000000000000000000000000000000000000..bd51a04cd73c227117107abb9a26c11e7d1600a3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/loader/collectioninembedded/Player.java @@ -0,0 +1,53 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.loader.collectioninembedded; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; + +@Entity +public class Player { + + private Long id; + private Team team; + private String name; + + @Id + @GeneratedValue + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @ManyToOne(targetEntity = Team.class) + @Fetch(FetchMode.SELECT) + @JoinColumn(name = "team_id") + public Team getTeam() { + return team; + } + + public void setTeam(Team team) { + this.team = team; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/loader/collectioninembedded/Team.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/loader/collectioninembedded/Team.java new file mode 100644 index 0000000000000000000000000000000000000000..96438b1f63b75ae86bdd24e55ccd1745added282 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/loader/collectioninembedded/Team.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.loader.collectioninembedded; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.annotations.Loader; + +@Entity +public class Team { + private Long id; + private TeamDetails details = new TeamDetails(); + + @Id + @GeneratedValue + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public TeamDetails getDetails() { + return details; + } + + public void setDetails(TeamDetails details) { + this.details = details; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/loader/collectioninembedded/TeamDetails.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/loader/collectioninembedded/TeamDetails.java new file mode 100644 index 0000000000000000000000000000000000000000..c4e8763fbaa32ea8b05d968730d29b123c8351d1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/loader/collectioninembedded/TeamDetails.java @@ -0,0 +1,35 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.loader.collectioninembedded; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Embeddable; +import javax.persistence.FetchType; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.annotations.Loader; + +@Embeddable +public class TeamDetails { + + private Set players = new HashSet(); + + @OneToMany(targetEntity = Player.class, mappedBy = "team", fetch = FetchType.EAGER) + @Fetch(FetchMode.SELECT) + @Loader(namedQuery = "loadByTeam") + public Set getPlayers() { + return players; + } + + public void setPlayers(Set players) { + this.players = players; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/naturalid/ImmutableNaturalKeyLookupTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/naturalid/ImmutableNaturalKeyLookupTest.java index 58ea046c3e05125668b9d38a864e4b782814d191..1ce438654c7448b41ada76a2a9925d9ac6e994aa 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/naturalid/ImmutableNaturalKeyLookupTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/naturalid/ImmutableNaturalKeyLookupTest.java @@ -132,8 +132,10 @@ public void testCriteriaWithFetchModeJoinCollection() { newTx = s.beginTransaction(); // please enable - // log4j.logger.org.hibernate.cache.StandardQueryCache=DEBUG - // log4j.logger.org.hibernate.cache.UpdateTimestampsCache=DEBUG + // logger.standard-query-cache.name=org.hibernate.cache.StandardQueryCache + // logger.standard-query-cache.level=debug + // logger.update-timestamps-cache.name=org.hibernate.cache.UpdateTimestampsCache + // logger.update-timestamps-cache.level=debug // to see that isUpToDate is called where not appropriated Assert.assertTrue( s.getSessionFactory().getStatistics().isStatisticsEnabled() ); @@ -228,8 +230,10 @@ public void testCriteriaWithAliasOneToOneJoin() { newTx = s.beginTransaction(); // please enable - // log4j.logger.org.hibernate.cache.StandardQueryCache=DEBUG - // log4j.logger.org.hibernate.cache.UpdateTimestampsCache=DEBUG + // logger.standard-query-cache.name=org.hibernate.cache.StandardQueryCache + // logger.standard-query-cache.level=debug + // logger.update-timestamps-cache.name=org.hibernate.cache.UpdateTimestampsCache + // logger.update-timestamps-cache.level=debug // to see that isUpToDate is called where not appropriated Assert.assertTrue( s.getSessionFactory().getStatistics().isStatisticsEnabled() ); @@ -274,8 +278,10 @@ public void testSubCriteriaOneToOneJoin() { newTx = s.beginTransaction(); // please enable - // log4j.logger.org.hibernate.cache.StandardQueryCache=DEBUG - // log4j.logger.org.hibernate.cache.UpdateTimestampsCache=DEBUG + // logger.standard-query-cache.name=org.hibernate.cache.StandardQueryCache + // logger.standard-query-cache.level=debug + // logger.update-timestamps-cache.name=org.hibernate.cache.UpdateTimestampsCache + // logger.update-timestamps-cache.level=debug // to see that isUpToDate is called where not appropriated Assert.assertTrue( s.getSessionFactory().getStatistics().isStatisticsEnabled() ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/CriteriaTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/CriteriaTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c7dbae580e6597d5242526533e519e8b1f35b5d2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/CriteriaTest.java @@ -0,0 +1,261 @@ +package org.hibernate.test.annotations.notfound; + +import java.util.List; +import javax.persistence.Column; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.Criteria; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.criterion.ForeingKeyProjection; +import org.hibernate.criterion.ProjectionList; +import org.hibernate.criterion.Projections; +import org.hibernate.criterion.PropertyProjection; +import org.hibernate.criterion.Restrictions; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +@TestForIssue(jiraKey = "HHH-15425") +public class CriteriaTest extends BaseCoreFunctionalTestCase { + + private Long personId = 1l; + private Long addressId = 2l; + + private Long personId2 = 3l; + private Long addressId2 = 4l; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class, Address.class, Street.class }; + } + + @Before + public void setUp() { + inTransaction( + session -> { + Address address = new Address( addressId, "Lollard Street, London" ); + Person person = new Person( personId, "andrea", address ); + + session.save( address ); + session.save( person ); + + Address address2 = new Address( addressId2, "Via Marconi, Rome" ); + Person person2 = new Person( personId2, "Fab", address2 ); + + session.save( address2 ); + session.save( person2 ); + } + ); + + inTransaction( + session -> + session.createNativeQuery( "update PERSON_TABLE set DDID = 100 where id = 1" ).executeUpdate() + ); + } + + @After + public void tearDown() { + inTransaction( + session -> { + session.createQuery( "delete from Person" ).executeUpdate(); + session.createQuery( "delete from Address" ).executeUpdate(); + } + ); + } + + @Test + public void selectAssociationId() { + inTransaction( + session -> { + Criteria criteria = session.createCriteria( Person.class, "p" ); + ProjectionList projList = Projections.projectionList(); + PropertyProjection property = Projections.property( "address.id" ); + projList.add( property ); + criteria.setProjection( projList ); + + List results = criteria.list(); + assertThat( results.size(), is( 1 ) ); + } + ); + } + + + @Test + public void selectAssociationIdWithRestrictions() { + inTransaction( + session -> { + Criteria criteria = session.createCriteria( Person.class, "p" ); + ProjectionList projList = Projections.projectionList(); + PropertyProjection property = Projections.property( "address.id" ); + projList.add( property ); + criteria.setProjection( projList ); + criteria.add( Restrictions.eq( "address.id", 1L ) ); + + criteria.list(); + } + ); + } + + @Test + public void testRestrictionOnAssociationId() { + inTransaction( + session -> { + Criteria criteria = session.createCriteria( Person.class, "p" ); + criteria.add( Restrictions.eq( "address.id", 1L ) ); + criteria.list(); + } + ); + } + + @Test + public void selectAssociationFKTest() { + inTransaction( + session -> { + Criteria criteria = session.createCriteria( Person.class, "p" ); + ProjectionList projList = Projections.projectionList(); + ForeingKeyProjection property = Projections.fk( "address" ); + projList.add( property ); + criteria.setProjection( projList ); + + List results = criteria.list(); + assertThat( results.size(), is( 2 ) ); + } + ); + } + + @Test + public void selectAssociationIdWithCriteriaAlias() { + inTransaction( + session -> { + Criteria criteria = session.createCriteria( Person.class, "p" ); + criteria.createAlias( "address", "a" ); + + ProjectionList projList = Projections.projectionList(); + + projList.add( Projections.property( "address.id" ) ); + projList.add( Projections.property( "a.street" ) ); + criteria.setProjection( projList ); + + criteria.list(); + } + ); + + } + + @Test + public void selectAssociationIdWithSubCriteria() { + inTransaction( + session -> { + Criteria criteria = session.createCriteria( Person.class, "p" ); + Criteria addressCriteria = criteria.createCriteria( "address", "a" ); + addressCriteria.createAlias( "street", "s" ); + + ProjectionList projList = Projections.projectionList(); + + projList.add( Projections.property( "address.id" ) ); + projList.add( Projections.property( "a.street" ) ); + criteria.setProjection( projList ); + + criteria.list(); + } + ); + + } + + @Test + public void fkEqRestictionTest() { + inTransaction( + session -> { + Criteria criteria = session.createCriteria( Person.class, "p" ); + criteria.add( Restrictions.fkEq( "address", 100L ) ); + + List results = criteria.list(); + assertThat( results.size(), is( 1 ) ); + } + ); + } + + @Entity(name = "Person") + @Table(name = "PERSON_TABLE") + public static class Person { + @Id + Long id; + + String name; + + @Column(name = "DDID") + Long addressId; + + @ManyToOne(fetch = FetchType.LAZY) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn(name = "DDID", insertable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) + public Address address; + + public Person() { + } + + public Person(Long id, String name, Address address) { + this.id = id; + this.name = name; + this.addressId = address.getId(); + this.address = address; + } + } + + @Entity(name = "Address") + @Table(name = "ADDRESS_TABLE") + public static class Address { + @Id + @Column(name = "DDID") + private Long id; + + String address; + + @ManyToOne(fetch = FetchType.LAZY) + public Street street; + + public Address() { + } + + public Address(Long id, String address) { + this.id = id; + this.address = address; + } + + public Long getId() { + return id; + } + + public String getAddress() { + return address; + } + + public Street getStreet() { + return street; + } + } + + @Entity(name = "Street") + @Table(name = "TABLE_STREET") + public static class Street { + @Id + private Long id; + + String name; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyTest.java index df06757ac22cf192169610197b438c0ea6fde41e..0027981c70c4dbaf312bb0853b452d32b435b3b7 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyTest.java @@ -37,6 +37,8 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Table; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.annotations.Customer; @@ -359,6 +361,7 @@ public void testCascadeDelete() throws Exception { } @Test + @RequiresDialectFeature(DialectChecks.SupportsCascadeDeleteCheck.class) public void testCascadeDeleteWithUnidirectionalAssociation() throws Exception { OnDeleteUnidirectionalOneToManyChild child = new OnDeleteUnidirectionalOneToManyChild(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOneMapsIdQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOneMapsIdQueryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a74fab77292c04033119b3ffd13ba8d035c69542 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOneMapsIdQueryTest.java @@ -0,0 +1,297 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.onetoone; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@TestForIssue( jiraKey = "HHH-13875") +public class OptionalOneToOneMapsIdQueryTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testOneToOneWithIdNamedId() { + // Test with associated entity having ID named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithIdNamedId bar = new BarWithIdNamedId(); + bar.id = 1L; + bar.longValue = 2L; + FooHasBarWithIdNamedId foo = new FooHasBarWithIdNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.createQuery( + "from FooHasBarWithIdNamedId where bar.id = ?1", + FooHasBarWithIdNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.get( FooHasBarWithIdNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.createQuery( + "from FooHasBarWithIdNamedId where bar.id = ?1", + FooHasBarWithIdNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @Test + public void testOneToOneWithNoIdOrPropNamedId() { + // Test with associated entity having ID not named "id", and with no property named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithNoIdOrPropNamedId bar = new BarWithNoIdOrPropNamedId(); + bar.barId = 1L; + bar.longValue = 2L; + FooHasBarWithNoIdOrPropNamedId foo = new FooHasBarWithNoIdOrPropNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.barId = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // Querying by the generic "id" should work the same as "barId". + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.id = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.get( FooHasBarWithNoIdOrPropNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.barId = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + // Querying by the generic "id" should work the same as "barId". + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.id = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @Test + public void testOneToOneWithNonIdPropNamedId() { + // Test with associated entity having a non-ID property named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithNonIdPropNamedId bar = new BarWithNonIdPropNamedId(); + bar.barId = 1L; + bar.id = 2L; + FooHasBarWithNonIdPropNamedId foo = new FooHasBarWithNonIdPropNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.barId = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // bar.id is a non-ID property. + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 2L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // bar.id is a non-ID property. + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.get( FooHasBarWithNonIdPropNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.barId = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 2L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @After + public void cleanupData() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "delete from FooHasBarWithIdNamedId" ).executeUpdate(); + session.createQuery( "delete from FooHasBarWithNoIdOrPropNamedId" ).executeUpdate(); + session.createQuery( "delete from FooHasBarWithNonIdPropNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithIdNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithNoIdOrPropNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithNoIdOrPropNamedId" ).executeUpdate(); + }); + } + + @Override + protected Class[] getAnnotatedClasses() + { + return new Class[] { + FooHasBarWithIdNamedId.class, + BarWithIdNamedId.class, + FooHasBarWithNoIdOrPropNamedId.class, + BarWithNoIdOrPropNamedId.class, + FooHasBarWithNonIdPropNamedId.class, + BarWithNonIdPropNamedId.class + }; + } + + @Entity(name = "FooHasBarWithIdNamedId") + public static class FooHasBarWithIdNamedId + { + @Id + private Long id; + + @OneToOne(optional = true) + @MapsId + @JoinColumn(name = "id") + @NotFound(action = NotFoundAction.IGNORE) + private BarWithIdNamedId bar; + } + + @Entity(name = "BarWithIdNamedId") + public static class BarWithIdNamedId { + @Id + private long id; + private long longValue; + } + + @Entity(name = "FooHasBarWithNoIdOrPropNamedId") + @Table(name = "FooHasBarNoIdOrPropNamedId") + public static class FooHasBarWithNoIdOrPropNamedId + { + @Id + private Long id; + + @OneToOne(optional = true) + @MapsId + @JoinColumn(name = "id") + @NotFound(action = NotFoundAction.IGNORE) + private BarWithNoIdOrPropNamedId bar; + } + + @Entity(name = "BarWithNoIdOrPropNamedId") + public static class BarWithNoIdOrPropNamedId { + @Id + private long barId; + private long longValue; + } + + @Entity(name = "FooHasBarWithNonIdPropNamedId") + @Table(name = "FooHasBarNonIdPropNamedId") + public static class FooHasBarWithNonIdPropNamedId + { + @Id + private Long id; + + @OneToOne(optional = true) + @MapsId + @JoinColumn(name = "id") + @NotFound(action = NotFoundAction.IGNORE) + private BarWithNonIdPropNamedId bar; + } + + @Entity(name = "BarWithNonIdPropNamedId") + public static class BarWithNonIdPropNamedId { + @Id + private long barId; + private long id; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOnePKJCQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOnePKJCQueryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..caf7fef69c8b1098779ccc6a505cd999bda2ff01 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOnePKJCQueryTest.java @@ -0,0 +1,289 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.onetoone; + +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.Table; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@TestForIssue( jiraKey = "HHH-13875") +public class OptionalOneToOnePKJCQueryTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testOneToOneWithIdNamedId() { + // Test with associated entity having ID named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithIdNamedId bar = new BarWithIdNamedId(); + bar.id = 1L; + bar.longValue = 2L; + FooHasBarWithIdNamedId foo = new FooHasBarWithIdNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.createQuery( + "from FooHasBarWithIdNamedId where bar.id = ?1", + FooHasBarWithIdNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.get( FooHasBarWithIdNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.createQuery( + "from FooHasBarWithIdNamedId where bar.id = ?1", + FooHasBarWithIdNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @Test + public void testOneToOneWithNoIdOrPropNamedId() { + // Test with associated entity having ID not named "id", and with no property named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithNoIdOrPropNamedId bar = new BarWithNoIdOrPropNamedId(); + bar.barId = 1L; + bar.longValue = 2L; + FooHasBarWithNoIdOrPropNamedId foo = new FooHasBarWithNoIdOrPropNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.barId = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // Querying by the generic "id" should work the same as "barId". + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.id = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.get( FooHasBarWithNoIdOrPropNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.barId = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + // Querying by the generic "id" should work the same as "barId". + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.id = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @Test + public void testOneToOneWithNonIdPropNamedId() { + // Test with associated entity having a non-ID property named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithNonIdPropNamedId bar = new BarWithNonIdPropNamedId(); + bar.barId = 1L; + bar.id = 2L; + FooHasBarWithNonIdPropNamedId foo = new FooHasBarWithNonIdPropNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.barId = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // bar.id is a non-ID property. + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 2L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // bar.id is a non-ID property. + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.get( FooHasBarWithNonIdPropNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.barId = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 2L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @After + public void cleanupData() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "delete from FooHasBarWithIdNamedId" ).executeUpdate(); + session.createQuery( "delete from FooHasBarWithNoIdOrPropNamedId" ).executeUpdate(); + session.createQuery( "delete from FooHasBarWithNonIdPropNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithIdNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithNoIdOrPropNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithNoIdOrPropNamedId" ).executeUpdate(); + }); + } + + @Override + protected Class[] getAnnotatedClasses() + { + return new Class[] { + FooHasBarWithIdNamedId.class, + BarWithIdNamedId.class, + FooHasBarWithNoIdOrPropNamedId.class, + BarWithNoIdOrPropNamedId.class, + FooHasBarWithNonIdPropNamedId.class, + BarWithNonIdPropNamedId.class + }; + } + + @Entity(name = "FooHasBarWithIdNamedId") + public static class FooHasBarWithIdNamedId + { + @Id + private long id; + + @OneToOne(optional = true) + @PrimaryKeyJoinColumn(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) + private BarWithIdNamedId bar; + } + + @Entity(name = "BarWithIdNamedId") + public static class BarWithIdNamedId { + @Id + private long id; + private long longValue; + } + + @Entity(name = "FooHasBarWithNoIdOrPropNamedId") + @Table(name = "FooHasBarNoIdOrPropNamedId") + public static class FooHasBarWithNoIdOrPropNamedId + { + @Id + private long id; + + @OneToOne(optional = true) + @PrimaryKeyJoinColumn() + private BarWithNoIdOrPropNamedId bar; + } + + @Entity(name = "BarWithNoIdOrPropNamedId") + public static class BarWithNoIdOrPropNamedId { + @Id + private long barId; + private long longValue; + } + + @Entity(name = "FooHasBarWithNonIdPropNamedId") + @Table(name = "FooHasBarNonIdPropNamedId") + public static class FooHasBarWithNonIdPropNamedId + { + @Id + private long id; + + @OneToOne(optional = true) + @PrimaryKeyJoinColumn() + private BarWithNonIdPropNamedId bar; + } + + @Entity(name = "BarWithNonIdPropNamedId") + public static class BarWithNonIdPropNamedId { + @Id + private long barId; + private long id; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/override/inheritance/EntityInheritanceAttributeOverrideTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/override/inheritance/EntityInheritanceAttributeOverrideTest.java index e39188d0b73c575047f8a7db0524b9ae914464c7..7ed667757d8feb244df22fb3acfe7e0efc810d12 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/override/inheritance/EntityInheritanceAttributeOverrideTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/override/inheritance/EntityInheritanceAttributeOverrideTest.java @@ -6,36 +6,39 @@ */ package org.hibernate.test.annotations.override.inheritance; +import static org.junit.Assert.assertTrue; + import javax.persistence.AttributeOverride; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; -import javax.persistence.MappedSuperclass; import javax.persistence.Table; import javax.persistence.UniqueConstraint; -import org.hibernate.AnnotationException; +import org.hibernate.cfg.AnnotationBinder; +import org.hibernate.internal.CoreMessageLogger; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; - -import org.hibernate.testing.FailureExpected; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.jboss.logging.Logger; +import org.junit.Rule; import org.junit.Test; -import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - /** * @author Vlad Mihalcea */ -@TestForIssue( jiraKey = "HHH-12609, HHH-12654" ) +@TestForIssue( jiraKey = "HHH-12609, HHH-12654, HHH-13172" ) public class EntityInheritanceAttributeOverrideTest extends BaseEntityManagerFunctionalTestCase { + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, AnnotationBinder.class.getName() ) ); + @Override - public Class[] getAnnotatedClasses() { + public Class[] getAnnotatedClasses() { return new Class[]{ CategoryEntity.class, TaxonEntity.class, @@ -45,13 +48,11 @@ public Class[] getAnnotatedClasses() { @Override public void buildEntityManagerFactory() { - try { - super.buildEntityManagerFactory(); - fail("Should throw AnnotationException"); - } - catch (AnnotationException e) { - assertTrue( e.getMessage().startsWith( "An entity annotated with @Inheritance cannot use @AttributeOverride or @AttributeOverrides" ) ); - } + Triggerable warningLogged = logInspection.watchForLogMessages( "HHH000499:" ); + + super.buildEntityManagerFactory(); + + assertTrue("A warning should have been logged for this unsupported configuration", warningLogged.wasTriggered()); } @Test diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java index 4f6bdd811348131a3ca5f81fc8be0d967f0e978c..97d48e9c660c1dcf782581d7074e141a2716b58a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java @@ -31,8 +31,6 @@ import org.hibernate.dialect.function.SQLFunction; import org.hibernate.stat.Statistics; import org.hibernate.type.StandardBasicTypes; - -import org.hibernate.testing.FailureExpected; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -95,9 +93,17 @@ public void testNativeQueryWithFormulaAttribute() { } @Test - @FailureExpected(jiraKey = "HHH-2225") public void testNativeQueryWithFormulaAttributeWithoutAlias() { - String sql = "select TABLE_NAME , sysdate() from all_tables where TABLE_NAME = 'AUDIT_ACTIONS' "; + SQLFunction dateFunction = getDialect().getFunctions().get( "current_date" ); + String dateFunctionRendered = dateFunction.render( + null, + java.util.Collections.EMPTY_LIST, + sessionFactory() + ); + String sql = String.format( + "select TABLE_NAME , %s from ALL_TABLES where TABLE_NAME = 'AUDIT_ACTIONS' ", + dateFunctionRendered + ); Session s = openSession(); s.beginTransaction(); s.createSQLQuery( sql ).addEntity( "t", AllTables.class ).list(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAOverriddenAnnotationReaderTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAOverriddenAnnotationReaderTest.java index d7030ac9a95f9a6a19d3ee04e98b515a7c6886ed..c79a60e708d2389f48c8e2ea169e6afe9cf52455 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAOverriddenAnnotationReaderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAOverriddenAnnotationReaderTest.java @@ -393,7 +393,7 @@ public void testEntityListeners() throws Exception { } private XMLContext buildContext(String ormfile) throws SAXException, DocumentException, IOException { - XMLHelper xmlHelper = new XMLHelper( ClassLoaderServiceTestingImpl.INSTANCE ); + XMLHelper xmlHelper = new XMLHelper(); InputStream is = ClassLoaderServiceTestingImpl.INSTANCE.locateResourceStream( ormfile ); assertNotNull( "ORM.xml not found: " + ormfile, is ); XMLContext context = new XMLContext( BootstrapContextImpl.INSTANCE ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java index c79061002884dbd2a64eae8aebb68df6e0755fe1..235fe820e4a3bfc01def4920b5c2c70e6f86c478 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java @@ -22,7 +22,6 @@ import org.hibernate.internal.util.xml.XMLHelper; import org.hibernate.testing.boot.BootstrapContextImpl; -import org.hibernate.testing.boot.ClassLoaderAccessTestingImpl; import org.hibernate.testing.boot.ClassLoaderServiceTestingImpl; /** @@ -31,7 +30,7 @@ public class XMLContextTest { @Test public void testAll() throws Exception { - final XMLHelper xmlHelper = new XMLHelper( ClassLoaderServiceTestingImpl.INSTANCE ); + final XMLHelper xmlHelper = new XMLHelper(); final XMLContext context = new XMLContext( BootstrapContextImpl.INSTANCE ); InputStream is = ClassLoaderServiceTestingImpl.INSTANCE.locateResourceStream( diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/MyEntityInstantiator.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/MyEntityInstantiator.java index 3cab644e71be0e35ec18d377e6d95f0cc7e87b6b..483c84e12d125893b09caf9f4e955e3a32746612 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/MyEntityInstantiator.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/tuplizer/bytebuddysubclass/MyEntityInstantiator.java @@ -8,11 +8,11 @@ import java.io.Serializable; +import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; import org.hibernate.mapping.PersistentClass; import org.hibernate.tuple.Instantiator; import net.bytebuddy.ByteBuddy; -import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.implementation.FixedValue; import net.bytebuddy.matcher.ElementMatchers; @@ -42,7 +42,8 @@ public static E createInstance(Class entityClass) { .method( ElementMatchers.named( "toString" ) ) .intercept( FixedValue.value( "transformed" ) ) .make() - .load( entityClass.getClassLoader(), ClassLoadingStrategy.Default.INJECTION ) + // we use our internal helper to get a class loading strategy suitable for the JDK used + .load( entityClass.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( entityClass ) ) .getLoaded(); try { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/MySQLDropConstraintThrowsExceptionTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/MySQLDropConstraintThrowsExceptionTest.java index 6745f5d3338364a352bb85709fad67141924b8e9..34f4911f54b41fc1722b176e4ea5df68ffec0697 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/MySQLDropConstraintThrowsExceptionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/MySQLDropConstraintThrowsExceptionTest.java @@ -25,7 +25,9 @@ import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; @@ -40,6 +42,7 @@ */ @TestForIssue(jiraKey = "HHH-11236") @RequiresDialect(MySQL5Dialect.class) +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class MySQLDropConstraintThrowsExceptionTest extends BaseUnitTestCase { @Before diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTestCase.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTestCase.java index c41b93bd99e4ee130f0319152581486106429e89..3c96ba4df1cb6fcb548d53eab8152299c9e635a3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTestCase.java @@ -64,7 +64,11 @@ protected XMLContext getContext(String resourceName) throws Exception { protected XMLContext getContext(InputStream is) throws Exception { XMLContext xmlContext = new XMLContext( BootstrapContextImpl.INSTANCE ); - Document doc = new SAXReader().read( is ); + SAXReader reader = new SAXReader(); + reader.setFeature( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false ); + reader.setFeature( "http://xml.org/sax/features/external-general-entities", false ); + reader.setFeature( "http://xml.org/sax/features/external-parameter-entities", false ); + Document doc = reader.read( is ); xmlContext.addDocument( doc ); return xmlContext; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/A.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/A.java index e3558de12055970537ae1439f90d85e02002e2b0..fcf80d24429f3a36dbca24bb5630e8d1f8865e9e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/A.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/A.java @@ -13,4 +13,8 @@ public interface A extends java.io.Serializable { public Integer getAId(); public void setAId(Integer aId); + + String getDescription(); + + void setDescription(String description); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/AImpl.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/AImpl.java index 34bab4b1dda5ff2b9236033bb1fd7ae78d2c4f12..3b13900d80b3837036daea88c7444ee0e9eaffb7 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/AImpl.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/hbm/AImpl.java @@ -23,6 +23,7 @@ public class AImpl implements A { private static final long serialVersionUID = 1L; private Integer aId = 0; + private String description; public AImpl() { } @@ -37,4 +38,13 @@ public Integer getAId() { public void setAId(Integer aId) { this.aId = aId; } + + @Column( name = "description" ) + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchReferencedColumnNameTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchReferencedColumnNameTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c161191188fe284399bbe60e34f84786ca2a2da6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchReferencedColumnNameTest.java @@ -0,0 +1,146 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.batchfetch; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +import java.time.ZonedDateTime; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Table; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Assert; +import org.junit.Test; + +public class BatchFetchReferencedColumnNameTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ Child.class, Parent.class }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + + configuration.setProperty( AvailableSettings.SHOW_SQL, Boolean.TRUE.toString() ); + configuration.setProperty( AvailableSettings.FORMAT_SQL, Boolean.TRUE.toString() ); + + configuration.setProperty( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "64" ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13059") + public void test() throws Exception { + doInHibernate( this::sessionFactory, session -> { + Parent p = new Parent(); + p.setId( 1L ); + session.save( p ); + + Child c1 = new Child(); + c1.setCreatedOn( ZonedDateTime.now() ); + c1.setParentId( 1L ); + c1.setId( 10L ); + session.save( c1 ); + + Child c2 = new Child(); + c2.setCreatedOn( ZonedDateTime.now() ); + c2.setParentId( 1L ); + c2.setId( 11L ); + session.save( c2 ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Parent p = session.get( Parent.class, 1L ); + Assert.assertNotNull( p ); + + Assert.assertEquals( 2, p.getChildren().size() ); + } ); + } + + @Entity + @Table(name = "CHILD") + public static class Child { + + @Id + @Column(name = "CHILD_ID") + private Long id; + + @Column(name = "PARENT_ID") + private Long parentId; + + @Column(name = "CREATED_ON") + private ZonedDateTime createdOn; + + public ZonedDateTime getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(ZonedDateTime createdOn) { + this.createdOn = createdOn; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getParentId() { + return parentId; + } + + public void setParentId(Long parentId) { + this.parentId = parentId; + } + } + + @Entity + @Table(name = "PARENT") + public static class Parent { + + @Id + @Column(name = "PARENT_ID") + private Long id; + + @OneToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL }) + @JoinColumn(name = "PARENT_ID", referencedColumnName = "PARENT_ID") + @OrderBy("createdOn desc") + private List children; + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchRefreshTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchRefreshTest.java new file mode 100644 index 0000000000000000000000000000000000000000..12c0d79bc0f49410d6143cd71dc1e3cc7bdf5649 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchRefreshTest.java @@ -0,0 +1,181 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.batchfetch; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.LockModeType; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.*; + +public class BatchFetchRefreshTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + + public void testRefreshWithBatch() { + + doInHibernate( this::sessionFactory, session -> { + + // Retrieve one of the parents into the session. + Parent parent = session.find(Parent.class, 1); + Assert.assertNotNull(parent); + + // Retrieve children but keep their parents lazy! + // This allows batch fetching to do its thing when we refresh below. + session.createQuery( "FROM Child" ).getResultList(); + + session.refresh( parent, LockModeType.PESSIMISTIC_WRITE ); + + // Just something to force delazification of children on parent entity + // The parent is obviously attached to the session (we just refreshed it!) + parent.getChildren().size(); + + // Another interesting thing to note - em.getLockMode returns an incorrect value after the above refresh + Assert.assertEquals( LockModeType.PESSIMISTIC_WRITE, session.getLockMode( parent ) ); + }); + } + + @Before + public void setupData() { + final int numParents = 5; + final int childrenPerParent = 2; + + doInHibernate( this::sessionFactory, session -> { + int k = 1; + for ( int i = 1; i <= numParents; i++ ) { + Parent parent = new Parent(); + parent.parentId = i; + parent.name = "Parent_" + i; + + session.persist( parent ); + + // Create some children for each parent... + for ( int j = 0; j < childrenPerParent; j++,k++ ) { + Child child = new Child(); + child.childId = k; + child.name = "Child_" + i + "_" + j; + child.age = 15; + child.parent = parent; + parent.getChildren().add( child ); + session.persist( child ); + } + } + }); + + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Parent.class, + Child.class + }; + } + + @Override + protected void addSettings(Map settings) { + super.addSettings( settings ); + settings.put( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "8" ); + } + + @Entity(name = "Parent") + public static class Parent { + + @Id + @Column(name = "parent_id") + private int parentId; + + @Column(name = "name") + private String name; + + @OneToMany(mappedBy = "parent") + private Set children = new LinkedHashSet<>(); + + public int getParentId() { + return parentId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getChildren() { + return children; + } + + public void setChildren(Set children) { + this.children = children; + } + + } + + @Entity(name = "Child") + public static class Child { + + @Id + @Column(name = "child_id") + private int childId; + + @Column(name = "name") + private String name; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "parent_id") + private Parent parent; + + @Column(name = "age") + private int age; + + public int getChildId() { + return childId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchingEntityLoaderInitializationWithNoLockModeTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchingEntityLoaderInitializationWithNoLockModeTest.java new file mode 100644 index 0000000000000000000000000000000000000000..461e57c7a8315a3370784adde088031248a155b3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchingEntityLoaderInitializationWithNoLockModeTest.java @@ -0,0 +1,114 @@ +package org.hibernate.test.batchfetch; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertNotNull; + +import java.util.Map; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.annotations.Fetch; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.loader.BatchFetchStyle; +import org.hibernate.metamodel.spi.MetamodelImplementor; +import org.hibernate.persister.entity.EntityPersister; +import org.junit.Test; + +public class BatchingEntityLoaderInitializationWithNoLockModeTest extends BaseEntityManagerFunctionalTestCase { + + private Long mainId; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { MainEntity.class, SubEntity.class }; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + protected Map buildSettings() { + Map settings = super.buildSettings(); + settings.put( AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.LEGACY ); + settings.put( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, 5 ); + return settings; + } + + @Test + public void testJoin() { + doInJPA( this::entityManagerFactory, em -> { + SubEntity sub = new SubEntity(); + em.persist( sub ); + + MainEntity main = new MainEntity(); + main.setSub( sub ); + em.persist( main ); + + this.mainId = main.getId(); + }); + + doInJPA( this::entityManagerFactory, em -> { + EntityPersister entityPersister = ( (MetamodelImplementor) em.getMetamodel() ) + .entityPersister( MainEntity.class ); + + // use some specific lock options to trigger the creation of a loader with lock options + LockOptions lockOptions = new LockOptions( LockMode.NONE ); + lockOptions.setTimeOut( 10 ); + + MainEntity main = (MainEntity) entityPersister.load( this.mainId, null, lockOptions, + (SharedSessionContractImplementor) em ); + assertNotNull( main.getSub() ); + } ); + } + + @Entity(name = "MainEntity") + public static class MainEntity { + + @Id + @GeneratedValue + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @Fetch(org.hibernate.annotations.FetchMode.JOIN) + private SubEntity sub; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public SubEntity getSub() { + return sub; + } + + public void setSub(SubEntity sub) { + this.sub = sub; + } + } + + @Entity(name = "SubEntity") + public static class SubEntity { + + @Id + @GeneratedValue + private Long id; + + public Long getId() { + return id; + } + + + public void setId(Long id) { + this.id = id; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/City.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/City.java new file mode 100644 index 0000000000000000000000000000000000000000..83a800a2670656314c5e88c1057073886ed59790 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/City.java @@ -0,0 +1,72 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.batchfetch; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "city") +public class City { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private Integer id; + + @Column(name = "name") + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "country_id") + private Country country; + + public City() { + } + + public City(String name, Country country) { + super(); + this.name = name; + this.country = country; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Country getCountry() { + return country; + } + + public void setCountry(Country country) { + this.country = country; + } + + @Override + public String toString() { + return name + " (" + ( country == null ? "?" : country.getName() ) + ")"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/Country.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/Country.java new file mode 100644 index 0000000000000000000000000000000000000000..76e40d62ca6bc905c44890e3eecbb7dc8f199cf4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/Country.java @@ -0,0 +1,66 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.batchfetch; + +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +@Entity +@Table(name = "country") +public class Country { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private Integer id; + + @Column(name = "name") + private String name; + + @OneToMany(mappedBy = "country") + private List cities; + + public Country() { + } + + public Country(String name) { + super(); + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getCities() { + return cities; + } + + @Override + public String toString() { + return name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/PaddedBatchFetchTestCase.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/PaddedBatchFetchTestCase.java new file mode 100644 index 0000000000000000000000000000000000000000..08c5f471b0cf7be0f5571446ce2df02b14dc4c2c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/PaddedBatchFetchTestCase.java @@ -0,0 +1,64 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.batchfetch; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; + +import java.util.List; +import java.util.stream.IntStream; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.loader.BatchFetchStyle; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +public class PaddedBatchFetchTestCase extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ Country.class, City.class }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + + configuration.setProperty( AvailableSettings.SHOW_SQL, Boolean.TRUE.toString() ); + configuration.setProperty( AvailableSettings.FORMAT_SQL, Boolean.TRUE.toString() ); + + configuration.setProperty( AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.PADDED.name() ); + configuration.setProperty( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "15" ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12835") + public void paddedBatchFetchTest() throws Exception { + doInHibernate( this::sessionFactory, session -> { + // Having DEFAULT_BATCH_FETCH_SIZE=15 + // results in batchSizes = [15, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + // Let's create 11 countries so batch size 15 will be used with padded values, + // this causes to have to remove 4 elements from list + int numberOfCountries = 11; + + IntStream.range( 0, numberOfCountries ).forEach( i -> { + Country c = new Country( "Country " + i ); + session.save( c ); + session.save( new City( "City " + i, c ) ); + } ); + } ); + + doInHibernate( this::sessionFactory, session -> { + List allCities = session.createQuery( "from City", City.class ).list(); + + // this triggers countries to be fetched in batch + assertNotNull( allCities.get( 0 ).getCountry().getName() ); + } ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java index 55fd7babe6472ce73ee07a4235b5e6812c8b123d..a21b27896239be9993cc8a6157f54bc80abbdd6f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java @@ -127,6 +127,10 @@ public static class Person implements Serializable { private boolean employed; + //Getters and setters are omitted for brevity + + //end::batch-bulk-hql-temp-table-base-class-example[] + public Integer getId() { return id; } @@ -176,6 +180,7 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( getId(), getCompanyName() ); } + //tag::batch-bulk-hql-temp-table-base-class-example[] } //end::batch-bulk-hql-temp-table-base-class-example[] @@ -189,6 +194,10 @@ public static class Engineer extends Person { private boolean fellow; + //Getters and setters are omitted for brevity + + //end::batch-bulk-hql-temp-table-sub-classes-example[] + public boolean isFellow() { return fellow; } @@ -196,6 +205,7 @@ public boolean isFellow() { public void setFellow(boolean fellow) { this.fellow = fellow; } + //tag::batch-bulk-hql-temp-table-sub-classes-example[] } //end::batch-bulk-hql-temp-table-sub-classes-example[] } \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/ReflectionOptimizerTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/ReflectionOptimizerTest.java index 2df2a00dbbe3559c27c27e50004a9a1dced99ad2..708287de90d57561fd4499ba5fac1dad5faa7895 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/ReflectionOptimizerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/ReflectionOptimizerTest.java @@ -9,7 +9,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import org.hibernate.bytecode.internal.javassist.BulkAccessor; import org.hibernate.bytecode.spi.BytecodeProvider; import org.hibernate.bytecode.spi.ReflectionOptimizer; import org.hibernate.cfg.Environment; @@ -21,15 +20,6 @@ * @author Steve Ebersole */ public class ReflectionOptimizerTest extends BaseUnitTestCase { - @Test - public void testBulkAccessorDirectly() { - BulkAccessor bulkAccessor = BulkAccessor.create( - Bean.class, - BeanReflectionHelper.getGetterNames(), - BeanReflectionHelper.getSetterNames(), - BeanReflectionHelper.getTypes() - ); - } @Test public void testReflectionOptimization() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/ManyToManyAssociationListTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/ManyToManyAssociationListTest.java new file mode 100644 index 0000000000000000000000000000000000000000..defbd0bfbd9ab2a275199df7e083d41b9c347fc3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/ManyToManyAssociationListTest.java @@ -0,0 +1,94 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.association; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToMany; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * @author Luis Barreiro + */ +@RunWith( BytecodeEnhancerRunner.class ) +public class ManyToManyAssociationListTest { + @Test + public void testBidirectionalExisting() { + Group group = new Group(); + Group anotherGroup = new Group(); + + User user = new User(); + anotherGroup.users.add( user ); + + user.setGroups( new ArrayList<>( Collections.singleton( group ) ) ); + user.setGroups( new ArrayList<>( Arrays.asList( group, anotherGroup ) ) ); + + Assert.assertEquals( 1, group.getUsers().size() ); + Assert.assertEquals( 1, anotherGroup.getUsers().size() ); + } + + // -- // + + @Entity + private static class Group { + + @Id + Long id; + + @Column + String name; + + @ManyToMany( mappedBy = "groups" ) + List users = new ArrayList<>(); + + List getUsers() { + return Collections.unmodifiableList( users ); + } + + void resetUsers() { + // this wouldn't trigger association management: users.clear(); + users = new ArrayList<>(); + } + } + + @Entity + private static class User { + + @Id + Long id; + + String password; + + @ManyToMany + List groups; + + void addGroup(Group group) { + List groups = this.groups == null ? new ArrayList<>() : this.groups; + groups.add( group ); + this.groups = groups; + } + + List getGroups() { + return Collections.unmodifiableList( groups ); + } + + void setGroups(List groups) { + this.groups = groups; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToOneAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToOneAssociationTest.java index d40f8591cadfb7f6238e34c51d56f12a90eb6d6c..6358ecae991f1e94cd8af3e70dc6cfe237664082 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToOneAssociationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/association/OneToOneAssociationTest.java @@ -49,6 +49,25 @@ public void test() { Assert.assertEquals( user, user.getCustomer().getUser() ); } + @Test + public void testSetNull() { + User user = new User(); + user.setLogin( UUID.randomUUID().toString() ); + + Customer customer = new Customer(); + customer.setUser( user ); + + Assert.assertEquals( customer, user.getCustomer() ); + + // check dirty tracking is set automatically with bi-directional association management + EnhancerTestUtils.checkDirtyTracking( user, "login", "customer" ); + + user.setCustomer( null ); + + Assert.assertNull( user.getCustomer() ); + Assert.assertNull( customer.getUser() ); + } + // --- // @Entity diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTest.java index edae6baf7835636f8cb360c17e99bfa7fdd4532e..fbe4ef03afea5906356cb084c607c01c4288be35 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/basic/BasicEnhancementTest.java @@ -209,15 +209,6 @@ public Object readObject(Object obj, String name, Object oldValue) { public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { return WRITE_MARKER; } - - @Override - public Set getInitializedLazyAttributeNames() { - return null; - } - - @Override - public void attributeInitialized(String name) { - } } // --- // diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteCollectionTest.java similarity index 47% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteTest.java rename to hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteCollectionTest.java index 7578d18ec08daa1033b86d05538a1f39c4a05ad9..1f8860e41ca87121f5b1e2738a5830a11a5e74bf 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteCollectionTest.java @@ -28,14 +28,22 @@ import java.util.Collections; import java.util.List; +import org.hibernate.Hibernate; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; + import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; /** * @author Luis Barreiro */ @TestForIssue( jiraKey = "HHH-10252" ) @RunWith( BytecodeEnhancerRunner.class ) -public class CascadeDeleteTest extends BaseCoreFunctionalTestCase { +public class CascadeDeleteCollectionTest extends BaseCoreFunctionalTestCase { + private Parent originalParent; @Override protected Class[] getAnnotatedClasses() { @@ -45,29 +53,121 @@ protected Class[] getAnnotatedClasses() { @Before public void prepare() { // Create a Parent with one Child - doInHibernate( this::sessionFactory, s -> { + originalParent = doInHibernate( this::sessionFactory, s -> { Parent p = new Parent(); p.setName( "PARENT" ); p.setLazy( "LAZY" ); p.makeChild(); s.persist( p ); + return p; } ); } @Test - public void test() { + public void testManagedWithUninitializedAssociation() { // Delete the Parent doInHibernate( this::sessionFactory, s -> { Parent loadedParent = (Parent) s.createQuery( "SELECT p FROM Parent p WHERE name=:name" ) .setParameter( "name", "PARENT" ) .uniqueResult(); + checkInterceptor( loadedParent, false ); + assertFalse( Hibernate.isPropertyInitialized( loadedParent, "children" ) ); + s.delete( loadedParent ); + } ); + // If the lazy relation is not fetch on cascade there is a constraint violation on commit + } + @Test + @TestForIssue(jiraKey = "HHH-13129") + public void testManagedWithInitializedAssociation() { + // Delete the Parent + doInHibernate( this::sessionFactory, s -> { + Parent loadedParent = (Parent) s.createQuery( "SELECT p FROM Parent p WHERE name=:name" ) + .setParameter( "name", "PARENT" ) + .uniqueResult(); + checkInterceptor( loadedParent, false ); + loadedParent.getChildren(); + assertTrue( Hibernate.isPropertyInitialized( loadedParent, "children" ) ); s.delete( loadedParent ); } ); // If the lazy relation is not fetch on cascade there is a constraint violation on commit } + @Test + @TestForIssue(jiraKey = "HHH-13129") + public void testDetachedWithUninitializedAssociation() { + final Parent detachedParent = doInHibernate( this::sessionFactory, s -> { + return s.get( Parent.class, originalParent.getId() ); + } ); + + assertFalse( Hibernate.isPropertyInitialized( detachedParent, "children" ) ); + + checkInterceptor( detachedParent, false ); + + // Delete the detached Parent with uninitialized children + doInHibernate( this::sessionFactory, s -> { + s.delete( detachedParent ); + } ); + // If the lazy relation is not fetch on cascade there is a constraint violation on commit + } + + @Test + @TestForIssue(jiraKey = "HHH-13129") + public void testDetachedWithInitializedAssociation() { + final Parent detachedParent = doInHibernate( this::sessionFactory, s -> { + Parent parent = s.get( Parent.class, originalParent.getId() ); + assertFalse( Hibernate.isPropertyInitialized( parent, "children" ) ); + + // initialize collection before detaching + parent.getChildren(); + return parent; + } ); + + assertTrue( Hibernate.isPropertyInitialized( detachedParent, "children" ) ); + + checkInterceptor( detachedParent, false ); + + // Delete the detached Parent with initialized children + doInHibernate( this::sessionFactory, s -> { + s.delete( detachedParent ); + } ); + // If the lazy relation is not fetch on cascade there is a constraint violation on commit + } + + @Test + @TestForIssue(jiraKey = "HHH-13129") + public void testDetachedOriginal() { + + // originalParent#children should be initialized + assertTrue( Hibernate.isPropertyInitialized( originalParent, "children" ) ); + + checkInterceptor( originalParent, true ); + + // Delete the Parent + doInHibernate( this::sessionFactory, s -> { + s.delete( originalParent ); + } ); + // If the lazy relation is not fetch on cascade there is a constraint violation on commit + } + + private void checkInterceptor(Parent parent, boolean isNullExpected) { + final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = + sessionFactory() + .getMetamodel() + .entityPersister( Parent.class ) + .getEntityMetamodel() + .getBytecodeEnhancementMetadata(); + if ( isNullExpected ) { + // if a null Interceptor is expected, then there shouldn't be any uninitialized attributes + assertFalse( bytecodeEnhancementMetadata.hasUnFetchedAttributes( parent ) ); + assertNull( bytecodeEnhancementMetadata.extractInterceptor( parent ) ); + } + else { + assertNotNull( bytecodeEnhancementMetadata.extractInterceptor( parent ) ); + } + } + // --- // @Entity( name = "Parent" ) diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteManyToOneTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5ba8d3a6016f686b3cec8add1914c50f6f6ae0b2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeDeleteManyToOneTest.java @@ -0,0 +1,288 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.cascade; + +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Luis Barreiro + */ +@TestForIssue(jiraKey = "HHH-10252") +@RunWith(BytecodeEnhancerRunner.class) +public class CascadeDeleteManyToOneTest extends BaseCoreFunctionalTestCase { + private Child originalChild; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, Child.class }; + } + + @Before + public void prepare() { + // Create a Parent with one Child + originalChild = doInHibernate( + this::sessionFactory, s -> { + Child c = new Child(); + c.setName( "CHILD" ); + c.setLazy( "LAZY" ); + c.makeParent(); + s.persist( c ); + return c; + } + ); + } + + @Test + public void testManagedWithUninitializedAssociation() { + // Delete the Child + doInHibernate( + this::sessionFactory, s -> { + Child loadedChild = (Child) s.createQuery( "SELECT c FROM Child c WHERE name=:name" ) + .setParameter( "name", "CHILD" ) + .uniqueResult(); + checkInterceptor( loadedChild, false ); + assertFalse( Hibernate.isPropertyInitialized( loadedChild, "parent" ) ); + s.delete( loadedChild ); + } + ); + // Explicitly check that both got deleted + doInHibernate( + this::sessionFactory, s -> { + assertNull( s.createQuery( "FROM Child c" ).uniqueResult() ); + assertNull( s.createQuery( "FROM Parent p" ).uniqueResult() ); + } + ); + } + + @Test + public void testManagedWithInitializedAssociation() { + // Delete the Child + doInHibernate( + this::sessionFactory, s -> { + Child loadedChild = (Child) s.createQuery( "SELECT c FROM Child c WHERE name=:name" ) + .setParameter( "name", "CHILD" ) + .uniqueResult(); + checkInterceptor( loadedChild, false ); + loadedChild.getParent(); + assertTrue( Hibernate.isPropertyInitialized( loadedChild, "parent" ) ); + s.delete( loadedChild ); + } + ); + // Explicitly check that both got deleted + doInHibernate( + this::sessionFactory, s -> { + assertNull( s.createQuery( "FROM Child c" ).uniqueResult() ); + assertNull( s.createQuery( "FROM Parent p" ).uniqueResult() ); + } + ); + } + + @Test + public void testDetachedWithUninitializedAssociation() { + final Child detachedChild = doInHibernate( + this::sessionFactory, s -> { + return s.get( Child.class, originalChild.getId() ); + } + ); + + assertFalse( Hibernate.isPropertyInitialized( detachedChild, "parent" ) ); + + checkInterceptor( detachedChild, false ); + + // Delete the detached Child with uninitialized parent + doInHibernate( + this::sessionFactory, s -> { + s.delete( detachedChild ); + } + ); + // Explicitly check that both got deleted + doInHibernate( + this::sessionFactory, s -> { + assertNull( s.createQuery( "FROM Child c" ).uniqueResult() ); + assertNull( s.createQuery( "FROM Parent p" ).uniqueResult() ); + } + ); + } + + @Test + public void testDetachedWithInitializedAssociation() { + final Child detachedChild = doInHibernate( + this::sessionFactory, s -> { + Child child = s.get( Child.class, originalChild.getId() ); + assertFalse( Hibernate.isPropertyInitialized( child, "parent" ) ); + + // initialize parent before detaching + child.getParent(); + return child; + } + ); + + assertTrue( Hibernate.isPropertyInitialized( detachedChild, "parent" ) ); + + checkInterceptor( detachedChild, false ); + + // Delete the detached Child with initialized parent + doInHibernate( + this::sessionFactory, s -> { + s.delete( detachedChild ); + } + ); + // Explicitly check that both got deleted + doInHibernate( + this::sessionFactory, s -> { + assertNull( s.createQuery( "FROM Child c" ).uniqueResult() ); + assertNull( s.createQuery( "FROM Parent p" ).uniqueResult() ); + } + ); + } + + @Test + public void testDetachedOriginal() { + + // originalChild#parent should be initialized + assertTrue( Hibernate.isPropertyInitialized( originalChild, "parent" ) ); + + checkInterceptor( originalChild, true ); + + // Delete the Child + doInHibernate( + this::sessionFactory, s -> { + s.delete( originalChild ); + } + ); + // Explicitly check that both got deleted + doInHibernate( + this::sessionFactory, s -> { + assertNull( s.createQuery( "FROM Child c" ).uniqueResult() ); + assertNull( s.createQuery( "FROM Parent p" ).uniqueResult() ); + } + ); + } + + private void checkInterceptor(Child child, boolean isNullExpected) { + final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = + sessionFactory() + .getMetamodel() + .entityPersister( Child.class ) + .getEntityMetamodel() + .getBytecodeEnhancementMetadata(); + if ( isNullExpected ) { + // if a null Interceptor is expected, then there shouldn't be any uninitialized attributes + assertFalse( bytecodeEnhancementMetadata.hasUnFetchedAttributes( child ) ); + assertNull( bytecodeEnhancementMetadata.extractInterceptor( child ) ); + } + else { + assertNotNull( bytecodeEnhancementMetadata.extractInterceptor( child ) ); + } + } + + // --- // + + @Entity(name = "Parent") + @Table(name = "PARENT") + public static class Parent { + + Long id; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + } + + @Entity(name = "Child") + @Table(name = "CHILD") + private static class Child { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + String name; + + @ManyToOne(optional = false, cascade = { + CascadeType.PERSIST, + CascadeType.MERGE, + CascadeType.REMOVE + }, fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + @LazyToOne(LazyToOneOption.NO_PROXY) + Parent parent; + + @Basic(fetch = FetchType.LAZY) + String lazy; + + Long getId() { + return id; + } + + void setId(Long id) { + this.id = id; + } + + String getName() { + return name; + } + + void setName(String name) { + this.name = name; + } + + Parent getParent() { + return parent; + } + + void setParent(Parent parent) { + this.parent = parent; + } + + String getLazy() { + return lazy; + } + + void setLazy(String lazy) { + this.lazy = lazy; + } + + void makeParent() { + parent = new Parent(); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeOnUninitializedTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeOnUninitializedTest.java new file mode 100644 index 0000000000000000000000000000000000000000..39a2637f5096e443bdae0cba812c1f5991a70810 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/cascade/CascadeOnUninitializedTest.java @@ -0,0 +1,286 @@ +package org.hibernate.test.bytecode.enhancement.cascade; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Bolek Ziobrowski + * @author Gail Badner + */ +@RunWith(BytecodeEnhancerRunner.class) +@TestForIssue(jiraKey = "HHH-13129") +public class CascadeOnUninitializedTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Address.class, + }; + } + + @Override + protected void addSettings(Map settings) { + super.addSettings( settings ); + settings.put( AvailableSettings.SHOW_SQL, "true" ); + settings.put( AvailableSettings.FORMAT_SQL, "true" ); + } + + @Test + public void testMergeDetachedEnhancedEntityWithUninitializedManyToOne() { + + Person person = persistPersonWithManyToOne(); + + // get a detached Person + Person detachedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return session.get( Person.class, person.getId() ); + } + ); + + // address should not be initialized + assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "primaryAddress" ) ); + detachedPerson.setName( "newName" ); + + Person mergedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return (Person) session.merge( detachedPerson ); + } + ); + + // address should not be initialized + assertFalse( Hibernate.isPropertyInitialized( mergedPerson, "primaryAddress" ) ); + assertEquals( "newName", mergedPerson.getName() ); + } + + @Test + public void testDeleteEnhancedEntityWithUninitializedManyToOne() { + Person person = persistPersonWithManyToOne(); + + // get a detached Person + Person detachedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return session.get( Person.class, person.getId() ); + } + ); + + // address should not be initialized + assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "primaryAddress" ) ); + + // deleting detachedPerson should result in detachedPerson.address being initialized, + // so that the DELETE operation can be cascaded to it. + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + session.delete( detachedPerson ); + } + ); + + // both the Person and its Address should be deleted + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + assertNull( session.get( Person.class, person.getId() ) ); + assertNull( session.get( Person.class, person.getPrimaryAddress().getId() ) ); + } + ); + } + + @Test + public void testMergeDetachedEnhancedEntityWithUninitializedOneToMany() { + + Person person = persistPersonWithOneToMany(); + + // get a detached Person + Person detachedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return session.get( Person.class, person.getId() ); + } + ); + + // address should not be initialized + assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "addresses" ) ); + detachedPerson.setName( "newName" ); + + Person mergedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return (Person) session.merge( detachedPerson ); + } + ); + + // address should be initialized + assertTrue( Hibernate.isPropertyInitialized( mergedPerson, "addresses" ) ); + assertEquals( "newName", mergedPerson.getName() ); + } + + @Test + public void testDeleteEnhancedEntityWithUninitializedOneToMany() { + Person person = persistPersonWithOneToMany(); + + // get a detached Person + Person detachedPerson = TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + return session.get( Person.class, person.getId() ); + } + ); + + // address should not be initialized + assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "addresses" ) ); + + // deleting detachedPerson should result in detachedPerson.address being initialized, + // so that the DELETE operation can be cascaded to it. + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + session.delete( detachedPerson ); + } + ); + + // both the Person and its Address should be deleted + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + assertNull( session.get( Person.class, person.getId() ) ); + assertNull( session.get( Person.class, person.getAddresses().iterator().next().getId() ) ); + } + ); + } + + public Person persistPersonWithManyToOne() { + Address address = new Address(); + address.setDescription( "ABC" ); + + final Person person = new Person(); + person.setName( "John Doe" ); + person.setPrimaryAddress( address ); + + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + session.persist( person ); + } + ); + + return person; + } + + public Person persistPersonWithOneToMany() { + Address address = new Address(); + address.setDescription( "ABC" ); + + final Person person = new Person(); + person.setName( "John Doe" ); + person.getAddresses().add( address ); + + TransactionUtil.doInHibernate( + this::sessionFactory, session -> { + session.persist( person ); + } + ); + + return person; + } + + @Entity + @Table(name = "TEST_PERSON") + public static class Person { + @Id + @GeneratedValue + private Long id; + + @Column(name = "NAME", length = 300, nullable = true) + private String name; + + @ManyToOne(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY) + @JoinColumn(name = "ADDRESS_ID") + @LazyToOne(LazyToOneOption.NO_PROXY) + private Address primaryAddress; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn + private Set
        addresses = new HashSet<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Address getPrimaryAddress() { + return primaryAddress; + } + + public void setPrimaryAddress(Address primaryAddress) { + this.primaryAddress = primaryAddress; + } + + public Set
        getAddresses() { + return addresses; + } + + public void setAddresses(Set
        addresses) { + this.addresses = addresses; + } + } + + @Entity + @Table(name = "TEST_ADDRESS") + public static class Address { + @Id + @GeneratedValue + private Long id; + + @Column(name = "DESCRIPTION", length = 300, nullable = true) + private String description; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + } +} + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5a636b08aeb71f0fc99389511a3f85f987d941be --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/BidirectionalLazyTest.java @@ -0,0 +1,424 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +import org.hibernate.bytecode.enhance.spi.UnloadedField; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.SessionImplementor; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * Tests removing non-owning side of the bidirectional association, + * with and without dirty-checking using enhancement. + * + * @author Gail Badner + */ +@TestForIssue(jiraKey = "HHH-13241") +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext({ + EnhancerTestContext.class, // supports laziness and dirty-checking + BidirectionalLazyTest.NoDirtyCheckEnhancementContext.class // supports laziness; does not support dirty-checking +}) +public class BidirectionalLazyTest extends BaseCoreFunctionalTestCase { + + public Class[] getAnnotatedClasses() { + return new Class[] { Employer.class, Employee.class, Unrelated.class }; + } + + @Test + public void testRemoveWithDeletedAssociatedEntity() { + + doInHibernate( + this::sessionFactory, session -> { + + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + employer.addEmployee( new Employee( "Jack" ) ); + employer.addEmployee( new Employee( "Jill" ) ); + employer.addEmployee( new Employee( "John" ) ); + for ( Employee employee : employer.getEmployees() ) { + session.persist( employee ); + } + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = session.get( Employer.class, "RedHat" ); + + // Delete the associated entity first + session.remove( employer ); + + for ( Employee employee : employer.getEmployees() ) { + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + // Should be initialized because at least one entity was deleted beforehand + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + assertSame( employer, employee.getEmployer() ); + // employee.getEmployer was initialized, and should be nullified in EntityEntry#deletedState + checkEntityEntryState( session, employee, employer, true ); + } + } + ); + + doInHibernate( + this::sessionFactory, session -> { + assertNull( session.find( Employer.class, "RedHat" ) ); + assertTrue( session.createQuery( "from Employee e", Employee.class ).getResultList().isEmpty() ); + } + ); + } + + @Test + public void testRemoveWithNonAssociatedRemovedEntity() { + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + Employee employee = new Employee( "Jack" ); + employer.addEmployee( employee ); + session.persist( employee ); + session.persist( new Unrelated( 1 ) ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + // Delete an entity that is not associated with Employee + session.remove( session.get( Unrelated.class, 1 ) ); + final Employee employee = session.get( Employee.class, "Jack" ); + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + // Should be initialized because at least one entity was deleted beforehand + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + // employee.getEmployer was initialized, and should not be nullified in EntityEntry#deletedState + checkEntityEntryState( session, employee, employee.getEmployer(), false ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + assertNull( session.find( Unrelated.class, 1 ) ); + assertNull( session.find( Employee.class, "Jack" ) ); + session.remove( session.find( Employer.class, "RedHat" ) ); + } + ); + } + + @Test + public void testRemoveWithNoRemovedEntities() { + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + Employee employee = new Employee( "Jack" ); + employer.addEmployee( employee ); + session.persist( employee ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + // Don't delete any entities before deleting the Employee + final Employee employee = session.get( Employee.class, "Jack" ); + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + // There were no other deleted entities before employee was deleted, + // so there was no need to initialize employee.employer. + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + // employee.getEmployer was not initialized, and should not be nullified in EntityEntry#deletedState + checkEntityEntryState( session, employee, LazyPropertyInitializer.UNFETCHED_PROPERTY, false ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + assertNull( session.find( Employee.class, "Jack" ) ); + session.remove( session.find( Employer.class, "RedHat" ) ); + } + ); + } + + @Test + public void testRemoveEntityWithNullLazyManyToOne() { + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + Employee employee = new Employee( "Jack" ); + session.persist( employee ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Employee employee = session.get( Employee.class, "Jack" ); + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + + // Get and delete an Employer that is not associated with employee + Employer employer = session.get( Employer.class, "RedHat" ); + session.remove( employer ); + + // employee.employer is uninitialized. Since the column for employee.employer + // is a foreign key, and there is an Employer that has already been removed, + // employee.employer will need to be iniitialized to determine if + // employee.employer is nullifiable. + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + } + ); + } + + /** + * @implSpec Same as {@link #testRemoveEntityWithNullLazyManyToOne} but + * deleting the Employer linked to the loaded Employee + */ + @Test + public void testRemoveEntityWithLinkedLazyManyToOne() { + inTransaction( + session -> { + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + Employee employee = new Employee( "Jack" ); + employee.setEmployer( employer ); + session.persist( employee ); + } + ); + + inTransaction( + session -> { + Employee employee = session.get( Employee.class, "Jack" ); + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + + // Get and delete an Employer that is not associated with employee + Employer employer = session.get( Employer.class, "RedHat" ); + session.remove( employer ); + + // employee.employer is uninitialized. Since the column for employee.employer + // is a foreign key, and there is an Employer that has already been removed, + // employee.employer will need to be iniitialized to determine if + // employee.employer is nullifiable. + assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) ); + session.remove( employee ); + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + } + ); + } + + private void checkEntityEntryState( + final Session session, + final Employee employee, + final Object employer, + final boolean isEmployerNullified) { + final SessionImplementor sessionImplementor = (SessionImplementor) session; + final EntityEntry entityEntry = sessionImplementor.getPersistenceContext().getEntry( employee ); + final int propertyNumber = entityEntry.getPersister().getEntityMetamodel().getPropertyIndex( "employer" ); + assertEquals( + employer, + entityEntry.getLoadedState()[propertyNumber] + ); + if ( isEmployerNullified ) { + assertEquals( null, entityEntry.getDeletedState()[propertyNumber] ); + } + else { + assertEquals( + employer, + entityEntry.getDeletedState()[propertyNumber] + ); + } + } + + @Entity(name = "Employer") + public static class Employer { + private String name; + + private Set employees; + + public Employer(String name) { + this(); + setName( name ); + } + + @Id + public String getName() { + return name; + } + + @OneToMany(mappedBy = "employer", fetch = FetchType.LAZY) + public Set getEmployees() { + return employees; + } + + public void addEmployee(Employee employee) { + if ( getEmployees() == null ) { + setEmployees( new HashSet<>() ); + } + employees.add( employee ); + employee.setEmployer( this ); + } + + protected Employer() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployees(Set employees) { + this.employees = employees; + } + } + + @Entity(name = "Employee") + public static class Employee { + private long id; + + private String name; + + private Employer employer; + + public Employee(String name) { + this(); + setName( name ); + } + + public long getId() { + return id; + } + + @Id + public String getName() { + return name; + } + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @JoinColumn(name = "employer_name") + public Employer getEmployer() { + return employer; + } + + protected Employee() { + // this form used by Hibernate + } + + protected void setId(long id) { + this.id = id; + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployer(Employer employer) { + this.employer = employer; + } + + public int hashCode() { + if ( name != null ) { + return name.hashCode(); + } + else { + return 0; + } + } + + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + else if ( o instanceof Employee ) { + Employee other = Employee.class.cast( o ); + if ( name != null ) { + return getName().equals( other.getName() ); + } + else { + return other.getName() == null; + } + } + else { + return false; + } + } + } + + @Entity(name = "Manager") + public static class Manager extends Employee { + } + + @Entity(name = "Unrelated") + public static class Unrelated { + private int id; + + public Unrelated() { + } + + public Unrelated(int id) { + setId( id ); + } + + @Id + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + } + + public static class NoDirtyCheckEnhancementContext extends DefaultEnhancementContext { + @Override + public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { + return true; + } + + @Override + public boolean isLazyLoadable(UnloadedField field) { + return true; + } + + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return false; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/NaturalIdInUninitializedAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/NaturalIdInUninitializedAssociationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3e9ede028f0a1a1e2cc03d0043fb4c3137717457 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/NaturalIdInUninitializedAssociationTest.java @@ -0,0 +1,173 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.annotations.NaturalId; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * @author Gail Badner + */ +@SuppressWarnings({"unused", "WeakerAccess","ResultOfMethodCallIgnored"}) +@TestForIssue( jiraKey = "HHH-13607" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true, extendedEnhancement = true ) +public class NaturalIdInUninitializedAssociationTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testImmutableNaturalId() { + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + assertFalse( Hibernate.isPropertyInitialized( e,"entityImmutableNaturalId" ) ); + } + ); + + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + assertEquals( "immutable name", e.entityImmutableNaturalId.name ); + } + ); + } + + @Test + public void testMutableNaturalId() { + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + assertFalse( Hibernate.isPropertyInitialized( e,"entityMutableNaturalId" ) ); + } + ); + + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + assertEquals( "mutable name", e.entityMutableNaturalId.name ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AnEntity.class ); + sources.addAnnotatedClass( EntityMutableNaturalId.class ); + sources.addAnnotatedClass( EntityImmutableNaturalId.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + EntityMutableNaturalId entityMutableNaturalId = new EntityMutableNaturalId( 1, "mutable name" ); + EntityImmutableNaturalId entityImmutableNaturalId = new EntityImmutableNaturalId( 2, "immutable name" ); + AnEntity anEntity = new AnEntity(); + anEntity.id = 3; + anEntity.entityImmutableNaturalId = entityImmutableNaturalId; + anEntity.entityMutableNaturalId = entityMutableNaturalId; + session.persist( anEntity ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.delete( session.get( AnEntity.class, 3 ) ); + } + ); + } + + @Entity(name = "AnEntity") + public static class AnEntity { + @Id + private int id; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @LazyToOne(LazyToOneOption.NO_PROXY ) + private EntityMutableNaturalId entityMutableNaturalId; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @LazyToOne(LazyToOneOption.NO_PROXY ) + private EntityImmutableNaturalId entityImmutableNaturalId; + } + + @Entity(name = "EntityMutableNaturalId") + public static class EntityMutableNaturalId { + @Id + private int id; + + @NaturalId(mutable = true) + private String name; + + public EntityMutableNaturalId() { + } + + public EntityMutableNaturalId(int id, String name) { + this.id = id; + this.name = name; + } + } + + @Entity(name = "EntityImmutableNaturalId") + public static class EntityImmutableNaturalId { + @Id + private int id; + + @NaturalId(mutable = false) + private String name; + + public EntityImmutableNaturalId() { + } + + public EntityImmutableNaturalId(int id, String name) { + this.id = id; + this.name = name; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/QueryScrollingWithInheritanceEagerManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/QueryScrollingWithInheritanceEagerManyToOneTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7fc33ef7ade167e501d0372215cd1eb8dd81c3a9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/QueryScrollingWithInheritanceEagerManyToOneTest.java @@ -0,0 +1,323 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.Session; +import org.hibernate.StatelessSession; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.query.Query; +import org.hibernate.stat.spi.StatisticsImplementor; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Andrea Boriero + */ +@RunWith(BytecodeEnhancerRunner.class) +public class QueryScrollingWithInheritanceEagerManyToOneTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( EmployeeParent.class ); + sources.addAnnotatedClass( Employee.class ); + sources.addAnnotatedClass( OtherEntity.class ); + } + + @Test + public void testScrollableWithStatelessSession() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + ScrollableResults scrollableResults = null; + final StatelessSession statelessSession = sessionFactory().openStatelessSession(); + + try { + statelessSession.beginTransaction(); + Query query = statelessSession.createQuery( + "select distinct e from Employee e left join fetch e.otherEntities order by e.dept", + Employee.class + ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + + while ( scrollableResults.next() ) { + final Employee employee = (Employee) scrollableResults.get( 0 ); + assertThat( Hibernate.isPropertyInitialized( employee, "otherEntities" ), is( true ) ); + assertThat( Hibernate.isInitialized( employee.getOtherEntities() ), is( true ) ); + if ( "ENG1".equals( employee.getDept() ) ) { + assertThat( employee.getOtherEntities().size(), is( 2 ) ); + for ( OtherEntity otherEntity : employee.getOtherEntities() ) { + if ( "test1".equals( otherEntity.id ) ) { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( false ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( otherEntity.employeeParent, is( employee ) ); + } + else { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( false ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( Hibernate.isInitialized( otherEntity.employeeParent ), is( true ) ); + } + } + } + else { + assertThat( employee.getOtherEntities().size(), is( 0 ) ); + } + } + statelessSession.getTransaction().commit(); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + if ( statelessSession.getTransaction().isActive() ) { + statelessSession.getTransaction().rollback(); + } + statelessSession.close(); + } + } + + @Test + public void testScrollableWithSession() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + ScrollableResults scrollableResults = null; + final Session session = sessionFactory().openSession(); + + try { + session.beginTransaction(); + Query query = session.createQuery( + "select distinct e from Employee e left join fetch e.otherEntities order by e.dept", + Employee.class + ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + + while ( scrollableResults.next() ) { + final Employee employee = (Employee) scrollableResults.get( 0 ); + assertThat( Hibernate.isPropertyInitialized( employee, "otherEntities" ), is( true ) ); + assertThat( Hibernate.isInitialized( employee.getOtherEntities() ), is( true ) ); + if ( "ENG1".equals( employee.getDept() ) ) { + assertThat( employee.getOtherEntities().size(), is( 2 ) ); + for ( OtherEntity otherEntity : employee.getOtherEntities() ) { + if ( "test1".equals( otherEntity.id ) ) { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( false ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( otherEntity.employeeParent, is( employee ) ); + } + else { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( false ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( true ) ); + assertThat( Hibernate.isInitialized( otherEntity.employeeParent ), is( true ) ); + } + } + } + else { + assertThat( employee.getOtherEntities().size(), is( 0 ) ); + } + } + session.getTransaction().commit(); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + if ( session.getTransaction().isActive() ) { + session.getTransaction().rollback(); + } + session.close(); + } + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + Employee e1 = new Employee( "ENG1" ); + Employee e2 = new Employee( "ENG2" ); + OtherEntity other1 = new OtherEntity( "test1" ); + OtherEntity other2 = new OtherEntity( "test2" ); + e1.getOtherEntities().add( other1 ); + e1.getOtherEntities().add( other2 ); + e1.getParentOtherEntities().add( other1 ); + e1.getParentOtherEntities().add( other2 ); + other1.employee = e1; + other2.employee = e1; + other1.employeeParent = e1; + other2.employeeParent = e2; + session.persist( other1 ); + session.persist( other2 ); + session.persist( e1 ); + session.persist( e2 ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from OtherEntity" ).executeUpdate(); + session.createQuery( "delete from Employee" ).executeUpdate(); + session.createQuery( "delete from EmployeeParent" ).executeUpdate(); + } + ); + } + + @Entity(name = "EmployeeParent") + @Table(name = "EmployeeParent") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static abstract class EmployeeParent { + + @Id + private String dept; + + @OneToMany(targetEntity = OtherEntity.class, mappedBy = "employeeParent", fetch = FetchType.LAZY) + protected Set parentOtherEntities = new HashSet<>(); + + public Set getParentOtherEntities() { + if ( parentOtherEntities == null ) { + parentOtherEntities = new LinkedHashSet(); + } + return parentOtherEntities; + } + + public void setOtherEntities(Set pParentOtherEntites) { + parentOtherEntities = pParentOtherEntites; + } + + public String getDept() { + return dept; + } + + protected void setDept(String dept) { + this.dept = dept; + } + + } + + @Entity(name = "Employee") + @Table(name = "Employee") + public static class Employee extends EmployeeParent { + + @OneToMany(targetEntity = OtherEntity.class, mappedBy = "employee", fetch = FetchType.LAZY) + protected Set otherEntities = new HashSet<>(); + + public Employee(String dept) { + this(); + setDept( dept ); + } + + protected Employee() { + // this form used by Hibernate + } + + public Set getOtherEntities() { + if ( otherEntities == null ) { + otherEntities = new LinkedHashSet(); + } + return otherEntities; + } + + public void setOtherEntities(Set pOtherEntites) { + otherEntities = pOtherEntites; + } + } + + @Entity(name = "OtherEntity") + @Table(name = "OtherEntity") + public static class OtherEntity { + + @Id + private String id; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @JoinColumn(name = "Employee_Id") + protected Employee employee = null; + + @ManyToOne(fetch = FetchType.EAGER) + //@LazyToOne(LazyToOneOption.NO_PROXY) + @JoinColumn(name = "EmployeeParent_Id") + protected EmployeeParent employeeParent = null; + + protected OtherEntity() { + // this form used by Hibernate + } + + public OtherEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/QueryScrollingWithInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/QueryScrollingWithInheritanceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e9be4a677e485b85ec7d8adf407935435e7af18b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/QueryScrollingWithInheritanceTest.java @@ -0,0 +1,306 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.Session; +import org.hibernate.StatelessSession; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.query.Query; +import org.hibernate.stat.spi.StatisticsImplementor; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Andrea Boriero + */ +@RunWith(BytecodeEnhancerRunner.class) +public class QueryScrollingWithInheritanceTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( EmployeeParent.class ); + sources.addAnnotatedClass( Employee.class ); + sources.addAnnotatedClass( OtherEntity.class ); + } + + @Test + public void testScrollableWithStatelessSession() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + ScrollableResults scrollableResults = null; + final StatelessSession statelessSession = sessionFactory().openStatelessSession(); + + try { + statelessSession.beginTransaction(); + Query query = statelessSession.createQuery( + "select distinct e from Employee e left join fetch e.otherEntities order by e.dept", + Employee.class + ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + + while ( scrollableResults.next() ) { + final Employee employee = (Employee) scrollableResults.get( 0 ); + assertThat( Hibernate.isPropertyInitialized( employee, "otherEntities" ), is( true ) ); + assertThat( Hibernate.isInitialized( employee.getOtherEntities() ), is( true ) ); + if ( "ENG1".equals( employee.getDept() ) ) { + assertThat( employee.getOtherEntities().size(), is( 2 ) ); + for ( OtherEntity otherEntity : employee.getOtherEntities() ) { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( false ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( false ) ); + } + } + else { + assertThat( employee.getOtherEntities().size(), is( 0 ) ); + } + } + statelessSession.getTransaction().commit(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + if ( statelessSession.getTransaction().isActive() ) { + statelessSession.getTransaction().rollback(); + } + statelessSession.close(); + } + } + + @Test + public void testScrollableWithSession() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + ScrollableResults scrollableResults = null; + final Session session = sessionFactory().openSession(); + + try { + session.beginTransaction(); + Query query = session.createQuery( + "select distinct e from Employee e left join fetch e.otherEntities order by e.dept", + Employee.class + ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + + while ( scrollableResults.next() ) { + final Employee employee = (Employee) scrollableResults.get( 0 ); + assertThat( Hibernate.isPropertyInitialized( employee, "otherEntities" ), is( true ) ); + assertThat( Hibernate.isInitialized( employee.getOtherEntities() ), is( true ) ); + if ( "ENG1".equals( employee.getDept() ) ) { + assertThat( employee.getOtherEntities().size(), is( 2 ) ); + for ( OtherEntity otherEntity : employee.getOtherEntities() ) { + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employee" ), is( false ) ); + assertThat( Hibernate.isPropertyInitialized( otherEntity, "employeeParent" ), is( false ) ); + } + } + else { + assertThat( employee.getOtherEntities().size(), is( 0 ) ); + } + } + session.getTransaction().commit(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + if ( session.getTransaction().isActive() ) { + session.getTransaction().rollback(); + } + session.close(); + } + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + Employee e1 = new Employee( "ENG1" ); + Employee e2 = new Employee( "ENG2" ); + OtherEntity other1 = new OtherEntity( "test1" ); + OtherEntity other2 = new OtherEntity( "test2" ); + e1.getOtherEntities().add( other1 ); + e1.getOtherEntities().add( other2 ); + e1.getParentOtherEntities().add( other1 ); + e1.getParentOtherEntities().add( other2 ); + other1.employee = e1; + other2.employee = e1; + other1.employeeParent = e1; + other2.employeeParent = e2; + session.persist( other1 ); + session.persist( other2 ); + session.persist( e1 ); + session.persist( e2 ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from OtherEntity" ).executeUpdate(); + session.createQuery( "delete from Employee" ).executeUpdate(); + session.createQuery( "delete from EmployeeParent" ).executeUpdate(); + } + ); + } + + @Entity(name = "EmployeeParent") + @Table(name = "EmployeeParent") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static abstract class EmployeeParent { + + @Id + private String dept; + + @OneToMany(targetEntity = OtherEntity.class, mappedBy = "employeeParent", fetch = FetchType.LAZY) + protected Set parentOtherEntities = new HashSet<>(); + + public Set getParentOtherEntities() { + if ( parentOtherEntities == null ) { + parentOtherEntities = new LinkedHashSet(); + } + return parentOtherEntities; + } + + public void setOtherEntities(Set pParentOtherEntites) { + parentOtherEntities = pParentOtherEntites; + } + + public String getDept() { + return dept; + } + + protected void setDept(String dept) { + this.dept = dept; + } + + } + + @Entity(name = "Employee") + @Table(name = "Employee") + public static class Employee extends EmployeeParent { + + @OneToMany(targetEntity = OtherEntity.class, mappedBy = "employee", fetch = FetchType.LAZY) + protected Set otherEntities = new HashSet<>(); + + public Employee(String dept) { + this(); + setDept( dept ); + } + + protected Employee() { + // this form used by Hibernate + } + + public Set getOtherEntities() { + if ( otherEntities == null ) { + otherEntities = new LinkedHashSet(); + } + return otherEntities; + } + + public void setOtherEntities(Set pOtherEntites) { + otherEntities = pOtherEntites; + } + } + + @Entity(name = "OtherEntity") + @Table(name = "OtherEntity") + public static class OtherEntity { + + @Id + private String id; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @JoinColumn(name = "Employee_Id") + protected Employee employee = null; + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @JoinColumn(name = "EmployeeParent_Id") + protected EmployeeParent employeeParent = null; + + protected OtherEntity() { + // this form used by Hibernate + } + + public OtherEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2aa0d1cdf448caa9d23e23185e65895a7f0ba57c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java @@ -0,0 +1,510 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy; + +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.StatelessSession; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.query.Query; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Steve Ebersole + */ +@RunWith(BytecodeEnhancerRunner.class) +public class StatelessQueryScrollingTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testDynamicFetchScroll() { + ScrollableResults scrollableResults = null; + final StatelessSession statelessSession = sessionFactory().openStatelessSession(); + try { + final Query query = statelessSession.createQuery( "from Task t join fetch t.resource join fetch t.user" ); + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + while ( scrollableResults.next() ) { + Task taskRef = (Task) scrollableResults.get( 0 ); + assertTrue( Hibernate.isInitialized( taskRef ) ); + assertTrue( Hibernate.isInitialized( taskRef.getUser() ) ); + assertTrue( Hibernate.isInitialized( taskRef.getResource() ) ); + assertFalse( Hibernate.isInitialized( taskRef.getResource().getOwner() ) ); + } + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + statelessSession.close(); + } + } + + @Test + public void testDynamicFetchCollectionScroll() { + ScrollableResults scrollableResults = null; + StatelessSession statelessSession = sessionFactory().openStatelessSession(); + statelessSession.beginTransaction(); + + try { + final Query query = statelessSession.createQuery( "select p from Producer p join fetch p.products" ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } + while ( scrollableResults.next() ) { + Producer producer = (Producer) scrollableResults.get( 0 ); + assertTrue( Hibernate.isInitialized( producer ) ); + assertTrue( Hibernate.isPropertyInitialized( producer, "products" ) ); + assertTrue( Hibernate.isInitialized( producer.getProducts() ) ); + + for ( Product product : producer.getProducts() ) { + assertTrue( Hibernate.isInitialized( product ) ); + assertFalse( Hibernate.isInitialized( product.getVendor() ) ); + } + } + } + finally { + if ( scrollableResults != null ) { + scrollableResults.close(); + } + statelessSession.getTransaction().commit(); + statelessSession.close(); + } + } + + + @Before + public void createTestData() { + inTransaction( + session -> { + Date now = new Date(); + User me = new User( "me" ); + User you = new User( "you" ); + Resource yourClock = new Resource( "clock", you ); + Task task = new Task( me, "clean", yourClock, now ); // :) + + session.save( me ); + session.save( you ); + session.save( yourClock ); + session.save( task ); + + User u3 = new User( "U3" ); + User u4 = new User( "U4" ); + Resource it = new Resource( "it", u4 ); + Task task2 = new Task( u3, "beat", it, now ); // :)) + + session.save( u3 ); + session.save( u4 ); + session.save( it ); + session.save( task2 ); + } + ); + + inTransaction( + session -> { + Producer p1 = new Producer( 1, "Acme" ); + Producer p2 = new Producer( 2, "ABC" ); + + session.save( p1 ); + session.save( p2 ); + + Vendor v1 = new Vendor( 1, "v1" ); + Vendor v2 = new Vendor( 2, "v2" ); + + session.save( v1 ); + session.save( v2 ); + + final Product product1 = new Product( 1, "123", v1, p1 ); + final Product product2 = new Product( 2, "456", v1, p1 ); + final Product product3 = new Product( 3, "789", v1, p2 ); + + session.save( product1 ); + session.save( product2 ); + session.save( product3 ); + } + ); + } + + @After + public void deleteTestData() { + inTransaction( + s -> { + s.createQuery( "delete Task" ).executeUpdate(); + s.createQuery( "delete Resource" ).executeUpdate(); + s.createQuery( "delete User" ).executeUpdate(); + + s.createQuery( "delete Product" ).executeUpdate(); + s.createQuery( "delete Producer" ).executeUpdate(); + s.createQuery( "delete Vendor" ).executeUpdate(); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( Task.class ); + sources.addAnnotatedClass( User.class ); + sources.addAnnotatedClass( Resource.class ); + sources.addAnnotatedClass( Product.class ); + sources.addAnnotatedClass( Producer.class ); + sources.addAnnotatedClass( Vendor.class ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Collection fetch scrolling + + @Entity(name = "Producer") + public static class Producer { + @Id + private Integer id; + + private String name; + + @OneToMany(mappedBy = "producer", fetch = FetchType.LAZY) + private Set products = new HashSet<>(); + + public Producer() { + } + + public Producer(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getProducts() { + return products; + } + + public void setProducts(Set products) { + this.products = products; + } + } + + @Entity(name = "Product") + public static class Product { + @Id + private Integer id; + private String sku; + + @ManyToOne(fetch = FetchType.LAZY) + private Vendor vendor; + + @ManyToOne(fetch = FetchType.LAZY) + private Producer producer; + + public Product() { + } + + public Product(Integer id, String sku, Vendor vendor, Producer producer) { + this.id = id; + this.sku = sku; + this.vendor = vendor; + this.producer = producer; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSku() { + return sku; + } + + public void setSku(String sku) { + this.sku = sku; + } + + public Vendor getVendor() { + return vendor; + } + + public void setVendor(Vendor vendor) { + this.vendor = vendor; + } + + public Producer getProducer() { + return producer; + } + + public void setProducer(Producer producer) { + this.producer = producer; + } + } + + @Entity(name = "Vendor") + public static class Vendor { + @Id + private Integer id; + private String name; + + @OneToMany(mappedBy = "vendor", fetch = FetchType.LAZY) + private Set products = new HashSet<>(); + + public Vendor() { + } + + public Vendor(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getProducts() { + return products; + } + + public void setProducts(Set products) { + this.products = products; + } + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Entity fetch scrolling + + @Entity(name = "Resource") + @Table(name = "resources") + public static class Resource { + @Id + @GeneratedValue(generator = "increment") + private Long id; + private String name; + @ManyToOne(fetch = FetchType.LAZY) + private User owner; + + public Resource() { + } + + public Resource(String name, User owner) { + this.name = name; + this.owner = owner; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public User getOwner() { + return owner; + } + + public void setOwner(User owner) { + this.owner = owner; + } + } + + @Entity(name = "User") + @Table(name = "users") + public static class User { + @Id + @GeneratedValue(generator = "increment") + private Long id; + private String name; + + public User() { + } + + public User(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Task") + public static class Task { + @Id + @GeneratedValue(generator = "increment") + private Long id; + private String description; + @ManyToOne(fetch = FetchType.LAZY) + private User user; + @ManyToOne(fetch = FetchType.LAZY) + private Resource resource; + private Date dueDate; + private Date startDate; + private Date completionDate; + + public Task() { + } + + public Task(User user, String description, Resource resource, Date dueDate) { + this( user, description, resource, dueDate, null, null ); + } + + public Task( + User user, + String description, + Resource resource, + Date dueDate, + Date startDate, + Date completionDate) { + this.user = user; + this.resource = resource; + this.description = description; + this.dueDate = dueDate; + this.startDate = startDate; + this.completionDate = completionDate; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Resource getResource() { + return resource; + } + + public void setResource(Resource resource) { + this.resource = resource; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getDueDate() { + return dueDate; + } + + public void setDueDate(Date dueDate) { + this.dueDate = dueDate; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getCompletionDate() { + return completionDate; + } + + public void setCompletionDate(Date completionDate) { + this.completionDate = completionDate; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsInEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsInEmbeddableTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2ff22a49adbe720d8c9fac8671f09dae4f15b43f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsInEmbeddableTest.java @@ -0,0 +1,227 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.group; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests removing non-owning side of the bidirectional association, + * where owning side is in an embeddable. + * + * Tests with and without dirty-checking using enhancement. + * + * @author Gail Badner + */ +@TestForIssue(jiraKey = "HHH-13241") +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext({ + EnhancerTestContext.class, + BidirectionalLazyGroupsInEmbeddableTest.NoDirtyCheckEnhancementContext.class +}) +public class BidirectionalLazyGroupsInEmbeddableTest extends BaseCoreFunctionalTestCase { + + public Class[] getAnnotatedClasses() { + return new Class[] { Employer.class, Employee.class }; + } + + @Test + @Ignore("Test is failing with Javassist and also fails with ByteBuddy if the mappings are moved to the fields.") + public void test() { + + doInHibernate( + this::sessionFactory, session -> { + + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + employer.addEmployee( new Employee( "Jack" ) ); + employer.addEmployee( new Employee( "Jill" ) ); + employer.addEmployee( new Employee( "John" ) ); + for ( Employee employee : employer.getEmployees() ) { + session.persist( employee ); + } + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = session.createQuery( "from Employer e", Employer.class ).getSingleResult(); + session.remove( employer ); + for ( Employee employee : employer.getEmployees() ) { + session.remove( employee ); + } + } + ); + } + + @Entity(name = "Employer") + public static class Employer { + private String name; + + private Set employees; + + public Employer(String name) { + this(); + setName( name ); + } + + @Id + public String getName() { + return name; + } + + @OneToMany(mappedBy = "employerContainer.employer", fetch = FetchType.LAZY) + @LazyGroup("Employees") + public Set getEmployees() { + return employees; + } + + public void addEmployee(Employee employee) { + if ( getEmployees() == null ) { + setEmployees( new HashSet<>() ); + } + employees.add( employee ); + if ( employee.getEmployerContainer() == null ) { + employee.setEmployerContainer( new EmployerContainer() ); + } + employee.getEmployerContainer().setEmployer( this ); + } + + protected Employer() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployees(Set employees) { + this.employees = employees; + } + } + + @Entity(name = "Employee") + public static class Employee { + private long id; + + private String name; + + public Employee(String name) { + this(); + setName( name ); + } + + @Id + @GeneratedValue + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public EmployerContainer employerContainer; + + protected Employee() { + // this form used by Hibernate + } + + public EmployerContainer getEmployerContainer() { + return employerContainer; + } + + protected void setId(long id) { + this.id = id; + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployerContainer(EmployerContainer employerContainer) { + this.employerContainer = employerContainer; + } + + public int hashCode() { + if ( name != null ) { + return name.hashCode(); + } + else { + return 0; + } + } + + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + else if ( o instanceof Employee ) { + Employee other = Employee.class.cast( o ); + if ( name != null ) { + return getName().equals( other.getName() ); + } + else { + return other.getName() == null; + } + } + else { + return false; + } + } + } + + @Embeddable + public static class EmployerContainer { + private Employer employer; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("EmployerForEmployee") + @JoinColumn(name = "employer_name") + public Employer getEmployer() { + return employer; + } + + protected void setEmployer(Employer employer) { + this.employer = employer; + } + } + + public static class NoDirtyCheckEnhancementContext extends DefaultEnhancementContext { + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return false; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8898e38d30087de85263f3e9e507edea43252302 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/BidirectionalLazyGroupsTest.java @@ -0,0 +1,218 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.group; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests removing non-owning side of the bidirectional association, + * with and without dirty-checking using enhancement. + * + * @author Gail Badner + */ +@TestForIssue(jiraKey = "HHH-13241") +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext({ + EnhancerTestContext.class, + BidirectionalLazyGroupsTest.NoDirtyCheckEnhancementContext.class +}) +public class BidirectionalLazyGroupsTest extends BaseCoreFunctionalTestCase { + + public Class[] getAnnotatedClasses() { + return new Class[] { Employer.class, Employee.class }; + } + + @Test + public void testRemoveCollectionOwnerNoCascade() { + + doInHibernate( + this::sessionFactory, session -> { + + Employer employer = new Employer( "RedHat" ); + session.persist( employer ); + employer.addEmployee( new Employee( "Jack" ) ); + employer.addEmployee( new Employee( "Jill" ) ); + employer.addEmployee( new Employee( "John" ) ); + for ( Employee employee : employer.getEmployees() ) { + session.persist( employee ); + } + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Employer employer = session.createQuery( "from Employer e", Employer.class ).getSingleResult(); + session.remove( employer ); + for ( Employee employee : employer.getEmployees() ) { + assertFalse( Hibernate.isPropertyInitialized( employee, "employer") ); + session.remove( employee ); + assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) ); + } + } + ); + + doInHibernate( + this::sessionFactory, session -> { + assertNull( session.find( Employer.class, "RedHat" ) ); + assertNull( session.createQuery( "from Employee e", Employee.class ).uniqueResult() ); + } + ); + } + + @Entity(name = "Employer") + public static class Employer { + private String name; + + private Set employees; + + public Employer(String name) { + this(); + setName( name ); + } + + @Id + public String getName() { + return name; + } + + @OneToMany(mappedBy = "employer", fetch = FetchType.LAZY) + @LazyGroup("Employees") + public Set getEmployees() { + return employees; + } + + public void addEmployee(Employee employee) { + if ( getEmployees() == null ) { + setEmployees( new HashSet<>() ); + } + employees.add( employee ); + employee.setEmployer( this ); + } + + protected Employer() { + // this form used by Hibernate + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployees(Set employees) { + this.employees = employees; + } + } + + @Entity(name = "Employee") + public static class Employee { + private long id; + + private String name; + + private Employer employer; + + public Employee(String name) { + this(); + setName( name ); + } + + @Id + @GeneratedValue + public long getId() { + return id; + } + + public String getName() { + return name; + } + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("EmployerForEmployee") + @JoinColumn(name = "employer_name") + public Employer getEmployer() { + return employer; + } + + protected Employee() { + // this form used by Hibernate + } + + protected void setId(long id) { + this.id = id; + } + + protected void setName(String name) { + this.name = name; + } + + protected void setEmployer(Employer employer) { + this.employer = employer; + } + + public int hashCode() { + if ( name != null ) { + return name.hashCode(); + } + else { + return 0; + } + } + + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + else if ( o instanceof Employee ) { + Employee other = Employee.class.cast( o ); + if ( name != null ) { + return getName().equals( other.getName() ); + } + else { + return other.getName() == null; + } + } + else { + return false; + } + } + } + + public static class NoDirtyCheckEnhancementContext extends DefaultEnhancementContext { + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return false; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTest.java index baacfbb3ee3a198ee5f5f1d11d65465fd6e46bfd..93a66e308c651d0c5efcb4d4c9f164a880ec860d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/group/SimpleLazyGroupUpdateTest.java @@ -64,7 +64,7 @@ public void prepare() { @Test public void test() { doInHibernate( this::sessionFactory, s -> { - TestEntity entity = s.load( TestEntity.class, 1L ); + TestEntity entity = s.get( TestEntity.class, 1L ); assertLoaded( entity, "name" ); assertNotLoaded( entity, "lifeStory" ); assertNotLoaded( entity, "reallyBigString" ); @@ -76,7 +76,7 @@ public void test() { } ); doInHibernate( this::sessionFactory, s -> { - TestEntity entity = s.load( TestEntity.class, 1L ); + TestEntity entity = s.get( TestEntity.class, 1L ); assertLoaded( entity, "name" ); assertNotLoaded( entity, "lifeStory" ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/AbstractKey.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/AbstractKey.java new file mode 100644 index 0000000000000000000000000000000000000000..ca1ccf7066c3e80e72ffd5ec7126df090cd1e825 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/AbstractKey.java @@ -0,0 +1,130 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +@Entity +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@Table(name = "PP_DCKey") +public abstract class AbstractKey extends ModelEntity + implements Serializable { + + @Column(name = "Name") + String name; + + @OneToMany(targetEntity = RoleEntity.class, mappedBy = "key", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("R") + protected Set roles = new LinkedHashSet<>(); + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("PR") + @JoinColumn + protected AbstractKey register; + + @OneToMany(targetEntity = AbstractKey.class, mappedBy = "register", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("RK") + protected Set keys = new LinkedHashSet(); + + @ManyToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("PP") + @JoinColumn + protected AbstractKey parent; + + @OneToMany(targetEntity = AbstractKey.class, mappedBy = "parent", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("PK") + protected Set otherKeys = new LinkedHashSet(); + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set role) { + this.roles = role; + } + + public void addRole(RoleEntity role) { + this.roles.add( role ); + } + + public AbstractKey getRegister() { + return register; + } + + public void setRegister(AbstractKey register) { + this.register = register; + } + + public Set getKeys() { + return keys; + } + + public void setKeys(Set keys) { + this.keys = keys; + } + + public void addRegisterKey(AbstractKey registerKey) { + keys.add( registerKey ); + } + + public AbstractKey getParent() { + return parent; + } + + public void setParent(AbstractKey parent) { + this.parent = parent; + } + + public Set getOtherKeys() { + return otherKeys; + } + + public void setOtherKeys(Set otherKeys) { + this.otherKeys = otherKeys; + } + + public void addPanelKey(AbstractKey panelKey) { + this.otherKeys.add( panelKey ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Activity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Activity.java new file mode 100644 index 0000000000000000000000000000000000000000..7b8b45a64056884da8821da0a71843e14f78b8d1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Activity.java @@ -0,0 +1,79 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Activity") +@Table(name = "activity") +@SuppressWarnings("WeakerAccess") +public class Activity extends BaseEntity { + private String description; + private Instruction instruction; + + protected WebApplication webApplication = null; + + /** + * Used by Hibernate + */ + @SuppressWarnings("unused") + public Activity() { + super(); + } + + public Activity(Integer id, String description, Instruction instruction) { + super( id ); + this.description = description; + this.instruction = instruction; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("Instruction") + @JoinColumn(name = "Instruction_Id") + public Instruction getInstruction() { + return instruction; + } + + @SuppressWarnings("unused") + public void setInstruction(Instruction instruction) { + this.instruction = instruction; + } + + @SuppressWarnings("unused") + @ManyToOne(fetch=FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("webApplication") + @JoinColumn(name="web_app_oid") + public WebApplication getWebApplication() { + return webApplication; + } + + public void setWebApplication(WebApplication webApplication) { + this.webApplication = webApplication; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Address.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Address.java new file mode 100644 index 0000000000000000000000000000000000000000..360a042081c26a4c28f7468a1011131536e26016 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Address.java @@ -0,0 +1,49 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Address") +@Table(name = "address") +public class Address { + private Integer id; + + private String text; + + public Address() { + } + + public Address(Integer id, String text) { + this.id = id; + this.text = text; + } + + @Id + @Column(name = "oid") + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BaseEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BaseEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..463d1bb7d6c81180883679dcbad79cf91ee149d4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BaseEntity.java @@ -0,0 +1,45 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Column; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +/** + * @author Steve Ebersole + */ +@MappedSuperclass +public class BaseEntity { + protected Integer id; + protected String nbr; + + public BaseEntity() { + } + + public BaseEntity(Integer id) { + this.id = id; + } + + @Id + @Column( name = "oid" ) + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getNbr() { + return nbr; + } + + public void setNbr(String nbr) { + this.nbr = nbr; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BatchFetchProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BatchFetchProxyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9cefe19f3b2584e23b5b9cd684e75811567fcfc1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BatchFetchProxyTest.java @@ -0,0 +1,261 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ + +@TestForIssue(jiraKey = "HHH-11147") +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class BatchFetchProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + private static int NUMBER_OF_ENTITIES = 20; + + @Test + @TestForIssue(jiraKey = "HHH-11147") + public void testBatchAssociationFetch() { + inTransaction( + session -> { + final Statistics statistics = sessionFactory().getStatistics(); + statistics.clear(); + List employees = session.createQuery( "from Employee", Employee.class ).getResultList(); + + assertEquals( 1, statistics.getPrepareStatementCount() ); + assertEquals( NUMBER_OF_ENTITIES, employees.size() ); + + for ( int i = 0; i < employees.size(); i++ ) { + final Employer employer = employees.get( i ).employer; + if ( i % 10 == 0 ) { + assertFalse( Hibernate.isInitialized( employer ) ); + Hibernate.initialize( employer ); + } + else { + assertTrue( Hibernate.isInitialized( employer ) ); + } + assertEquals( "Employer #" + employer.id, employer.name ); + } + + // assert that all 20 Employee and all 20 Employers have been loaded + assertThat( statistics.getEntityLoadCount(), is( 40L ) ); + // but assert that it only took 3 queries (the initial plus the 2 batch fetches) + assertThat( statistics.getPrepareStatementCount(), is( 3L ) ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-11147") + public void testBatchAssociation() { + inTransaction( + session -> { + final Statistics statistics = sessionFactory().getStatistics(); + statistics.clear(); + List employees = session.createQuery( "from Employee", Employee.class ).getResultList(); + + assertEquals( 1, statistics.getPrepareStatementCount() ); + assertEquals( NUMBER_OF_ENTITIES, employees.size() ); + + for ( int i = 0 ; i < employees.size() ; i++ ) { + final Employer employer = employees.get( i ).employer; + if ( i % 10 == 0 ) { + assertFalse( Hibernate.isInitialized( employer ) ); + Hibernate.initialize( employer ); + } + else { + assertTrue( Hibernate.isInitialized( employer ) ); + } + assertEquals( "Employer #" + employer.id, employer.name ); + } + + assertEquals( 3, statistics.getPrepareStatementCount() ); + } + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-11147") + public void testBatchEntityLoad() { + inTransaction( + session -> { + final Statistics statistics = sessionFactory().getStatistics(); + statistics.clear(); + + List employers = new ArrayList<>(); + for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) { + employers.add( session.load( Employer.class, i + 1) ); + } + + assertEquals( 0, statistics.getPrepareStatementCount() ); + + for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) { + final Employer employer = employers.get( i ); + if ( i % 10 == 0 ) { + assertFalse( Hibernate.isInitialized( employer ) ); + Hibernate.initialize( employer ); + } + else { + assertTrue( Hibernate.isInitialized( employer ) ); + } + assertEquals( "Employer #" + employer.id, employer.name ); + } + + assertEquals( 2, statistics.getPrepareStatementCount() ); + } + ); + } + + + @Test + @TestForIssue(jiraKey = "HHH-11147") + public void testBatchEntityLoadThenModify() { + inTransaction( + session -> { + final Statistics statistics = sessionFactory().getStatistics(); + statistics.clear(); + + List employers = new ArrayList<>(); + for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) { + employers.add( session.load( Employer.class, i + 1) ); + } + + assertEquals( 0, statistics.getPrepareStatementCount() ); + + for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) { + final Employer employer = employers.get( i ); + if ( i % 10 == 0 ) { + assertFalse( Hibernate.isInitialized( employer ) ); + Hibernate.initialize( employer ); + } + else { + assertTrue( Hibernate.isInitialized( employer ) ); + } + assertEquals( "Employer #" + employer.id, employer.name ); + employer.name = employer.name + " new"; + } + + assertEquals( 2, statistics.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + for ( int i = 0; i < NUMBER_OF_ENTITIES; i++ ) { + final Employer employer = session.get( Employer.class, i + 1 ); + assertEquals( "Employer #" + employer.id + " new", employer.name ); + } + } + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Employee.class, + Employer.class + }; + } + + @Before + public void setUpData() { + inTransaction( + session -> { + for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) { + final Employee employee = new Employee(); + employee.id = i + 1; + employee.name = "Employee #" + employee.id; + final Employer employer = new Employer(); + employer.id = i + 1; + employer.name = "Employer #" + employer.id; + employee.employer = employer; + session.persist( employee ); + } + } + ); + } + + @After + public void cleanupDate() { + inTransaction( + session -> { + session.createQuery( "delete from Employee" ).executeUpdate(); + session.createQuery( "delete from Employer" ).executeUpdate(); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + ssrb.applySetting( AvailableSettings.SHOW_SQL, true ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Entity(name = "Employee") + public static class Employee { + @Id + private int id; + + private String name; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @LazyToOne(LazyToOneOption.NO_PROXY) + private Employer employer; + } + + @Entity(name = "Employer") + @BatchSize(size = 10) + public static class Employer { + @Id + private int id; + + private String name; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BidirectionalProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BidirectionalProxyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..fd91eb9cf1022a1b4412b5a4d4b04c65bdf6b9e0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/BidirectionalProxyTest.java @@ -0,0 +1,364 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; + +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class BidirectionalProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testIt() { + inTransaction( + session -> { + for (BEntity b : session.createQuery("from BEntity b", BEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AChildEntity a = b.getA(); + assertEquals( 0, stats.getPrepareStatementCount() ); + a.getVersion(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getStringField(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getIntegerField(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setIntegerField( 1 ); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getEntries(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setVersion( new Short( "2" ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setStringField( "this is a string" ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + AMappedSuperclass mappedSuperclass = a; + mappedSuperclass.getVersion(); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + } + ); + + inTransaction( + session -> { + for (BEntity b : session.createQuery("from BEntity b", BEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AChildEntity a = b.getA(); + assertEquals( "this is a string", a.getStringField() ); + assertEquals( 2, a.getVersion() ); + assertEquals( new Integer( 1 ), a.getIntegerField() ); + } + } + ); + + inTransaction( + session -> { + for (CEntity c : session.createQuery("from CEntity c", CEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AEntity a = c.getA(); + assertEquals( 0, stats.getPrepareStatementCount() ); + a.getVersion(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.getIntegerField(); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setIntegerField( 1 ); + assertEquals( 1, stats.getPrepareStatementCount() ); + a.setVersion( new Short( "2" ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + } + ); + + inTransaction( + session -> { + for (CEntity c : session.createQuery("from CEntity c", CEntity.class).getResultList()) { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + AEntity a = c.getA(); + assertEquals( 2, a.getVersion() ); + assertEquals( new Integer( 1 ), a.getIntegerField() ); + } + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( BEntity.class ); + sources.addAnnotatedClass( CEntity.class ); + sources.addAnnotatedClass( AMappedSuperclass.class ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( AChildEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + AChildEntity a = new AChildEntity("a"); + BEntity b = new BEntity("b"); + b.setA(a); + session.persist(a); + session.persist(b); + + AChildEntity a1 = new AChildEntity("a1"); + CEntity c = new CEntity( "c" ); + c.setA( a1 ); + session.persist( a1 ); + session.persist( c ); + } + ); + } + + @After + public void clearTestData(){ + inTransaction( + session -> { + session.createQuery( "delete from BEntity" ).executeUpdate(); + session.createQuery( "delete from CEntity" ).executeUpdate(); + session.createQuery( "delete from AEntity" ).executeUpdate(); + } + ); + } + + @Entity(name="CEntity") + @Table(name="C") + public static class CEntity implements Serializable { + @Id + private String id; + + public CEntity(String id) { + this(); + setId(id); + } + + protected CEntity() { + } + + public String getId() { + return id; + } + + protected void setId(String id) { + this.id = id; + } + + public void setA(AEntity a) { + aChildEntity = a; + a.getcEntries().add(this); + } + + public AEntity getA() { + return aChildEntity; + } + + @ManyToOne(fetch= FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("aEntity") + @JoinColumn(name="aEntity") + protected AEntity aChildEntity = null; + } + + @Entity(name="BEntity") + @Table(name="B") + public static class BEntity implements Serializable { + @Id + private String id; + + public BEntity(String id) { + this(); + setId(id); + } + + protected BEntity() { + } + + public String getId() { + return id; + } + + protected void setId(String id) { + this.id = id; + } + + public void setA(AChildEntity a) { + aChildEntity = a; + a.getEntries().add(this); + } + + public AChildEntity getA() { + return aChildEntity; + } + + @ManyToOne(fetch= FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("aEntity") + @JoinColumn(name="aEntity") + protected AChildEntity aChildEntity = null; + } + + @MappedSuperclass + public static class AMappedSuperclass implements Serializable { + + @Id + private String id; + + @Basic + private short version; + + @Column(name = "INTEGER_FIELD") + private Integer integerField; + + public AMappedSuperclass(String id) { + setId(id); + } + + protected AMappedSuperclass() { + } + + public String getId() { + return id; + } + + protected void setId(String id) { + this.id = id; + } + + public short getVersion() { + return version; + } + + public void setVersion(short version) { + this.version = version; + } + + public Integer getIntegerField() { + return integerField; + } + + public void setIntegerField(Integer integerField) { + this.integerField = integerField; + } + } + + @Entity(name="AEntity") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + @Table(name="A") + public static class AEntity extends AMappedSuperclass { + + public AEntity(String id) { + super(id); + } + + protected AEntity() { + } + + @OneToMany(targetEntity=CEntity.class, mappedBy="aChildEntity", fetch=FetchType.LAZY) + protected Set cEntries = new LinkedHashSet(); + + public Set getcEntries() { + return cEntries; + } + + public void setcEntries(Set cEntries) { + this.cEntries = cEntries; + } + } + + @Entity(name="AChildEntity") + @Table(name="ACChild") + public static class AChildEntity extends AEntity { + + private String stringField; + + @OneToMany(targetEntity=BEntity.class, mappedBy="aChildEntity", fetch=FetchType.LAZY) + protected Set entries = new LinkedHashSet(); + + public AChildEntity(String id) { + super(id); + } + + protected AChildEntity() { + } + + public Set getEntries() { + return entries; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(String stringField) { + this.stringField = stringField; + } + + @Override + public Integer getIntegerField() { + return super.getIntegerField(); + } + + @Override + public void setIntegerField(Integer integerField) { + super.setIntegerField( integerField ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/CreditCardPayment.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/CreditCardPayment.java new file mode 100644 index 0000000000000000000000000000000000000000..f0529f83e8bae57245f2a9588be689c2d7224206 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/CreditCardPayment.java @@ -0,0 +1,35 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "CreditCardPayment") +@Table(name = "credit_payment") +public class CreditCardPayment extends Payment { + private String transactionId; + + public CreditCardPayment() { + } + + public CreditCardPayment(Integer oid, Float amount, String transactionId) { + super( oid, amount ); + this.transactionId = transactionId; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Customer.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Customer.java new file mode 100644 index 0000000000000000000000000000000000000000..a709fe1fea2a49bed1cfcb4971b808c5d4650128 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Customer.java @@ -0,0 +1,106 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Customer") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +public abstract class Customer { + private Integer oid; + private String name; + private Set orders = new HashSet<>(); + + private Address address; + + private Customer parentCustomer; + private Set childCustomers = new HashSet<>(); + + public Customer() { + } + + public Customer(Integer oid, String name, Address address, Customer parentCustomer) { + this.oid = oid; + this.name = name; + this.address = address; + this.parentCustomer = parentCustomer; + } + + @Id + @Column(name = "oid") + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "customer") + public Set getOrders() { + return orders; + } + + public void setOrders(Set orders) { + this.orders = orders; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + @LazyToOne(LazyToOneOption.NO_PROXY) + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + @LazyToOne(LazyToOneOption.NO_PROXY) + public Customer getParentCustomer() { + return parentCustomer; + } + + public void setParentCustomer(Customer parentCustomer) { + this.parentCustomer = parentCustomer; + } + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "parentCustomer") + public Set getChildCustomers() { + return childCustomers; + } + + public void setChildCustomers(Set childCustomers) { + this.childCustomers = childCustomers; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DebitCardPayment.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DebitCardPayment.java new file mode 100644 index 0000000000000000000000000000000000000000..9eb37212be10bc17aec006f96be882b2c74c4402 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DebitCardPayment.java @@ -0,0 +1,35 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "DebitCardPayment") +@Table(name = "debit_payment") +public class DebitCardPayment extends Payment { + private String transactionId; + + public DebitCardPayment() { + } + + public DebitCardPayment(Integer oid, Float amount, String transactionId) { + super( oid, amount ); + this.transactionId = transactionId; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceProxyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6b522ce761cc4122b3e08293138e275974482590 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceProxyTest.java @@ -0,0 +1,629 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.MappedSuperclass; + +import org.hibernate.Hibernate; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ + +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class DeepInheritanceProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testRootGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertFalse( Hibernate.isInitialized( aEntity) ); + aEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.get( AEntity.class, "AEntity" ); + + assertTrue( Hibernate.isInitialized( aEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity ) ); + aaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertFalse( Hibernate.isInitialized( aaEntity) ); + aaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.get( AAEntity.class, "AAEntity" ); + + assertTrue( Hibernate.isInitialized( aaEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + aaaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.setFieldInAEntity( false ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( Hibernate.isInitialized( aaaEntity) ); + aaaEntity.setFieldInAAAEntity( 4 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.get( AAAEntity.class, "AAAEntity" ); + + assertTrue( Hibernate.isInitialized( aaaEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AMappedSuperclass.class ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( AAEntity.class ); + sources.addAnnotatedClass( AAAEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + AEntity aEntity = new AEntity( "AEntity" ); + aEntity.setFieldInAMappedSuperclass( (short) 2 ); + aEntity.setFieldInAEntity( true ); + session.persist( aEntity ); + + AAEntity aaEntity = new AAAEntity( "AAEntity" ); + aaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaEntity.setFieldInAEntity( true ); + aaEntity.setFieldInAAEntity( "field in AAEntity" ); + session.persist( aaEntity ); + + AAAEntity aaaEntity = new AAAEntity( "AAAEntity" ); + aaaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaaEntity.setFieldInAEntity( true ); + aaaEntity.setFieldInAAEntity( "field in AAEntity" ); + aaaEntity.setFieldInAAAEntity( 3 ); + session.persist( aaaEntity ); + } + ); + } + + @After + public void clearTestData(){ + inTransaction( + session -> { + session.createQuery( "delete from AEntity" ).executeUpdate(); + } + ); + } + + @MappedSuperclass + public static class AMappedSuperclass implements Serializable { + + @Id + private String id; + + private short fieldInAMappedSuperclass; + + public short getFieldInAMappedSuperclass() { + return fieldInAMappedSuperclass; + } + + public void setFieldInAMappedSuperclass(short fieldInAMappedSuperclass) { + this.fieldInAMappedSuperclass = fieldInAMappedSuperclass; + } + + public AMappedSuperclass(String id) { + this.id = id; + } + + protected AMappedSuperclass() { + } + } + + @Entity(name="AEntity") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + public static class AEntity extends AMappedSuperclass { + + private Boolean fieldInAEntity; + + public AEntity(String id) { + super(id); + } + + protected AEntity() { + } + + public Boolean getFieldInAEntity() { + return fieldInAEntity; + } + public void setFieldInAEntity(Boolean fieldInAEntity) { + this.fieldInAEntity = fieldInAEntity; + } + } + + @Entity(name="AAEntity") + public static class AAEntity extends AEntity { + + private String fieldInAAEntity; + + public AAEntity(String id) { + super(id); + } + + protected AAEntity() { + } + + public String getFieldInAAEntity() { + return fieldInAAEntity; + } + + public void setFieldInAAEntity(String fieldInAAEntity) { + this.fieldInAAEntity = fieldInAAEntity; + } + } + + @Entity(name="AAAEntity") + public static class AAAEntity extends AAEntity { + + private long fieldInAAAEntity; + + public AAAEntity(String id) { + super(id); + } + + protected AAAEntity() { + } + + public long getFieldInAAAEntity() { + return fieldInAAAEntity; + } + + public void setFieldInAAAEntity(long fieldInAAAEntity) { + this.fieldInAAAEntity = fieldInAAAEntity; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceWithNonEntitiesProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceWithNonEntitiesProxyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5b1e466e3b10181770c12ba5f514ee57f74e31a1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DeepInheritanceWithNonEntitiesProxyTest.java @@ -0,0 +1,1514 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.MappedSuperclass; + +import org.hibernate.Hibernate; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ + +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class DeepInheritanceWithNonEntitiesProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testRootGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootGetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.getFieldInNonEntityAMappedSuperclassSuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.getFieldInNonEntityAEntitySuperclass(); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property + aEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.get( AEntity.class, "AEntity" ); + + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( (short) 3, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testRootSetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( (short) 5 ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 5, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + assertEquals( 6, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aEntity.getFieldInNonEntityAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.getReference( AEntity.class, "AEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aEntity ) ); + assertFalse( Hibernate.isInitialized( aEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aEntity.setFieldInNonEntityAEntitySuperclass( 10L ); + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 10 ), aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + assertEquals( 6, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aEntity.getFieldInNonEntityAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AEntity aEntity = session.get( AEntity.class, "AEntity" ); + + assertTrue( Hibernate.isInitialized( aEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property + aaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleGetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.getFieldInNonEntityAEntitySuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.getFieldInNonEntityAAEntitySuperclass(); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.setFieldInAEntity( false ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property + aaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.get( AAEntity.class, "AAEntity" ); + + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( (short) 3, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testMiddleSetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity ) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( (short) 10 ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 10, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + assertEquals( 6, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 4 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + assertEquals( 6, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.getReference( AAEntity.class, "AAEntity" ); + + assertTrue( HibernateProxy.class.isInstance( aaEntity ) ); + assertFalse( Hibernate.isInitialized( aaEntity) ); + // Gets initialized when access any property (even in a non-entity) + aaEntity.setFieldInNonEntityAAEntitySuperclass( "xyz" ); + assertTrue( Hibernate.isInitialized( aaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "xyz", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + assertEquals( 6, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAEntity aaEntity = session.get( AAEntity.class, "AAEntity" ); + + assertTrue( Hibernate.isInitialized( aaEntity) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafGetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity gets initialized when a persistent property is accessed + aaaEntity.getFieldInAMappedSuperclass(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity gets initialized when a persistent property is accessed + aaaEntity.getFieldInAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity gets initialized when a persistent property is accessed + aaaEntity.getFieldInAAEntity(); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafGetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAEntitySuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity only gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAAEntitySuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.getFieldInNonEntityAAAEntitySuperclass(); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafSetValueToInitialize() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAMappedSuperclass( (short) 3 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAEntity( false ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAAEntity( "updated field in AAEntity" ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity only gets initialized when a persistent property is accessed + aaaEntity.setFieldInAAAEntity( 4 ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.get( AAAEntity.class, "AAAEntity" ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( (short) 3, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( false, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "updated field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 4, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Test + public void testLeafSetValueInNonEntity() { + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 99 ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 99, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAEntitySuperclass( 10L ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 10 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "xyz" ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "xyz", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertEquals( 0, stats.getPrepareStatementCount() ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + // aaaEntity only gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.getReference( AAAEntity.class, "AAAEntity" ); + + assertFalse( HibernateProxy.class.isInstance( aaaEntity ) ); + assertFalse( Hibernate.isInitialized( aaaEntity) ); + // aaaEntity is not a HibernateProxy + // aaaEntity will not get intialized when a non-entity property is accessed + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( true ); + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( true, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + + assertFalse( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 0, stats.getPrepareStatementCount() ); + + // aaaEntity gets initialized when a persistent property is accessed + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + + // set the properties that are in non-entity classes after initialization + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 6 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 104L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "?" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( false ); + assertEquals( 6, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( Long.valueOf( 104 ), aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( "?", aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( false, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + } + ); + + inTransaction( + session -> { + final Statistics stats = sessionFactory().getStatistics(); + stats.clear(); + + AAAEntity aaaEntity = session.get( AAAEntity.class, "AAAEntity" ); + + assertTrue( Hibernate.isInitialized( aaaEntity ) ); + + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 0, aaaEntity.getFieldInNonEntityAMappedSuperclassSuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAEntitySuperclass() ); + assertEquals( null, aaaEntity.getFieldInNonEntityAAAEntitySuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( (short) 2, aaaEntity.getFieldInAMappedSuperclass() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( true, aaaEntity.getFieldInAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( "field in AAEntity", aaaEntity.getFieldInAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + assertEquals( 3, aaaEntity.getFieldInAAAEntity() ); + assertEquals( 1, stats.getPrepareStatementCount() ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AMappedSuperclass.class ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( AAEntity.class ); + sources.addAnnotatedClass( AAAEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + AEntity aEntity = new AEntity( "AEntity" ); + aEntity.setFieldInAMappedSuperclass( (short) 2 ); + aEntity.setFieldInAEntity( true ); + aEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 3 ); + aEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + session.persist( aEntity ); + + AAEntity aaEntity = new AAAEntity( "AAEntity" ); + aaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaEntity.setFieldInAEntity( true ); + aaEntity.setFieldInAAEntity( "field in AAEntity" ); + aaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 3 ); + aaEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + aaEntity.setFieldInNonEntityAAEntitySuperclass( "abc" ); + session.persist( aaEntity ); + + AAAEntity aaaEntity = new AAAEntity( "AAAEntity" ); + aaaEntity.setFieldInAMappedSuperclass( (short) 2 ); + aaaEntity.setFieldInAEntity( true ); + aaaEntity.setFieldInAAEntity( "field in AAEntity" ); + aaaEntity.setFieldInAAAEntity( 3 ); + aaaEntity.setFieldInNonEntityAMappedSuperclassSuperclass( 3 ); + aaaEntity.setFieldInNonEntityAEntitySuperclass( 4L ); + aaaEntity.setFieldInNonEntityAAEntitySuperclass( "abc" ); + aaaEntity.setFieldInNonEntityAAAEntitySuperclass( true ); + session.persist( aaaEntity ); + } + ); + } + + @After + public void clearTestData(){ + inTransaction( + session -> { + session.createQuery( "delete from AEntity" ).executeUpdate(); + } + ); + } + + public static class NonEntityAMappedSuperclassSuperclass { + private int fieldInNonEntityAMappedSuperclassSuperclass; + + public int getFieldInNonEntityAMappedSuperclassSuperclass() { + return fieldInNonEntityAMappedSuperclassSuperclass; + } + + public void setFieldInNonEntityAMappedSuperclassSuperclass(int fieldInNonEntityAMappedSuperclassSuperclass) { + this.fieldInNonEntityAMappedSuperclassSuperclass = fieldInNonEntityAMappedSuperclassSuperclass; + } + } + + @MappedSuperclass + public static class AMappedSuperclass extends NonEntityAMappedSuperclassSuperclass implements Serializable { + + @Id + private String id; + + private short fieldInAMappedSuperclass; + + public short getFieldInAMappedSuperclass() { + return fieldInAMappedSuperclass; + } + + public void setFieldInAMappedSuperclass(short fieldInAMappedSuperclass) { + this.fieldInAMappedSuperclass = fieldInAMappedSuperclass; + } + + public AMappedSuperclass(String id) { + this.id = id; + } + + protected AMappedSuperclass() { + } + } + + public static class NonEntityAEntitySuperclass extends AMappedSuperclass { + + private Long fieldInNonEntityAEntitySuperclass; + + public NonEntityAEntitySuperclass(String id) { + super( id ); + } + + protected NonEntityAEntitySuperclass() { + } + + public Long getFieldInNonEntityAEntitySuperclass() { + return fieldInNonEntityAEntitySuperclass; + } + + public void setFieldInNonEntityAEntitySuperclass(Long fieldInNonEntityAEntitySuperclass) { + this.fieldInNonEntityAEntitySuperclass = fieldInNonEntityAEntitySuperclass; + } + } + + @Entity(name="AEntity") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + public static class AEntity extends NonEntityAEntitySuperclass { + + private Boolean fieldInAEntity; + + public AEntity(String id) { + super(id); + } + + protected AEntity() { + } + + public Boolean getFieldInAEntity() { + return fieldInAEntity; + } + public void setFieldInAEntity(Boolean fieldInAEntity) { + this.fieldInAEntity = fieldInAEntity; + } + } + + public static class NonEntityAAEntitySuperclass extends AEntity { + + private String fieldInNonEntityAAEntitySuperclass; + + public NonEntityAAEntitySuperclass(String id) { + super( id ); + } + + protected NonEntityAAEntitySuperclass() { + } + + public String getFieldInNonEntityAAEntitySuperclass() { + return fieldInNonEntityAAEntitySuperclass; + } + + public void setFieldInNonEntityAAEntitySuperclass(String fieldInNonEntityAAEntitySuperclass) { + this.fieldInNonEntityAAEntitySuperclass = fieldInNonEntityAAEntitySuperclass; + } + } + + @Entity(name="AAEntity") + public static class AAEntity extends NonEntityAAEntitySuperclass { + + private String fieldInAAEntity; + + public AAEntity(String id) { + super(id); + } + + protected AAEntity() { + } + + public String getFieldInAAEntity() { + return fieldInAAEntity; + } + + public void setFieldInAAEntity(String fieldInAAEntity) { + this.fieldInAAEntity = fieldInAAEntity; + } + } + + public static class NonEntityAAAEntitySuperclass extends AAEntity { + + private Boolean fieldInNonEntityAAAEntitySuperclass; + + public NonEntityAAAEntitySuperclass(String id) { + super( id ); + } + + protected NonEntityAAAEntitySuperclass() { + } + + public Boolean getFieldInNonEntityAAAEntitySuperclass() { + return fieldInNonEntityAAAEntitySuperclass; + } + + public void setFieldInNonEntityAAAEntitySuperclass(Boolean fieldInNonEntityAAAEntitySuperclass) { + this.fieldInNonEntityAAAEntitySuperclass = fieldInNonEntityAAAEntitySuperclass; + } + } + + @Entity(name="AAAEntity") + public static class AAAEntity extends NonEntityAAAEntitySuperclass { + + private long fieldInAAAEntity; + + public AAAEntity(String id) { + super(id); + } + + protected AAAEntity() { + } + + public long getFieldInAAAEntity() { + return fieldInAAAEntity; + } + + public void setFieldInAAAEntity(long fieldInAAAEntity) { + this.fieldInAAAEntity = fieldInAAAEntity; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DomesticCustomer.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DomesticCustomer.java new file mode 100644 index 0000000000000000000000000000000000000000..8647b578e44a80874d54ab6ce0bf99d72168aa4f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/DomesticCustomer.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "DomesticCustomer") +@Table(name = "domestic_cust") +public class DomesticCustomer extends Customer { + private String taxId; + + public DomesticCustomer() { + } + + public DomesticCustomer( + Integer oid, + String name, + String taxId, + Address address, + Customer parentCustomer) { + super( oid, name, address, parentCustomer ); + this.taxId = taxId; + } + + public String getTaxId() { + return taxId; + } + + public void setTaxId(String taxId) { + this.taxId = taxId; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/EntitySharedInCollectionAndToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/EntitySharedInCollectionAndToOneTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1794cb01fd9494c35a5eb716a645bc4ea59fae0f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/EntitySharedInCollectionAndToOneTest.java @@ -0,0 +1,252 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class EntitySharedInCollectionAndToOneTest extends BaseNonConfigCoreFunctionalTestCase { + @Test + public void testIt() { + inTransaction( + session -> { + int passes = 0; + for ( CodeTable codeTable : session.createQuery( "from CodeTable ct where ct.id = 2", CodeTable.class ).list() ) { + assert 0 == passes; + passes++; + Hibernate.initialize( codeTable.getCodeTableItems() ); + } + + assertThat( session.getPersistenceContext().getNumberOfManagedEntities(), is( 2 ) ); + } + ); + } + + @Before + public void createTestData() { + inTransaction( + session -> { + final CodeTable codeTable1 = new CodeTable( 1, 1 ); + final CodeTableItem item1 = new CodeTableItem( 1, 1, "first" ); + final CodeTableItem item2 = new CodeTableItem( 2, 1, "second" ); + final CodeTableItem item3 = new CodeTableItem( 3, 1, "third" ); + + session.save( codeTable1 ); + session.save( item1 ); + session.save( item2 ); + session.save( item3 ); + + codeTable1.getCodeTableItems().add( item1 ); + item1.setCodeTable( codeTable1 ); + + codeTable1.getCodeTableItems().add( item2 ); + item2.setCodeTable( codeTable1 ); + + codeTable1.getCodeTableItems().add( item3 ); + item3.setCodeTable( codeTable1 ); + + codeTable1.setDefaultItem( item1 ); + item1.setDefaultItemInverse( codeTable1 ); + + final CodeTable codeTable2 = new CodeTable( 2, 1 ); + final CodeTableItem item4 = new CodeTableItem( 4, 1, "fourth" ); + + session.save( codeTable2 ); + session.save( item4 ); + + codeTable2.getCodeTableItems().add( item4 ); + item4.setCodeTable( codeTable2 ); + + codeTable2.setDefaultItem( item4 ); + item4.setDefaultItemInverse( codeTable2 ); + } + ); + } + +// @After +// public void deleteTestData() { +// inTransaction( +// session -> { +// for ( CodeTable codeTable : session.createQuery( "from CodeTable", CodeTable.class ).list() ) { +// session.delete( codeTable ); +// } +// } +// ); +// } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( CodeTableItem.class ); + sources.addAnnotatedClass( CodeTable.class ); + } + + @MappedSuperclass + public static class BaseEntity { + @Id + private Integer oid; + private int version; + + public BaseEntity() { + } + + public BaseEntity(Integer oid, int version) { + this.oid = oid; + this.version = version; + } + + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + } + + @Entity( name = "CodeTable" ) + @Table( name = "code_table" ) + public static class CodeTable extends BaseEntity { + @OneToOne( fetch = FetchType.LAZY ) + @LazyGroup( "defaultCodeTableItem" ) + @JoinColumn( name = "default_code_id" ) + private CodeTableItem defaultItem; + + @OneToMany( mappedBy = "codeTable" ) + private Set codeTableItems = new HashSet<>(); + + public CodeTable() { + } + + public CodeTable(Integer oid, int version) { + super( oid, version ); + } + + public CodeTableItem getDefaultItem() { + return defaultItem; + } + + public void setDefaultItem(CodeTableItem defaultItem) { + this.defaultItem = defaultItem; + } + + public Set getCodeTableItems() { + return codeTableItems; + } + + public void setCodeTableItems(Set codeTableItems) { + this.codeTableItems = codeTableItems; + } + } + + @Entity( name = "CodeTableItem" ) + @Table( name = "code_table_item" ) + public static class CodeTableItem extends BaseEntity { + private String name; + + @ManyToOne( fetch = FetchType.LAZY ) + @LazyGroup( "codeTable" ) + @JoinColumn( name = "code_table_oid" ) + private CodeTable codeTable; + + @OneToOne( mappedBy = "defaultItem", fetch=FetchType.LAZY ) + @LazyToOne( LazyToOneOption.NO_PROXY ) + @LazyGroup( "defaultItemInverse" ) + protected CodeTable defaultItemInverse; + + + public CodeTableItem() { + } + + public CodeTableItem(Integer oid, int version, String name) { + super( oid, version ); + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public CodeTable getCodeTable() { + return codeTable; + } + + public void setCodeTable(CodeTable codeTable) { + this.codeTable = codeTable; + } + + public CodeTable getDefaultItemInverse() { + return defaultItemInverse; + } + + public void setDefaultItemInverse(CodeTable defaultItemInverse) { + this.defaultItemInverse = defaultItemInverse; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/FetchGraphTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/FetchGraphTest.java new file mode 100644 index 0000000000000000000000000000000000000000..10ec469f9ef9f2093b2ec1f233f510bbc14475ec --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/FetchGraphTest.java @@ -0,0 +1,928 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.sql.Blob; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.ScrollableResults; +import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.stat.spi.StatisticsImplementor; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings({"unused", "WeakerAccess","ResultOfMethodCallIgnored"}) +@TestForIssue( jiraKey = "HHH-11147" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class FetchGraphTest extends BaseNonConfigCoreFunctionalTestCase { + + + @Test + public void testLoadNonOwningOneToOne() { + // Test loading D and accessing E + // E is the owner of the FK, not D. When `D#e` is accessed we + // need to actually load E because its table has the FK value, not + // D's table + + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getBytecodeEnhancementMetadata() + .isEnhancedForLazyLoading(); + + inSession( + session -> { + final DEntity entityD = session.load( DEntity.class, 1L ); + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + assert !Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + + entityD.getE(); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + assert Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isInitialized( entityD.getA() ); + assert Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isInitialized( entityD.getC() ); + assert Hibernate.isPropertyInitialized( entityD, "e" ); + assert Hibernate.isInitialized( entityD.getE() ); + } + ); + } + + @Test + public void testLoadOwningOneToOne() { + // switch it around + + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getBytecodeEnhancementMetadata() + .isEnhancedForLazyLoading(); + + inSession( + session -> { + final EEntity entityE = session.load( EEntity.class, 17L ); + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + assert !Hibernate.isPropertyInitialized( entityE, "d" ); + + final DEntity entityD = entityE.getD(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + assert !Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + } + ); + } + + @Test + public void testFetchingScroll() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getBytecodeEnhancementMetadata() + .isEnhancedForLazyLoading(); + + + inStatelessSession( + session -> { + final String qry = "select e from E e join fetch e.d"; + + final ScrollableResults scrollableResults = session.createQuery( qry ).scroll(); + while ( scrollableResults.next() ) { + System.out.println( "Got entity : " + scrollableResults.get( 0 ) ); + } + } + ); + + inStatelessSession( + session -> { + final String qry = "select d from D d " + + "join fetch d.a " + + "join fetch d.bs " + + "join fetch d.c " + + "join fetch d.e " + + "join fetch d.g"; + + final ScrollableResults scrollableResults = session.createQuery( qry ).scroll(); + while ( scrollableResults.next() ) { + System.out.println( "Got entity : " + scrollableResults.get( 0 ) ); + } + } + ); + + inStatelessSession( + session -> { + final String qry = "select g from G g join fetch g.dEntities"; + + final ScrollableResults scrollableResults = session.createQuery( qry ).scroll(); + while ( scrollableResults.next() ) { + System.out.println( "Got entity : " + scrollableResults.get( 0 ) ); + } + } + ); + } + + + @Test + public void testLazyAssociationSameAsNonLazyInPC() { + + assert sessionFactory().getMetamodel() + .entityPersister( DEntity.class ) + .getInstrumentationMetadata() + .isEnhancedForLazyLoading(); + + inSession( + session -> { + final AEntity entityA = session.get( AEntity.class, 1L ); + + final DEntity entityD = session.load( DEntity.class, 1L ); + assert !Hibernate.isInitialized( entityD ); + Hibernate.initialize( entityD ); + assert Hibernate.isPropertyInitialized( entityD, "a" ); + assert entityA.getOid() == entityD.getA().getOid(); + assert session.getPersistenceContext().getEntry( entityA ) == + session.getPersistenceContext().getEntry( entityD.getA() ); + assert entityA == entityD.getA(); + } + ); + } + + @Test + public void testRandomAccess() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + final EntityPersister persister = sessionFactory().getMetamodel().entityPersister( DEntity.class ); + assert persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); + + inSession( + session -> { + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Load a D + + final DEntity entityD = session.load( DEntity.class, 1L ); + + assertThat( entityD instanceof HibernateProxy, is(false) ); + assertThat( entityD instanceof PersistentAttributeInterceptable, is(true) ); + assertThat( Hibernate.isInitialized( entityD ), is(false) ); + // Because D is enhanced we should not have executed any SQL + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + + // access the id. + // -since entityD is a "enhanced proxy", this should not trigger loading + assertThat( entityD.getOid(), is(1L) ); + assertThat( Hibernate.isInitialized( entityD ), is(false) ); + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + + + // Because D is enhanced we should not have executed any SQL + assertThat( stats.getPrepareStatementCount(), is( 0L ) ); + + assert !Hibernate.isPropertyInitialized( entityD, "a" ); + assert !Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + assert !Hibernate.isPropertyInitialized( entityD, "g" ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access C + + final CEntity c = entityD.getC(); + + // make sure interception happened + assertThat( c, notNullValue() ); + + // See `#testLoadNonOwningOneToOne` + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + // The fields themselves are initialized - set to the + // enhanced entity "proxy" instance + assert Hibernate.isPropertyInitialized( entityD, "a" ); + assert Hibernate.isPropertyInitialized( entityD, "c" ); + assert !Hibernate.isPropertyInitialized( entityD, "e" ); + + assert !Hibernate.isInitialized( entityD.getA() ); + assert !Hibernate.isInitialized( entityD.getC() ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access C again + + entityD.getC(); + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access E + + final EEntity e1 = entityD.getE(); + assert Hibernate.isPropertyInitialized( entityD, "e" ); + + assert Hibernate.isInitialized( entityD.getE() ); + + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + assert Hibernate.isInitialized( e1 ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Access E again + + entityD.getE(); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + + assertThat( entityD.getE().getOid(), is(17L) ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // now lets access the attribute "proxies" + + // this will load the table C data + entityD.getC().getC1(); + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + assert Hibernate.isInitialized( entityD.getC() ); + + // this should not - it was already loaded above + entityD.getE().getE1(); + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + + Set bs = entityD.getBs(); + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + assertThat( bs.size(), is( 2 ) ); + assertThat( stats.getPrepareStatementCount(), is( 4L ) ); + + entityD.getG().getOid(); + assertThat( stats.getPrepareStatementCount(), is( 4L ) ); + } + ); + } + + @Test + public void testNullManyToOneHql() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + inTransaction( + session -> { + final String qry = "select e from Activity e"; + final List activities = session.createQuery( qry, Activity.class ).list(); + + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + long expectedCount = 1L; + + for ( Activity activity : activities ) { + if ( activity.getInstruction() != null ) { + // do something special + // - here we just access an attribute to trigger + // the initialization of the association + + activity.getInstruction().getSummary(); + assertThat( stats.getPrepareStatementCount(), is( ++expectedCount ) ); + } + + if ( activity.getWebApplication() != null ) { + // trigger base group initialization + activity.getWebApplication().getName(); + assertThat( stats.getPrepareStatementCount(), is( ++expectedCount ) ); + // trigger initialization + activity.getWebApplication().getSiteUrl(); + assertThat( stats.getPrepareStatementCount(), is( ++expectedCount ) ); + } + } + } + ); + } + + @Test + public void testAbstractClassAssociation() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + assert sessionFactory().getMetamodel() + .entityPersister( RoleEntity.class ) + .getInstrumentationMetadata() + .isEnhancedForLazyLoading(); + + inTransaction( + session -> { + final String qry = "select e from RoleEntity e"; + final List keyRoleEntities = session.createQuery( qry, RoleEntity.class ).list(); + + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + for ( RoleEntity keyRoleEntity : keyRoleEntities ) { + Object key = Hibernate.unproxy( keyRoleEntity.getKey() ); + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + + Set specializedEntities = ( (SpecializedKey) key ) + .getSpecializedEntities(); + + assertThat( stats.getPrepareStatementCount(), is( 2L ) ); + + Iterator iterator = specializedEntities.iterator(); + while ( iterator.hasNext() ) { + SpecializedEntity specializedEntity = iterator.next(); + assertThat( specializedEntity.getId(), notNullValue() ); + specializedEntity.getValue(); + } + + // but regardless there should not be an additional query + assertThat( stats.getPrepareStatementCount(), is( 3L ) ); + } + } + ); + } + + @Test + public void testNonAbstractAssociationWithSubclassValue() { + final StatisticsImplementor stats = sessionFactory().getStatistics(); + stats.clear(); + + inTransaction( + session -> { + final String qry = "select e from RoleEntity e"; + final List keyRoleEntities = session.createQuery( qry, RoleEntity.class ).list(); + + assertThat( stats.getPrepareStatementCount(), is( 1L ) ); + + assertThat( keyRoleEntities.size(), is( 1 ) ); + + RoleEntity roleEntity = keyRoleEntities.get( 0 ); + assertThat( + Hibernate.unproxy( roleEntity.getKey() ).getClass().getName(), + is( SpecializedKey.class.getName() ) + ); + + assertThat( + Hibernate.unproxy( roleEntity.getSpecializedKey() ).getClass().getName(), + is( MoreSpecializedKey.class.getName() ) + ); + } + ); + } + + @Test + public void testQueryAndDeleteDEntity() { + inTransaction( + session -> { + List result = session.createQuery( + "select d from D d ", + DEntity.class + ).list(); + result.forEach( entity -> { + session.delete( entity ); + session.delete( entity.getE() ); + session.delete( entity.getA() ); + Set bs = entity.getBs(); + bs.forEach( bEntity -> session.delete( bEntity ) ); + session.delete( entity.getC() ); + session.delete( entity.getG() ); + + } ); + } + ); + } + + @Test + public void testLoadAndDeleteDEntity() { + inTransaction( + session -> { + DEntity entity = session.load( DEntity.class, 1L ); + session.delete( entity ); + session.delete( entity.getE() ); + session.delete( entity.getA() ); + Set bs = entity.getBs(); + bs.forEach( bEntity -> session.delete( bEntity ) ); + session.delete( entity.getC() ); + session.delete( entity.getG() ); + } + ); + } + + @Test + public void testGetAndDeleteDEntity() { + inTransaction( + session -> { + DEntity entity = session.get( DEntity.class, 1L ); + session.delete( entity ); + session.delete( entity.getE() ); + session.delete( entity.getA() ); + Set bs = entity.getBs(); + bs.forEach( bEntity -> session.delete( bEntity ) ); + session.delete( entity.getC() ); + session.delete( entity.getG() ); + } + ); + } + + @Test + public void testGetAndDeleteEEntity() { + inTransaction( + session -> { + EEntity entity = session.get( EEntity.class, 17L ); + session.delete( entity ); + session.delete( entity.getD() ); + } + ); + } + + @Test + public void testLoadAndDeleteEEntity() { + inTransaction( + session -> { + EEntity entity = session.load( EEntity.class, 17L ); + session.delete( entity ); + session.delete( entity.getD() ); + } + ); + } + + @Test + public void testQueryAndDeleteEEntity() { + inTransaction( + session -> { + List result = session.createQuery( + "select e from E e", + EEntity.class + ).list(); + result.forEach( entity -> { + session.delete( entity ); + session.delete( entity.getD() ); + } ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AEntity.class ); + sources.addAnnotatedClass( BEntity.class ); + sources.addAnnotatedClass( CEntity.class ); + sources.addAnnotatedClass( DEntity.class ); + sources.addAnnotatedClass( EEntity.class ); + sources.addAnnotatedClass( GEntity.class ); + + sources.addAnnotatedClass( Activity.class ); + sources.addAnnotatedClass( Instruction.class ); + sources.addAnnotatedClass( WebApplication.class ); + + sources.addAnnotatedClass( SpecializedKey.class ); + sources.addAnnotatedClass( MoreSpecializedKey.class ); + sources.addAnnotatedClass( RoleEntity.class ); + sources.addAnnotatedClass( AbstractKey.class ); + sources.addAnnotatedClass( GenericKey.class ); + sources.addAnnotatedClass( SpecializedEntity.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + DEntity d = new DEntity(); + d.setD( "bla" ); + d.setOid( 1 ); + + byte[] lBytes = "agdfagdfagfgafgsfdgasfdgfgasdfgadsfgasfdgasfdgasdasfdg".getBytes(); + Blob lBlob = Hibernate.getLobCreator( session ).createBlob( lBytes ); + d.setBlob( lBlob ); + + BEntity b1 = new BEntity(); + b1.setOid( 1 ); + b1.setB1( 34 ); + b1.setB2( "huhu" ); + + BEntity b2 = new BEntity(); + b2.setOid( 2 ); + b2.setB1( 37 ); + b2.setB2( "haha" ); + + Set lBs = new HashSet<>(); + lBs.add( b1 ); + lBs.add( b2 ); + d.setBs( lBs ); + + AEntity a = new AEntity(); + a.setOid( 1 ); + a.setA( "hihi" ); + d.setA( a ); + + EEntity e = new EEntity(); + e.setOid( 17 ); + e.setE1( "Balu" ); + e.setE2( "Bär" ); + + e.setD( d ); + d.setE( e ); + + CEntity c = new CEntity(); + c.setOid( 1 ); + c.setC1( "ast" ); + c.setC2( "qwert" ); + c.setC3( "yxcv" ); + d.setC( c ); + + GEntity g = new GEntity(); + g.setOid( 1 ); + g.getdEntities().add( d ); + d.setG( g ); + + + session.save( b1 ); + session.save( b2 ); + session.save( a ); + session.save( c ); + session.save( g ); + session.save( d ); + session.save( e ); + + + // create a slew of Activity objects, some with Instruction reference + // some without. + + for ( int i = 0; i < 30; i++ ) { + final Activity activity = new Activity( i, "Activity #" + i, null ); + if ( i % 2 == 0 ) { + final Instruction instr = new Instruction( i, "Instruction #" + i ); + activity.setInstruction( instr ); + session.save( instr ); + } + else { + final WebApplication webApplication = new WebApplication( i, "http://" + i + ".com" ); + webApplication.setName( "name #" + i ); + activity.setWebApplication( webApplication ); + webApplication.getActivities().add( activity ); + session.save( webApplication ); + } + + session.save( activity ); + } + + RoleEntity roleEntity = new RoleEntity(); + roleEntity.setOid( 1L ); + + SpecializedKey specializedKey = new SpecializedKey(); + specializedKey.setOid(1L); + + MoreSpecializedKey moreSpecializedKey = new MoreSpecializedKey(); + moreSpecializedKey.setOid( 3L ); + + SpecializedEntity specializedEntity = new SpecializedEntity(); + specializedEntity.setId( 2L ); + specializedKey.addSpecializedEntity( specializedEntity ); + specializedEntity.setSpecializedKey( specializedKey); + + specializedKey.addRole( roleEntity ); + roleEntity.setKey( specializedKey ); + roleEntity.setSpecializedKey( moreSpecializedKey ); + moreSpecializedKey.addRole( roleEntity ); + session.save( specializedEntity ); + session.save( roleEntity ); + session.save( specializedKey ); + session.save( moreSpecializedKey ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from E" ).executeUpdate(); + session.createQuery( "delete from D" ).executeUpdate(); + session.createQuery( "delete from C" ).executeUpdate(); + session.createQuery( "delete from B" ).executeUpdate(); + session.createQuery( "delete from A" ).executeUpdate(); + session.createQuery( "delete from G" ).executeUpdate(); + + session.createQuery( "delete from Activity" ).executeUpdate(); + session.createQuery( "delete from Instruction" ).executeUpdate(); + session.createQuery( "delete from WebApplication" ).executeUpdate(); + + session.createQuery( "delete from SpecializedEntity" ).executeUpdate(); + session.createQuery( "delete from RoleEntity" ).executeUpdate(); + session.createQuery( "delete from MoreSpecializedKey" ).executeUpdate(); + session.createQuery( "delete from SpecializedKey" ).executeUpdate(); + session.createQuery( "delete from GenericKey" ).executeUpdate(); + session.createQuery( "delete from AbstractKey" ).executeUpdate(); + } + ); + } + + @MappedSuperclass + public static class BaseEntity { + @Id + private long oid; + private short version; + + public long getOid() { + return oid; + } + + public void setOid(long oid) { + this.oid = oid; + } + + public short getVersion() { + return version; + } + + public void setVersion(short version) { + this.version = version; + } + } + + @Entity(name = "A") + @Table(name = "A") + public static class AEntity extends BaseEntity { + @Column(name = "A") + private String a; + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + } + + + @Entity(name = "B") + @Table(name = "B") + public static class BEntity extends BaseEntity { + private Integer b1; + private String b2; + + public Integer getB1() { + return b1; + } + + public void setB1(Integer b1) { + this.b1 = b1; + } + + public String getB2() { + return b2; + } + + public void setB2(String b2) { + this.b2 = b2; + } + } + + + @Entity(name = "C") + @Table(name = "C") + public static class CEntity extends BaseEntity { + private String c1; + private String c2; + private String c3; + private Long c4; + + public String getC1() { + return c1; + } + + public void setC1(String c1) { + this.c1 = c1; + } + + public String getC2() { + return c2; + } + + @Basic(fetch = FetchType.LAZY) + public void setC2(String c2) { + this.c2 = c2; + } + + public String getC3() { + return c3; + } + + public void setC3(String c3) { + this.c3 = c3; + } + + public Long getC4() { + return c4; + } + + public void setC4(Long c4) { + this.c4 = c4; + } + } + + @Entity(name = "D") + @Table(name = "D") + public static class DEntity extends BaseEntity { + private String d; + + // ****** Relations ***************** + @OneToOne(fetch = FetchType.LAZY) +// @LazyToOne(LazyToOneOption.PROXY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("a") + public AEntity a; + + @OneToOne(fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) +// @LazyToOne(LazyToOneOption.PROXY) + @LazyGroup("c") + public CEntity c; + + @OneToMany(targetEntity = BEntity.class) + public Set bs; + + @OneToOne(mappedBy = "d", fetch = FetchType.LAZY) + @LazyToOne(LazyToOneOption.NO_PROXY) + @LazyGroup("e") + private EEntity e; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn() + @LazyGroup("g") + public GEntity g; + + @Lob + @Basic(fetch = FetchType.LAZY) + @LazyGroup("blob") + @Column(name = "blob_field") + private Blob blob; + + public String getD() { + return d; + } + + public void setD(String d) { + this.d = d; + } + + + public AEntity getA() { + return a; + } + + public void setA(AEntity a) { + this.a = a; + } + + public Set getBs() { + return bs; + } + + public void setBs(Set bs) { + this.bs = bs; + } + + public CEntity getC() { + return c; + } + + public void setC(CEntity c) { + this.c = c; + } + + public Blob getBlob() { + return blob; + } + + public void setBlob(Blob blob) { + this.blob = blob; + } + + public EEntity getE() { + return e; + } + + public void setE(EEntity e) { + this.e = e; + } + + public GEntity getG() { + return g; + } + + public void setG(GEntity g) { + this.g = g; + } + } + + @Entity(name = "E") + @Table(name = "E") + public static class EEntity extends BaseEntity { + private String e1; + private String e2; + + @OneToOne(fetch = FetchType.LAZY) + private DEntity d; + + public String getE1() { + return e1; + } + + public void setE1(String e1) { + this.e1 = e1; + } + + public String getE2() { + return e2; + } + + public void setE2(String e2) { + this.e2 = e2; + } + + public DEntity getD() { + return d; + } + + public void setD(DEntity d) { + this.d = d; + } + } + + @Entity(name = "G") + @Table(name = "G") + public static class GEntity extends BaseEntity { + + @OneToMany(mappedBy = "g") + public Set dEntities = new HashSet<>(); + + public Set getdEntities() { + return dEntities; + } + + public void setdEntities(Set dEntities) { + this.dEntities = dEntities; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ForeignCustomer.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ForeignCustomer.java new file mode 100644 index 0000000000000000000000000000000000000000..a9b0c6acc3deb5ad40c7e42cd347cb86c4c51878 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/ForeignCustomer.java @@ -0,0 +1,48 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "ForeignCustomer") +@Table(name = "foreign_cust") +public class ForeignCustomer extends Customer { + private String vat; + + public ForeignCustomer() { + } + + public ForeignCustomer( + Integer oid, + String name, + Address address, + String vat, + Customer parentCustomer) { + super( oid, name, address, parentCustomer ); + this.vat = vat; + } + + public ForeignCustomer( + Integer oid, + String name, + Address address, + String vat) { + this( oid, name, address, vat, null ); + } + + public String getVat() { + return vat; + } + + public void setVat(String vat) { + this.vat = vat; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/GenericKey.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/GenericKey.java new file mode 100644 index 0000000000000000000000000000000000000000..6f52802abf1e6b155821e67a2f415e3700a3413b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/GenericKey.java @@ -0,0 +1,27 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Table; + +@Entity(name="GenericKey") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@Table(name="PP_GenericDCKey") +public abstract class GenericKey extends AbstractKey implements Serializable { + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Instruction.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Instruction.java new file mode 100644 index 0000000000000000000000000000000000000000..56d594b368da8612767f072593f73b1d89335d84 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/Instruction.java @@ -0,0 +1,41 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Instruction") +@Table(name = "instruction") +public class Instruction extends BaseEntity { + private String summary; + + /** + * Used by Hibernate + */ + @SuppressWarnings("unused") + public Instruction() { + super(); + } + + @SuppressWarnings("WeakerAccess") + public Instruction(Integer id, String summary) { + super( id ); + this.summary = summary; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyCollectionDeletedAllowProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyCollectionDeletedAllowProxyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e3cbe4779e0adcc794e1622ee6fc08123a82c4ad --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/LazyCollectionDeletedAllowProxyTest.java @@ -0,0 +1,287 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; +import javax.persistence.Query; +import javax.persistence.Table; + +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +@TestForIssue(jiraKey = "HHH-11147") +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions(lazyLoading = true) +public class LazyCollectionDeletedAllowProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + private Long postId; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Post.class, Tag.class, AdditionalDetails.class, Label.class }; + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + ssrb.applySetting( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + } + + @Test + public void updatingAnAttributeDoesNotDeleteLazyCollectionsTest() { + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from AdditionalDetails where id = :id" ); + query.setParameter( "id", postId ); + AdditionalDetails additionalDetails = (AdditionalDetails) query.getSingleResult(); + additionalDetails.setDetails( "New data" ); + s.persist( additionalDetails ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from Post where id = :id" ); + query.setParameter( "id", postId ); + Post retrievedPost = (Post) query.getSingleResult(); + + assertFalse( "No tags found", retrievedPost.getTags().isEmpty() ); + retrievedPost.getTags().forEach( tag -> assertNotNull( tag ) ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from AdditionalDetails where id = :id" ); + query.setParameter( "id", postId ); + AdditionalDetails additionalDetails = (AdditionalDetails) query.getSingleResult(); + + Post post = additionalDetails.getPost(); + assertIsEnhancedProxy( post ); + post.setMessage( "new message" ); + } ); + + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from Post where id = :id" ); + query.setParameter( "id", postId ); + Post retrievedPost = (Post) query.getSingleResult(); + + assertEquals( "new message", retrievedPost.getMessage() ); + assertFalse( "No tags found", retrievedPost.getTags().isEmpty() ); + retrievedPost.getTags().forEach( tag -> { + assertNotNull( tag ); + assertFalse( "No Labels found", tag.getLabels().isEmpty() ); + } ); + + } ); + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + Post post = new Post(); + + Tag tag1 = new Tag( "tag1" ); + Tag tag2 = new Tag( "tag2" ); + + Label label1 = new Label( "label1" ); + Label label2 = new Label( "label2" ); + + tag1.addLabel( label1 ); + tag2.addLabel( label2 ); + + Set tagSet = new HashSet<>(); + tagSet.add( tag1 ); + tagSet.add( tag2 ); + post.setTags( tagSet ); + + AdditionalDetails details = new AdditionalDetails(); + details.setPost( post ); + post.setAdditionalDetails( details ); + details.setDetails( "Some data" ); + + postId = (Long) s.save( post ); + } ); + } + + @After + public void cleanData() { + doInHibernate( this::sessionFactory, s -> { + Query query = s.createQuery( "from Post" ); + List posts = query.getResultList(); + posts.forEach( post -> { + s.delete( post ); + } ); + } ); + } + + + private void assertIsEnhancedProxy(Object entity) { + assertThat( entity, instanceOf( PersistentAttributeInterceptable.class ) ); + + final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) entity; + final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); + assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) ); + } + + // --- // + + @Entity(name = "Tag") + @Table(name = "TAG") + private static class Tag { + + @Id + @GeneratedValue + Long id; + + String name; + + @ManyToMany(cascade = CascadeType.ALL) + Set

    Property NameProperty Value